desktop: stop the progress bar timer when dealing with a conflict
[barry/progweb.git] / desktop / src / SyncStatusDlg.cc
blob92693dbc86841d6618d930be19db656113de3506
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, 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 <wx/filename.h>
28 #include <iostream>
29 #include <sstream>
30 #include <iomanip>
31 #include <iterator>
33 using namespace std;
35 SillyBuffer sb;
37 //////////////////////////////////////////////////////////////////////////////
38 // StatusConnection class
40 StatusConnection::StatusConnection(SyncStatusDlg &dlg,
41 wxTextCtrl &window)
42 : m_dlg(dlg)
43 , m_status(window)
47 bool StatusConnection::OnPoke(const wxString &topic,
48 const wxString &item,
49 wxChar *data,
50 int size,
51 wxIPCFormat format)
53 if( topic != TOPIC_STATUS )
54 return false;
56 wxString msg(data, size);
58 if( item == STATUS_ITEM_ERROR ) {
59 m_dlg.Print(msg, *wxRED);
60 m_dlg.ShortPrint(msg);
62 else if( item == STATUS_ITEM_ENTRY ) {
63 m_dlg.Print(msg, *wxBLUE);
64 m_dlg.ShortPrint("Syncing entries...");
66 else if( item == STATUS_ITEM_MAPPING ) {
67 m_dlg.Print(msg, *wxBLUE);
69 else if( item == STATUS_ITEM_ENGINE ) {
70 wxString key = ENGINE_STATUS_SLOW_SYNC;
71 if( msg.substr(0, key.size()) == key ) {
72 m_dlg.OnSlowSync();
74 else {
75 m_dlg.Print(msg, *wxGREEN);
76 m_dlg.ShortPrint(msg);
79 else if( item == STATUS_ITEM_MEMBER ) {
80 m_dlg.Print(msg, *wxCYAN);
82 else {
83 // unknown item
84 m_dlg.Print(msg, *wxBLACK);
86 return true;
90 //////////////////////////////////////////////////////////////////////////////
91 // ConflictConnection class
93 ConflictConnection::ConflictConnection(SyncStatusDlg &dlg)
94 : m_dlg(dlg)
95 , m_asking_user(false)
96 , m_current_sequenceID(-1)
97 , m_current_offset(-1)
98 , m_expected_total_changes(0)
100 // check if there's a favoured plugin name from the DeviceEntry config
101 if( m_dlg.GetCurrentDevice() &&
102 m_dlg.GetCurrentDevice()->GetExtras() )
104 m_always.m_favour_plugin_name = m_dlg.GetCurrentDevice()->
105 GetExtras()->m_favour_plugin_name;
109 bool ConflictConnection::OnPoke(const wxString &topic,
110 const wxString &item,
111 wxChar *data,
112 int size,
113 wxIPCFormat format)
115 barryverbose("Conflict::OnPoke: " << topic.utf8_str() << ", "
116 << item.utf8_str());
118 if( topic != TOPIC_CONFLICT )
119 return false;
121 // if currently handling a user request, don't change
122 // the state machine... the client shouldn't be poking
123 // if he just Request'd anyway
124 if( m_asking_user )
125 return false;
127 wxString msg(data, size);
128 istringstream iss(string(msg.utf8_str()));
130 if( item == CONFLICT_ITEM_START ) {
131 // overwrite any existing sequence
132 m_changes.clear();
133 iss >> m_current_sequenceID
134 >> m_current_offset
135 >> m_expected_total_changes
136 >> m_supported_commands;
137 if( !iss || m_current_offset != 0 || m_expected_total_changes < 2) {
138 // invalid start command, throw it away
139 m_current_sequenceID = -1;
140 m_current_offset = -1;
141 m_expected_total_changes = 0;
142 return false;
145 barryverbose("New conflict item: " << m_current_sequenceID
146 << ", " << m_current_offset << ", "
147 << "expected changes: " << m_expected_total_changes
148 << ", supported commands: " << m_supported_commands);
150 else if( item == CONFLICT_ITEM_CHANGE ) {
151 int sequenceID = 0, offset = 0;
152 OpenSync::SyncChange change;
153 iss >> sequenceID >> offset >> change.id >> ws;
154 getline(iss, change.plugin_name);
155 getline(iss, change.uid);
156 change.member_id = 0;
158 if( !iss || sequenceID != m_current_sequenceID || offset != (m_current_offset + 1) ) {
159 return false;
162 m_current_offset = offset;
164 // grab remaining "printable data"
165 copy((istreambuf_iterator<char>(iss)),
166 (istreambuf_iterator<char>()),
167 back_inserter(change.printable_data));
169 m_changes.push_back(change);
170 barryverbose("New conflict change: " << m_current_sequenceID
171 << ", " << m_current_offset << ", data: "
172 << change.printable_data);
175 return true;
178 wxChar* ConflictConnection::OnRequest(const wxString &topic,
179 const wxString &item,
180 int *size,
181 wxIPCFormat format)
183 // make sure we are in a valid sequence
184 if( m_current_sequenceID == -1 || m_current_offset == -1 || m_expected_total_changes < 2) {
185 barryverbose("Conflict: not in a valid sequence: "
186 << m_current_sequenceID << ", "
187 << m_current_offset << ", "
188 << m_expected_total_changes);
189 return NULL;
192 // make sure we have a valid set of changes
193 if( m_current_offset != m_expected_total_changes || (size_t)m_expected_total_changes != m_changes.size() ) {
194 barryverbose("Conflict: not a valid set of changes: "
195 << m_current_offset << ", "
196 << m_expected_total_changes << ", "
197 << m_changes.size());
198 return NULL;
201 m_asking_user = true;
203 // ask the user what to do
204 ConflictDlg dlg(&m_dlg, m_supported_commands, m_changes, m_always);
205 m_dlg.StopTimer();
206 dlg.ShowModal();
207 m_dlg.StartTimer();
209 // done
210 m_asking_user = false;
212 // did the user ask to kill the sync?
213 if( dlg.IsKillSync() ) {
214 // die!
215 m_dlg.KillSync();
216 return NULL;
219 // prepare response for client
220 ostringstream oss;
221 oss << m_current_sequenceID << " " << dlg.GetCommand();
222 m_buf.buf(oss.str());
223 // oddly, this is the size in bytes, not in wxChars
224 *size = (m_buf.size() + 1) * sizeof(wxChar);
225 return m_buf.buf();
229 //////////////////////////////////////////////////////////////////////////////
230 // SyncStatus class
232 BEGIN_EVENT_TABLE(SyncStatusDlg, wxDialog)
233 EVT_INIT_DIALOG (SyncStatusDlg::OnInitDialog)
234 EVT_BUTTON (Dialog_SyncStatus_RunAppButton,
235 SyncStatusDlg::OnRunApp)
236 EVT_BUTTON (Dialog_SyncStatus_SyncAgainButton,
237 SyncStatusDlg::OnSyncAgain)
238 EVT_BUTTON (Dialog_SyncStatus_KillCloseButton,
239 SyncStatusDlg::OnKillClose)
240 EVT_BUTTON (Dialog_SyncStatus_ShowDetailsButton,
241 SyncStatusDlg::OnShowDetails)
242 EVT_END_PROCESS (Dialog_SyncStatus_SyncTerminated,
243 SyncStatusDlg::OnExecTerminated)
244 EVT_TIMER (Dialog_SyncStatus_Timer,
245 SyncStatusDlg::OnTimer)
246 END_EVENT_TABLE()
248 SyncStatusDlg::SyncStatusDlg(wxWindow *parent,
249 const DeviceSet::subset_type &subset)
250 : wxDialog(parent, Dialog_SyncStatus, _T("Device Sync Progress"))
251 , TermCatcher(this, Dialog_SyncStatus_SyncTerminated)
252 , m_subset(subset)
253 , m_next_device(m_subset.begin())
254 , m_jailexec(this)
255 , m_killingjail(false)
256 , m_timer(this, Dialog_SyncStatus_Timer)
257 , m_topsizer(0)
258 , m_short_status(0)
259 , m_throbber(0)
260 , m_status_edit(0)
261 , m_runapp_button(0)
262 , m_syncagain_button(0)
263 , m_killclose_button(0)
265 wxBusyCursor wait;
267 // setup the raw GUI
268 CreateLayout();
270 // create the IPC server
271 wxServer::Create(SERVER_SERVICE_NAME);
274 SyncStatusDlg::~SyncStatusDlg()
276 // make sure bsyncjail dies if we do
277 m_jailexec.KillApp();
280 void SyncStatusDlg::CreateLayout()
282 m_topsizer = new wxBoxSizer(wxVERTICAL);
283 AddStatusSizer(m_topsizer);
284 AddButtonSizer(m_topsizer);
286 SetSizer(m_topsizer);
287 m_topsizer->SetSizeHints(this);
288 m_topsizer->Layout();
291 void SyncStatusDlg::AddStatusSizer(wxSizer *sizer)
293 wxSizer *ss = new wxStaticBoxSizer(
294 new wxStaticBox(this, wxID_ANY, _T("Sync Progress")),
295 wxVERTICAL
298 // add a set of short status and progress throbber
299 wxSizer *shorts = new wxBoxSizer(wxHORIZONTAL);
300 shorts->Add(
301 m_short_status = new wxStaticText(this, wxID_ANY, _T(""),
302 wxDefaultPosition, wxSize(350, -1),
303 wxALIGN_LEFT),
304 1, wxALIGN_CENTRE_VERTICAL | wxALL, 2);
305 shorts->Add(
306 m_throbber = new wxGauge(this, wxID_ANY, 0),
307 0, wxALL | wxEXPAND, 2);
308 ss->Add( shorts, 0, wxEXPAND, 0);
310 ss->Add(
311 m_status_edit = new wxTextCtrl(this, wxID_ANY, _T(""),
312 wxDefaultPosition, wxSize(475, 450),
313 wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH),
314 0, wxALL | wxEXPAND, 2);
315 // start with the details hidden
316 m_status_edit->Hide();
318 sizer->Add(ss, 1, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, 5);
321 void SyncStatusDlg::AddButtonSizer(wxSizer *sizer)
323 wxSizer *button = new wxBoxSizer(wxHORIZONTAL);
325 button->Add(
326 m_details_button = new wxButton(this,
327 Dialog_SyncStatus_ShowDetailsButton,
328 _T("Show Details")),
329 0, wxALL, 3);
331 button->Add( -1, -1, 1 );
333 if( m_subset.size() == 1 ) {
334 button->Add(
335 m_runapp_button = new wxButton(this,
336 Dialog_SyncStatus_RunAppButton,
337 _T("Run App")),
338 0, wxALL, 3);
340 button->Add(
341 m_syncagain_button = new wxButton(this,
342 Dialog_SyncStatus_SyncAgainButton,
343 _T("Sync Again")),
344 0, wxALL, 3);
345 button->Add(
346 m_killclose_button = new wxButton(this,
347 Dialog_SyncStatus_KillCloseButton,
348 _T("Kill Sync")),
349 0, wxALL, 3);
351 sizer->Add(button, 0, wxALL | wxEXPAND, 5);
354 void SyncStatusDlg::SetRunning()
356 if( m_runapp_button )
357 m_runapp_button->Enable(false);
358 m_syncagain_button->Enable(false);
359 m_killclose_button->SetLabel(_T("Kill Sync"));
360 m_killclose_button->Enable(true);
361 UpdateTitle();
363 m_throbber->SetRange(10);
364 m_throbber->SetValue(0);
365 StartTimer();
368 void SyncStatusDlg::SetClose()
370 if( m_runapp_button )
371 m_runapp_button->Enable(true);
372 m_syncagain_button->Enable(true);
373 m_killclose_button->SetLabel(_T("Close"));
374 m_killclose_button->Enable(true);
375 UpdateTitle();
377 m_throbber->SetRange(10);
378 m_throbber->SetValue(10);
379 StopTimer();
382 void SyncStatusDlg::Print(const std::string &msg, const wxColour &colour)
384 Print(wxString(msg.c_str(), wxConvUTF8), colour);
387 void SyncStatusDlg::Print(const wxString &msg, const wxColour &colour)
389 m_status_edit->SetDefaultStyle(wxTextAttr(colour));
390 m_status_edit->AppendText(_T("\n") + msg);
393 void SyncStatusDlg::ShortPrint(const std::string &msg)
395 ShortPrint(wxString(msg.c_str(), wxConvUTF8));
398 void SyncStatusDlg::ShortPrint(const wxString &msg)
400 m_short_status->SetLabel(msg);
403 void SyncStatusDlg::Throb()
405 m_throbber->Pulse();
408 void SyncStatusDlg::StartTimer()
410 m_timer.Start(250);
413 void SyncStatusDlg::StopTimer()
415 m_timer.Stop();
418 DeviceEntry* SyncStatusDlg::GetCurrentDevice()
420 if( m_current_device == m_subset.end() )
421 return 0;
422 return &(*(*m_current_device));
425 void SyncStatusDlg::UpdateTitle()
427 if( m_next_device == m_subset.end() ) {
428 SetTitle(_T("Sync Progress Dialog"));
430 else {
431 ostringstream oss;
432 oss << "Syncing: " << (*m_next_device)->GetPin().str()
433 << " with " << (*m_next_device)->GetAppNames();
434 wxString label(oss.str().c_str(), wxConvUTF8);
435 SetTitle(label);
439 void SyncStatusDlg::KillSync()
441 m_jailexec.KillApp(m_killingjail);
442 m_killingjail = true;
444 // jump to the end of the sync roster, so we don't start the
445 // next device
446 m_current_device = m_next_device = m_subset.end();
449 void SyncStatusDlg::StartNextSync()
451 m_killingjail = false;
453 // anything to do?
454 if( m_next_device == m_subset.end() ) {
455 Print("No more devices to sync.", *wxBLACK);
456 SetClose();
457 return;
459 else {
460 SetRunning();
463 // grab all required information we need to sync
464 m_current_device = m_next_device;
465 DeviceEntry &device = *(*m_next_device);
466 m_device_id = device.GetPin().str() + " (" + device.GetDeviceName() + ")";
468 if( !device.IsConfigured() ) {
469 Print(m_device_id + " is not configured, skipping.", *wxRED);
470 ++m_next_device;
471 m_current_device = m_subset.end();
472 StartNextSync();
473 return;
476 OpenSync::API *engine = device.GetEngine();
477 OpenSync::Config::Group *group = device.GetConfigGroup();
478 string group_name = group->GetGroupName();
480 string statusmsg = "Starting sync for: " + m_device_id;
481 Print(statusmsg, *wxBLACK);
482 ShortPrint(statusmsg);
484 // for each plugin / app, perform the pre-sync steps
485 for( OpenSync::Config::Group::iterator i = group->begin();
486 i != group->end();
487 ++i )
489 ConfigUI::ptr ui = ConfigUI::CreateConfigUI((*i)->GetAppName());
490 if( ui.get() ) {
491 ui->PreSyncAppInit();
495 // initialize sync jail process
496 if( m_jailexec.IsAppRunning() ) {
497 string msg = "ERROR: App running in StartNextSync()";
498 Print(msg, *wxRED);
499 ShortPrint(msg);
500 SetClose();
501 return;
504 // assume that bsyncjail is in the same directory as barrydesktop
505 wxFileName path(wxTheApp->argv[0]);
506 wxString command = path.GetPath();
507 command += path.GetPathSeparator();
508 command += _T("bsyncjail ");
509 command += wxString(engine->GetVersion(), wxConvUTF8);
510 command += _T(" ");
511 command += wxString(group_name.c_str(), wxConvUTF8);
513 if( !m_jailexec.Run(NULL, "bsyncjail", command) ) {
514 Print("ERROR: unable to start bsyncjail: " + string(command.utf8_str()), *wxRED);
515 ShortPrint("ERROR: unable to start bsyncjail");
516 SetClose();
517 return;
520 // sync is underway... advance to the next device
521 ++m_next_device;
524 void SyncStatusDlg::OnSlowSync()
526 Print("Slow sync detected! Killing sync automatically.", *wxRED);
527 KillSync();
529 Print("Slow syncs are known to be unreliable.", *wxBLACK);
530 Print("Do a 1 Way Reset, and sync again.", *wxBLACK);
533 void SyncStatusDlg::OnInitDialog(wxInitDialogEvent &event)
535 barryverbose("OnInitDialog");
536 StartNextSync();
539 void SyncStatusDlg::OnRunApp(wxCommandEvent &event)
541 if( m_subset.size() != 1 )
542 return;
544 if( m_ui.get() && m_ui->IsAppRunning() ) {
545 wxMessageBox(_T("The application is already running."),
546 _T("Run Application"), wxOK | wxICON_ERROR);
547 return;
550 OpenSync::Config::Group *group = m_subset[0]->GetConfigGroup();
551 if( !group )
552 return;
554 m_ui = ConfigUI::CreateConfigUI(group->GetAppNames());
555 if( !m_ui.get() )
556 return;
558 m_ui->RunApp(this);
561 void SyncStatusDlg::OnSyncAgain(wxCommandEvent &event)
563 m_next_device = m_subset.begin();
564 StartNextSync();
567 void SyncStatusDlg::OnKillClose(wxCommandEvent &event)
569 if( m_jailexec.IsAppRunning() ) {
570 int choice;
571 if( m_killingjail ) {
572 choice = wxMessageBox(_T("Already killing sync. Kill again?"),
573 _T("Kill Sync"), wxYES_NO | wxICON_QUESTION);
575 else {
576 choice = wxMessageBox(_T("This will kill the syncing child process, and will likely require a configuration reset.\n\nKill sync process anyway?"),
577 _T("Kill Sync"), wxYES_NO | wxICON_QUESTION);
580 if( choice == wxYES ) {
581 KillSync();
583 // print a warning so the user know's what's going on
584 Print("Killing sync... this may take a little while...", *wxRED);
585 Print("Remember to re-plug your device.", *wxRED);
587 // let the terminate call clean up the buttons
588 return;
591 else {
592 EndModal(0);
596 void SyncStatusDlg::OnShowDetails(wxCommandEvent &event)
598 if( m_status_edit->IsShown() ) {
599 m_status_edit->Hide();
600 m_details_button->SetLabel(_T("Show Details"));
602 else {
603 m_status_edit->Show();
604 m_details_button->SetLabel(_T("Hide Details"));
606 m_topsizer->Fit(this);
608 // try to position the window in a readable spot
609 wxSize size = GetSize();
610 wxPoint pos = GetScreenPosition();
611 int screen_height = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y);
612 if( (pos.y + size.GetHeight()) > screen_height &&
613 size.GetHeight() < screen_height )
615 int wiggle_room = screen_height - size.GetHeight();
616 int y = wiggle_room / 2;
617 Move(pos.x, y);
621 wxConnectionBase* SyncStatusDlg::OnAcceptConnection(const wxString &topic)
623 wxConnectionBase *con = 0;
625 if( topic == TOPIC_STATUS && m_status_edit )
626 con = new StatusConnection(*this, *m_status_edit);
627 else if( topic == TOPIC_CONFLICT )
628 con = new ConflictConnection(*this);
630 if( con )
631 m_connections.push_back( dynamic_cast<OptOut::Element*> (con) );
633 return con;
636 void SyncStatusDlg::OnExecTerminated(wxProcessEvent &event)
638 ostringstream oss;
639 if( m_killingjail )
640 oss << "Sync terminated: ";
641 else
642 oss << "Sync finished: ";
643 oss << m_device_id;
644 Print(oss.str(), *wxBLACK);
645 ShortPrint(oss.str());
647 m_current_device = m_subset.end();
649 StartNextSync();
652 void SyncStatusDlg::OnTimer(wxTimerEvent &event)
654 Throb();