Bumped copyright dates for 2013
[barry.git] / desktop / src / SyncStatusDlg.cc
blob5454ca121d411dc891bea0e1b859bfbdc73a137a
1 ///
2 /// \file SyncStatusDlg.cc
3 /// The dialog used during a sync, to display status messages
4 /// and error messages, and handle sync conflicts via callback.
5 ///
7 /*
8 Copyright (C) 2010-2013, Net Direct Inc. (http://www.netdirect.ca/)
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 See the GNU General Public License in the COPYING file at the
20 root directory of this project for more details.
23 #include "SyncStatusDlg.h"
24 #include "ConflictDlg.h"
25 #include "windowids.h"
26 #include "configui.h"
27 #include "barrydesktop.h"
28 #include <wx/filename.h>
29 #include <iostream>
30 #include <sstream>
31 #include <iomanip>
32 #include <iterator>
33 #include "wxi18n.h"
35 using namespace std;
37 SillyBuffer sb;
39 //////////////////////////////////////////////////////////////////////////////
40 // StatusConnection class
42 StatusConnection::StatusConnection(SyncStatusDlg &dlg,
43 wxTextCtrl &window)
44 : m_dlg(dlg)
45 , m_status(window)
49 bool StatusConnection::OnPoke(const wxString &topic,
50 const wxString &item,
51 wxChar *data,
52 int size,
53 wxIPCFormat format)
55 if( topic != TOPIC_STATUS )
56 return false;
58 wxString msg(data, size);
60 if( item == STATUS_ITEM_ERROR ) {
61 m_dlg.Print(msg, *wxRED);
62 m_dlg.ShortPrint(msg);
64 else if( item == STATUS_ITEM_ENTRY ) {
65 m_dlg.Print(msg, *wxBLUE);
66 m_dlg.ShortPrint(_W("Syncing entries..."));
68 else if( item == STATUS_ITEM_MAPPING ) {
69 m_dlg.Print(msg, *wxBLUE);
71 else if( item == STATUS_ITEM_ENGINE ) {
72 wxString key = ENGINE_STATUS_SLOW_SYNC;
73 if( msg.substr(0, key.size()) == key ) {
74 m_dlg.OnSlowSync();
76 else {
77 m_dlg.Print(msg, *wxGREEN);
78 m_dlg.ShortPrint(msg);
81 else if( item == STATUS_ITEM_MEMBER ) {
82 m_dlg.Print(msg, *wxCYAN);
84 else {
85 // unknown item
86 m_dlg.Print(msg, *wxBLACK);
88 return true;
92 //////////////////////////////////////////////////////////////////////////////
93 // ConflictConnection class
95 ConflictConnection::ConflictConnection(SyncStatusDlg &dlg)
96 : m_dlg(dlg)
97 , m_asking_user(false)
98 , m_current_sequenceID(-1)
99 , m_current_offset(-1)
100 , m_expected_total_changes(0)
102 // check if there's a favoured plugin name from the DeviceEntry config
103 if( m_dlg.GetCurrentDevice() &&
104 m_dlg.GetCurrentDevice()->GetExtras() )
106 m_always.m_favour_plugin_name = m_dlg.GetCurrentDevice()->
107 GetExtras()->m_favour_plugin_name;
111 bool ConflictConnection::OnPoke(const wxString &topic,
112 const wxString &item,
113 wxChar *data,
114 int size,
115 wxIPCFormat format)
117 barryverbose("Conflict::OnPoke: " << string(topic.utf8_str()) << ", "
118 << string(item.utf8_str()));
120 if( topic != TOPIC_CONFLICT )
121 return false;
123 // if currently handling a user request, don't change
124 // the state machine... the client shouldn't be poking
125 // if he just Request'd anyway
126 if( m_asking_user )
127 return false;
129 wxString msg(data, size);
130 istringstream iss(string(msg.utf8_str()));
132 if( item == CONFLICT_ITEM_START ) {
133 // overwrite any existing sequence
134 m_changes.clear();
135 iss >> m_current_sequenceID
136 >> m_current_offset
137 >> m_expected_total_changes
138 >> m_supported_commands;
139 if( !iss || m_current_offset != 0 || m_expected_total_changes < 2) {
140 // invalid start command, throw it away
141 m_current_sequenceID = -1;
142 m_current_offset = -1;
143 m_expected_total_changes = 0;
144 return false;
147 barryverbose("New conflict item: " << m_current_sequenceID
148 << ", " << m_current_offset << ", "
149 << "expected changes: " << m_expected_total_changes
150 << ", supported commands: " << m_supported_commands);
152 else if( item == CONFLICT_ITEM_CHANGE ) {
153 int sequenceID = 0, offset = 0;
154 OpenSync::SyncChange change;
155 iss >> sequenceID >> offset >> change.id >> ws;
156 getline(iss, change.plugin_name);
157 getline(iss, change.uid);
158 change.member_id = 0;
160 if( !iss || sequenceID != m_current_sequenceID || offset != (m_current_offset + 1) ) {
161 return false;
164 m_current_offset = offset;
166 // grab remaining "printable data"
167 copy((istreambuf_iterator<char>(iss)),
168 (istreambuf_iterator<char>()),
169 back_inserter(change.printable_data));
171 m_changes.push_back(change);
172 barryverbose("New conflict change: " << m_current_sequenceID
173 << ", " << m_current_offset << ", data: "
174 << change.printable_data);
177 return true;
180 wxChar* ConflictConnection::OnRequest(const wxString &topic,
181 const wxString &item,
182 int *size,
183 wxIPCFormat format)
185 // make sure we are in a valid sequence
186 if( m_current_sequenceID == -1 || m_current_offset == -1 || m_expected_total_changes < 2) {
187 barryverbose("Conflict: not in a valid sequence: "
188 << m_current_sequenceID << ", "
189 << m_current_offset << ", "
190 << m_expected_total_changes);
191 return NULL;
194 // make sure we have a valid set of changes
195 if( m_current_offset != m_expected_total_changes || (size_t)m_expected_total_changes != m_changes.size() ) {
196 barryverbose("Conflict: not a valid set of changes: "
197 << m_current_offset << ", "
198 << m_expected_total_changes << ", "
199 << m_changes.size());
200 return NULL;
203 m_asking_user = true;
205 // ask the user what to do
206 if( !m_dlg.GetCurrentDevice() ) {
207 barryverbose("Conflict: current device is null");
208 return NULL;
210 OpenSync::API *engine = m_dlg.GetCurrentDevice()->GetEngine();
211 ConflictDlg dlg(&m_dlg, *engine, m_supported_commands,
212 m_changes, m_always);
213 m_dlg.StopTimer();
214 dlg.ShowModal();
215 m_dlg.StartTimer();
217 // done
218 m_asking_user = false;
220 // did the user ask to kill the sync?
221 if( dlg.IsKillSync() ) {
222 // die!
223 m_dlg.KillSync();
224 return NULL;
227 // prepare response for client
228 ostringstream oss;
229 oss << m_current_sequenceID << " " << dlg.GetCommand();
230 m_buf.buf(oss.str());
231 // oddly, this is the size in bytes, not in wxChars
232 *size = (m_buf.size() + 1) * sizeof(wxChar);
233 return m_buf.buf();
237 //////////////////////////////////////////////////////////////////////////////
238 // SyncStatus class
240 BEGIN_EVENT_TABLE(SyncStatusDlg, wxDialog)
241 EVT_INIT_DIALOG (SyncStatusDlg::OnInitDialog)
242 EVT_BUTTON (Dialog_SyncStatus_RunAppButton,
243 SyncStatusDlg::OnRunApp)
244 EVT_BUTTON (Dialog_SyncStatus_SyncAgainButton,
245 SyncStatusDlg::OnSyncAgain)
246 EVT_BUTTON (Dialog_SyncStatus_KillCloseButton,
247 SyncStatusDlg::OnKillClose)
248 EVT_BUTTON (Dialog_SyncStatus_ShowDetailsButton,
249 SyncStatusDlg::OnShowDetails)
250 EVT_END_PROCESS (Dialog_SyncStatus_SyncTerminated,
251 SyncStatusDlg::OnExecTerminated)
252 EVT_TIMER (Dialog_SyncStatus_Timer,
253 SyncStatusDlg::OnTimer)
254 END_EVENT_TABLE()
256 SyncStatusDlg::SyncStatusDlg(wxWindow *parent,
257 const DeviceSet::subset_type &subset)
258 : wxDialog(parent, Dialog_SyncStatus, _W("Device Sync Progress"),
259 wxDefaultPosition, wxDefaultSize,
260 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
261 , TermCatcher(this, Dialog_SyncStatus_SyncTerminated)
262 , m_subset(subset)
263 , m_next_device(m_subset.begin())
264 , m_jailexec(this)
265 , m_killingjail(false)
266 , m_timer(this, Dialog_SyncStatus_Timer)
267 , m_topsizer(0)
268 , m_short_status(0)
269 , m_throbber(0)
270 , m_status_edit(0)
271 , m_runapp_button(0)
272 , m_syncagain_button(0)
273 , m_killclose_button(0)
274 , m_details_button(0)
275 , m_repositioned(false)
277 wxBusyCursor wait;
279 // setup the raw GUI
280 CreateLayout();
282 // create the IPC server
283 wxServer::Create(SERVER_SERVICE_NAME);
286 SyncStatusDlg::~SyncStatusDlg()
288 // make sure bsyncjail dies if we do
289 m_jailexec.KillApp();
292 void SyncStatusDlg::CreateLayout()
294 m_topsizer = new wxBoxSizer(wxVERTICAL);
295 AddStatusSizer(m_topsizer);
296 AddButtonSizer(m_topsizer);
298 SetSizer(m_topsizer);
299 m_topsizer->SetSizeHints(this);
300 m_topsizer->Layout();
303 void SyncStatusDlg::AddStatusSizer(wxSizer *sizer)
305 wxSizer *ss = new wxStaticBoxSizer(
306 new wxStaticBox(this, wxID_ANY, _W("Sync Progress")),
307 wxVERTICAL
310 // add a set of short status and progress throbber
311 wxSizer *shorts = new wxBoxSizer(wxHORIZONTAL);
312 shorts->Add(
313 m_short_status = new wxStaticText(this, wxID_ANY, _T(""),
314 wxDefaultPosition, wxSize(350, -1),
315 wxALIGN_LEFT),
316 1, wxALIGN_CENTRE_VERTICAL | wxALL, 2);
317 shorts->Add(
318 m_throbber = new wxGauge(this, wxID_ANY, 0),
319 0, wxALL | wxEXPAND, 2);
320 ss->Add( shorts, 0, wxEXPAND, 0);
322 ss->Add(
323 m_status_edit = new wxTextCtrl(this, wxID_ANY, _T(""),
324 wxDefaultPosition, wxSize(475, 450),
325 wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH),
326 0, wxALL | wxEXPAND, 2);
327 // start with the details hidden
328 m_status_edit->Hide();
330 sizer->Add(ss, 1, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, 5);
333 void SyncStatusDlg::AddButtonSizer(wxSizer *sizer)
335 wxSizer *button = new wxBoxSizer(wxHORIZONTAL);
337 button->Add(
338 m_details_button = new wxButton(this,
339 Dialog_SyncStatus_ShowDetailsButton,
340 _W("Show Details")),
341 0, wxALL, 3);
343 button->Add( -1, -1, 1 );
345 if( m_subset.size() == 1 ) {
346 button->Add(
347 m_runapp_button = new wxButton(this,
348 Dialog_SyncStatus_RunAppButton,
349 _W("Run App")),
350 0, wxALL, 3);
352 button->Add(
353 m_syncagain_button = new wxButton(this,
354 Dialog_SyncStatus_SyncAgainButton,
355 _W("Sync Again")),
356 0, wxALL, 3);
357 button->Add(
358 m_killclose_button = new wxButton(this,
359 Dialog_SyncStatus_KillCloseButton,
360 _W("Kill Sync")),
361 0, wxALL, 3);
363 sizer->Add(button, 0, wxALL | wxEXPAND, 5);
366 void SyncStatusDlg::SetRunning()
368 if( m_runapp_button )
369 m_runapp_button->Enable(false);
370 m_syncagain_button->Enable(false);
371 m_killclose_button->SetLabel(_W("Kill Sync"));
372 m_killclose_button->Enable(true);
373 UpdateTitle();
375 m_throbber->SetRange(10);
376 m_throbber->SetValue(0);
377 StartTimer();
380 void SyncStatusDlg::SetClose()
382 if( m_runapp_button )
383 m_runapp_button->Enable(true);
384 m_syncagain_button->Enable(true);
385 m_killclose_button->SetLabel(_W("Close"));
386 m_killclose_button->Enable(true);
387 UpdateTitle();
389 m_throbber->SetRange(10);
390 m_throbber->SetValue(10);
391 StopTimer();
394 void SyncStatusDlg::PrintStd(const std::string &msg, const wxColour &colour)
396 Print(wxString(msg.c_str(), wxConvUTF8), colour);
399 void SyncStatusDlg::Print(const wxString &msg, const wxColour &colour)
401 m_status_edit->SetDefaultStyle(wxTextAttr(colour));
402 m_status_edit->AppendText(_T("\n") + msg);
405 void SyncStatusDlg::ShortPrintStd(const std::string &msg)
407 ShortPrint(wxString(msg.c_str(), wxConvUTF8));
410 void SyncStatusDlg::ShortPrint(const wxString &msg)
412 m_short_status->SetLabel(msg);
415 void SyncStatusDlg::Throb()
417 m_throbber->Pulse();
420 void SyncStatusDlg::StartTimer()
422 m_timer.Start(250);
425 void SyncStatusDlg::StopTimer()
427 m_timer.Stop();
430 DeviceEntry* SyncStatusDlg::GetCurrentDevice()
432 if( m_current_device == m_subset.end() )
433 return 0;
434 return &(*(*m_current_device));
437 void SyncStatusDlg::UpdateTitle()
439 if( m_next_device == m_subset.end() ) {
440 SetTitle(_W("Sync Progress Dialog"));
442 else {
443 ostringstream oss;
444 oss << _C("Syncing: ") << (*m_next_device)->GetPin().Str()
445 << _C(" with ") << (*m_next_device)->GetAppNames();
446 wxString label(oss.str().c_str(), wxConvUTF8);
447 SetTitle(label);
451 void SyncStatusDlg::UpdateLastSyncTime()
453 if( m_current_device != m_subset.end() &&
454 (*m_current_device)->GetConfigGroup() &&
455 (*m_current_device)->GetExtras() )
457 (*m_current_device)->GetExtras()->m_last_sync_time = time(NULL);
458 (*m_current_device)->GetExtras()->Save(
459 wxGetApp().GetGlobalConfig(),
460 (*m_current_device)->GetConfigGroup()->GetGroupName());
464 void SyncStatusDlg::KillSync()
466 m_jailexec.KillApp(m_killingjail);
467 m_killingjail = true;
469 // jump to the end of the sync roster, so we don't start the
470 // next device
471 m_current_device = m_next_device = m_subset.end();
474 void SyncStatusDlg::StartNextSync()
476 m_killingjail = false;
478 // anything to do?
479 if( m_next_device == m_subset.end() ) {
480 Print(_W("No more devices to sync."), *wxBLACK);
481 SetClose();
482 return;
484 else {
485 SetRunning();
488 // grab all required information we need to sync
489 m_current_device = m_next_device;
490 DeviceEntry &device = *(*m_next_device);
491 const DeviceExtras *extras = device.GetExtras();
492 m_device_id = device.GetPin().Str() + " (" + device.GetDeviceName() + ")";
494 if( !device.IsConfigured() ) {
495 PrintStd(m_device_id + _C(" is not configured, skipping."), *wxRED);
496 ShortPrintStd(_C("Skipping unconfigured: ") + m_device_id);
497 ++m_next_device;
498 m_current_device = m_subset.end();
499 StartNextSync();
500 return;
503 OpenSync::API *engine = device.GetEngine();
504 OpenSync::Config::Group *group = device.GetConfigGroup();
505 string group_name = group->GetGroupName();
507 string statusmsg = _C("Starting sync for: ") + m_device_id;
508 PrintStd(statusmsg, *wxBLACK);
509 ShortPrintStd(statusmsg);
511 // for each plugin / app, perform the pre-sync steps
512 for( OpenSync::Config::Group::iterator i = group->begin();
513 i != group->end();
514 ++i )
516 ConfigUI::ptr ui = ConfigUI::CreateConfigUI((*i)->GetAppName());
517 if( ui.get() ) {
518 ui->PreSyncAppInit();
522 // initialize sync jail process
523 if( m_jailexec.IsAppRunning() ) {
524 wxString msg(_W("ERROR: App running in StartNextSync()"));
525 Print(msg, *wxRED);
526 ShortPrint(msg);
527 SetClose();
528 return;
531 // bsyncjail is in pkglibexecdir
532 wxFileName path(wxTheApp->argv[0]);
533 wxString command(BARRYDESKTOP_PKGLIBEXECDIR, wxConvUTF8);
534 command += path.GetPathSeparator();
535 command += _T("bsyncjail ");
536 command += wxString(engine->GetVersion(), wxConvUTF8);
537 command += _T(" ");
538 command += wxString(group_name.c_str(), wxConvUTF8);
539 command += _T(" ");
540 ostringstream sync_code;
541 sync_code << dec << extras->m_sync_types;
542 command += wxString(sync_code.str().c_str(), wxConvUTF8);
544 if( !m_jailexec.Run(NULL, "bsyncjail", command) ) {
545 Print(_W("ERROR: unable to start bsyncjail: ") + command, *wxRED);
546 ShortPrint(_W("ERROR: unable to start bsyncjail"));
547 SetClose();
548 return;
551 // sync is underway... advance to the next device
552 ++m_next_device;
555 void SyncStatusDlg::OnSlowSync()
557 Print(_W("Slow sync detected! Killing sync automatically."), *wxRED);
558 KillSync();
560 Print(_W("Slow syncs are known to be unreliable."), *wxBLACK);
561 Print(_W("Do a 1 Way Reset, and sync again."), *wxBLACK);
564 void SyncStatusDlg::OnInitDialog(wxInitDialogEvent &event)
566 barryverbose("OnInitDialog");
567 StartNextSync();
570 void SyncStatusDlg::OnRunApp(wxCommandEvent &event)
572 if( m_subset.size() != 1 )
573 return;
575 if( m_ui.get() && m_ui->IsAppRunning() ) {
576 wxMessageBox(_W("The application is already running."),
577 _W("Run Application"), wxOK | wxICON_ERROR, this);
578 return;
581 OpenSync::Config::Group *group = m_subset[0]->GetConfigGroup();
582 if( !group )
583 return;
585 m_ui = ConfigUI::CreateConfigUI(group->GetAppNames());
586 if( !m_ui.get() )
587 return;
589 m_ui->RunApp(this);
592 void SyncStatusDlg::OnSyncAgain(wxCommandEvent &event)
594 m_next_device = m_subset.begin();
595 StartNextSync();
598 void SyncStatusDlg::OnKillClose(wxCommandEvent &event)
600 if( m_jailexec.IsAppRunning() ) {
601 int choice;
602 if( m_killingjail ) {
603 choice = wxMessageBox(_W("Already killing sync. Kill again?"),
604 _W("Kill Sync"), wxYES_NO | wxICON_QUESTION, this);
606 else {
607 choice = wxMessageBox(_W("This will kill the syncing child process, and will likely require a configuration reset.\n\nKill sync process anyway?"),
608 _W("Kill Sync"), wxYES_NO | wxICON_QUESTION, this);
611 if( choice == wxYES ) {
612 KillSync();
614 Print(_W("Killing sync... this may take a little while..."), *wxRED);
615 Print(_W("Remember to re-plug your device."), *wxRED);
616 // let the terminate call clean up the buttons
617 return;
620 else {
621 EndModal(0);
625 void SyncStatusDlg::OnShowDetails(wxCommandEvent &event)
627 if( m_status_edit->IsShown() ) {
628 m_status_edit->Hide();
629 m_details_button->SetLabel(_W("Show Details"));
631 else {
632 if( !m_repositioned ) {
633 // try to position the window in a readable spot...
634 // do this first, so that the resize
635 wxSize size = GetSize();
636 wxPoint pos = GetScreenPosition();
637 int screen_height = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y);
638 int new_height = size.GetHeight() + 470;
639 if( (pos.y + new_height) > screen_height &&
640 new_height < screen_height )
642 int wiggle_room = screen_height - new_height;
643 int y = wiggle_room / 2;
644 Move(pos.x, y);
647 m_repositioned = true;
650 m_status_edit->Show();
651 m_details_button->SetLabel(_W("Hide Details"));
653 m_topsizer->Fit(this);
656 wxConnectionBase* SyncStatusDlg::OnAcceptConnection(const wxString &topic)
658 wxConnectionBase *con = 0;
660 if( topic == TOPIC_STATUS && m_status_edit )
661 con = new StatusConnection(*this, *m_status_edit);
662 else if( topic == TOPIC_CONFLICT )
663 con = new ConflictConnection(*this);
665 if( con )
666 m_connections.push_back( dynamic_cast<OptOut::Element*> (con) );
668 return con;
671 void SyncStatusDlg::OnExecTerminated(wxProcessEvent &event)
673 ostringstream oss;
674 if( m_killingjail )
675 oss << _C("Sync terminated: ");
676 else
677 oss << _C("Sync finished: ");
678 oss << m_device_id;
679 if( m_jailexec.GetChildExitCode() )
680 oss << _C(" with error code ") << m_jailexec.GetChildExitCode();
681 PrintStd(oss.str(), *wxBLACK);
682 ShortPrintStd(oss.str());
683 UpdateLastSyncTime();
685 m_current_device = m_subset.end();
687 StartNextSync();
690 void SyncStatusDlg::OnTimer(wxTimerEvent &event)
692 Throb();