desktop: fixed ExecHelper usage, to use child exit code
[barry/progweb.git] / desktop / src / SyncStatusDlg.cc
blob346b81cb89b6f688a5708d861fbeef36574b06e8
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-2012, 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>
34 using namespace std;
36 SillyBuffer sb;
38 //////////////////////////////////////////////////////////////////////////////
39 // StatusConnection class
41 StatusConnection::StatusConnection(SyncStatusDlg &dlg,
42 wxTextCtrl &window)
43 : m_dlg(dlg)
44 , m_status(window)
48 bool StatusConnection::OnPoke(const wxString &topic,
49 const wxString &item,
50 wxChar *data,
51 int size,
52 wxIPCFormat format)
54 if( topic != TOPIC_STATUS )
55 return false;
57 wxString msg(data, size);
59 if( item == STATUS_ITEM_ERROR ) {
60 m_dlg.Print(msg, *wxRED);
61 m_dlg.ShortPrint(msg);
63 else if( item == STATUS_ITEM_ENTRY ) {
64 m_dlg.Print(msg, *wxBLUE);
65 m_dlg.ShortPrint("Syncing entries...");
67 else if( item == STATUS_ITEM_MAPPING ) {
68 m_dlg.Print(msg, *wxBLUE);
70 else if( item == STATUS_ITEM_ENGINE ) {
71 wxString key = ENGINE_STATUS_SLOW_SYNC;
72 if( msg.substr(0, key.size()) == key ) {
73 m_dlg.OnSlowSync();
75 else {
76 m_dlg.Print(msg, *wxGREEN);
77 m_dlg.ShortPrint(msg);
80 else if( item == STATUS_ITEM_MEMBER ) {
81 m_dlg.Print(msg, *wxCYAN);
83 else {
84 // unknown item
85 m_dlg.Print(msg, *wxBLACK);
87 return true;
91 //////////////////////////////////////////////////////////////////////////////
92 // ConflictConnection class
94 ConflictConnection::ConflictConnection(SyncStatusDlg &dlg)
95 : m_dlg(dlg)
96 , m_asking_user(false)
97 , m_current_sequenceID(-1)
98 , m_current_offset(-1)
99 , m_expected_total_changes(0)
101 // check if there's a favoured plugin name from the DeviceEntry config
102 if( m_dlg.GetCurrentDevice() &&
103 m_dlg.GetCurrentDevice()->GetExtras() )
105 m_always.m_favour_plugin_name = m_dlg.GetCurrentDevice()->
106 GetExtras()->m_favour_plugin_name;
110 bool ConflictConnection::OnPoke(const wxString &topic,
111 const wxString &item,
112 wxChar *data,
113 int size,
114 wxIPCFormat format)
116 barryverbose("Conflict::OnPoke: " << topic.utf8_str() << ", "
117 << item.utf8_str());
119 if( topic != TOPIC_CONFLICT )
120 return false;
122 // if currently handling a user request, don't change
123 // the state machine... the client shouldn't be poking
124 // if he just Request'd anyway
125 if( m_asking_user )
126 return false;
128 wxString msg(data, size);
129 istringstream iss(string(msg.utf8_str()));
131 if( item == CONFLICT_ITEM_START ) {
132 // overwrite any existing sequence
133 m_changes.clear();
134 iss >> m_current_sequenceID
135 >> m_current_offset
136 >> m_expected_total_changes
137 >> m_supported_commands;
138 if( !iss || m_current_offset != 0 || m_expected_total_changes < 2) {
139 // invalid start command, throw it away
140 m_current_sequenceID = -1;
141 m_current_offset = -1;
142 m_expected_total_changes = 0;
143 return false;
146 barryverbose("New conflict item: " << m_current_sequenceID
147 << ", " << m_current_offset << ", "
148 << "expected changes: " << m_expected_total_changes
149 << ", supported commands: " << m_supported_commands);
151 else if( item == CONFLICT_ITEM_CHANGE ) {
152 int sequenceID = 0, offset = 0;
153 OpenSync::SyncChange change;
154 iss >> sequenceID >> offset >> change.id >> ws;
155 getline(iss, change.plugin_name);
156 getline(iss, change.uid);
157 change.member_id = 0;
159 if( !iss || sequenceID != m_current_sequenceID || offset != (m_current_offset + 1) ) {
160 return false;
163 m_current_offset = offset;
165 // grab remaining "printable data"
166 copy((istreambuf_iterator<char>(iss)),
167 (istreambuf_iterator<char>()),
168 back_inserter(change.printable_data));
170 m_changes.push_back(change);
171 barryverbose("New conflict change: " << m_current_sequenceID
172 << ", " << m_current_offset << ", data: "
173 << change.printable_data);
176 return true;
179 wxChar* ConflictConnection::OnRequest(const wxString &topic,
180 const wxString &item,
181 int *size,
182 wxIPCFormat format)
184 // make sure we are in a valid sequence
185 if( m_current_sequenceID == -1 || m_current_offset == -1 || m_expected_total_changes < 2) {
186 barryverbose("Conflict: not in a valid sequence: "
187 << m_current_sequenceID << ", "
188 << m_current_offset << ", "
189 << m_expected_total_changes);
190 return NULL;
193 // make sure we have a valid set of changes
194 if( m_current_offset != m_expected_total_changes || (size_t)m_expected_total_changes != m_changes.size() ) {
195 barryverbose("Conflict: not a valid set of changes: "
196 << m_current_offset << ", "
197 << m_expected_total_changes << ", "
198 << m_changes.size());
199 return NULL;
202 m_asking_user = true;
204 // ask the user what to do
205 if( !m_dlg.GetCurrentDevice() ) {
206 barryverbose("Conflict: current device is null");
207 return NULL;
209 OpenSync::API *engine = m_dlg.GetCurrentDevice()->GetEngine();
210 ConflictDlg dlg(&m_dlg, *engine, m_supported_commands,
211 m_changes, m_always);
212 m_dlg.StopTimer();
213 dlg.ShowModal();
214 m_dlg.StartTimer();
216 // done
217 m_asking_user = false;
219 // did the user ask to kill the sync?
220 if( dlg.IsKillSync() ) {
221 // die!
222 m_dlg.KillSync();
223 return NULL;
226 // prepare response for client
227 ostringstream oss;
228 oss << m_current_sequenceID << " " << dlg.GetCommand();
229 m_buf.buf(oss.str());
230 // oddly, this is the size in bytes, not in wxChars
231 *size = (m_buf.size() + 1) * sizeof(wxChar);
232 return m_buf.buf();
236 //////////////////////////////////////////////////////////////////////////////
237 // SyncStatus class
239 BEGIN_EVENT_TABLE(SyncStatusDlg, wxDialog)
240 EVT_INIT_DIALOG (SyncStatusDlg::OnInitDialog)
241 EVT_BUTTON (Dialog_SyncStatus_RunAppButton,
242 SyncStatusDlg::OnRunApp)
243 EVT_BUTTON (Dialog_SyncStatus_SyncAgainButton,
244 SyncStatusDlg::OnSyncAgain)
245 EVT_BUTTON (Dialog_SyncStatus_KillCloseButton,
246 SyncStatusDlg::OnKillClose)
247 EVT_BUTTON (Dialog_SyncStatus_ShowDetailsButton,
248 SyncStatusDlg::OnShowDetails)
249 EVT_END_PROCESS (Dialog_SyncStatus_SyncTerminated,
250 SyncStatusDlg::OnExecTerminated)
251 EVT_TIMER (Dialog_SyncStatus_Timer,
252 SyncStatusDlg::OnTimer)
253 END_EVENT_TABLE()
255 SyncStatusDlg::SyncStatusDlg(wxWindow *parent,
256 const DeviceSet::subset_type &subset)
257 : wxDialog(parent, Dialog_SyncStatus, _T("Device Sync Progress"))
258 , TermCatcher(this, Dialog_SyncStatus_SyncTerminated)
259 , m_subset(subset)
260 , m_next_device(m_subset.begin())
261 , m_jailexec(this)
262 , m_killingjail(false)
263 , m_timer(this, Dialog_SyncStatus_Timer)
264 , m_topsizer(0)
265 , m_short_status(0)
266 , m_throbber(0)
267 , m_status_edit(0)
268 , m_runapp_button(0)
269 , m_syncagain_button(0)
270 , m_killclose_button(0)
272 wxBusyCursor wait;
274 // setup the raw GUI
275 CreateLayout();
277 // create the IPC server
278 wxServer::Create(SERVER_SERVICE_NAME);
281 SyncStatusDlg::~SyncStatusDlg()
283 // make sure bsyncjail dies if we do
284 m_jailexec.KillApp();
287 void SyncStatusDlg::CreateLayout()
289 m_topsizer = new wxBoxSizer(wxVERTICAL);
290 AddStatusSizer(m_topsizer);
291 AddButtonSizer(m_topsizer);
293 SetSizer(m_topsizer);
294 m_topsizer->SetSizeHints(this);
295 m_topsizer->Layout();
298 void SyncStatusDlg::AddStatusSizer(wxSizer *sizer)
300 wxSizer *ss = new wxStaticBoxSizer(
301 new wxStaticBox(this, wxID_ANY, _T("Sync Progress")),
302 wxVERTICAL
305 // add a set of short status and progress throbber
306 wxSizer *shorts = new wxBoxSizer(wxHORIZONTAL);
307 shorts->Add(
308 m_short_status = new wxStaticText(this, wxID_ANY, _T(""),
309 wxDefaultPosition, wxSize(350, -1),
310 wxALIGN_LEFT),
311 1, wxALIGN_CENTRE_VERTICAL | wxALL, 2);
312 shorts->Add(
313 m_throbber = new wxGauge(this, wxID_ANY, 0),
314 0, wxALL | wxEXPAND, 2);
315 ss->Add( shorts, 0, wxEXPAND, 0);
317 ss->Add(
318 m_status_edit = new wxTextCtrl(this, wxID_ANY, _T(""),
319 wxDefaultPosition, wxSize(475, 450),
320 wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH),
321 0, wxALL | wxEXPAND, 2);
322 // start with the details hidden
323 m_status_edit->Hide();
325 sizer->Add(ss, 1, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, 5);
328 void SyncStatusDlg::AddButtonSizer(wxSizer *sizer)
330 wxSizer *button = new wxBoxSizer(wxHORIZONTAL);
332 button->Add(
333 m_details_button = new wxButton(this,
334 Dialog_SyncStatus_ShowDetailsButton,
335 _T("Show Details")),
336 0, wxALL, 3);
338 button->Add( -1, -1, 1 );
340 if( m_subset.size() == 1 ) {
341 button->Add(
342 m_runapp_button = new wxButton(this,
343 Dialog_SyncStatus_RunAppButton,
344 _T("Run App")),
345 0, wxALL, 3);
347 button->Add(
348 m_syncagain_button = new wxButton(this,
349 Dialog_SyncStatus_SyncAgainButton,
350 _T("Sync Again")),
351 0, wxALL, 3);
352 button->Add(
353 m_killclose_button = new wxButton(this,
354 Dialog_SyncStatus_KillCloseButton,
355 _T("Kill Sync")),
356 0, wxALL, 3);
358 sizer->Add(button, 0, wxALL | wxEXPAND, 5);
361 void SyncStatusDlg::SetRunning()
363 if( m_runapp_button )
364 m_runapp_button->Enable(false);
365 m_syncagain_button->Enable(false);
366 m_killclose_button->SetLabel(_T("Kill Sync"));
367 m_killclose_button->Enable(true);
368 UpdateTitle();
370 m_throbber->SetRange(10);
371 m_throbber->SetValue(0);
372 StartTimer();
375 void SyncStatusDlg::SetClose()
377 if( m_runapp_button )
378 m_runapp_button->Enable(true);
379 m_syncagain_button->Enable(true);
380 m_killclose_button->SetLabel(_T("Close"));
381 m_killclose_button->Enable(true);
382 UpdateTitle();
384 m_throbber->SetRange(10);
385 m_throbber->SetValue(10);
386 StopTimer();
389 void SyncStatusDlg::Print(const std::string &msg, const wxColour &colour)
391 Print(wxString(msg.c_str(), wxConvUTF8), colour);
394 void SyncStatusDlg::Print(const wxString &msg, const wxColour &colour)
396 m_status_edit->SetDefaultStyle(wxTextAttr(colour));
397 m_status_edit->AppendText(_T("\n") + msg);
400 void SyncStatusDlg::ShortPrint(const std::string &msg)
402 ShortPrint(wxString(msg.c_str(), wxConvUTF8));
405 void SyncStatusDlg::ShortPrint(const wxString &msg)
407 m_short_status->SetLabel(msg);
410 void SyncStatusDlg::Throb()
412 m_throbber->Pulse();
415 void SyncStatusDlg::StartTimer()
417 m_timer.Start(250);
420 void SyncStatusDlg::StopTimer()
422 m_timer.Stop();
425 DeviceEntry* SyncStatusDlg::GetCurrentDevice()
427 if( m_current_device == m_subset.end() )
428 return 0;
429 return &(*(*m_current_device));
432 void SyncStatusDlg::UpdateTitle()
434 if( m_next_device == m_subset.end() ) {
435 SetTitle(_T("Sync Progress Dialog"));
437 else {
438 ostringstream oss;
439 oss << "Syncing: " << (*m_next_device)->GetPin().Str()
440 << " with " << (*m_next_device)->GetAppNames();
441 wxString label(oss.str().c_str(), wxConvUTF8);
442 SetTitle(label);
446 void SyncStatusDlg::UpdateLastSyncTime()
448 if( m_current_device != m_subset.end() &&
449 (*m_current_device)->GetConfigGroup() &&
450 (*m_current_device)->GetExtras() )
452 (*m_current_device)->GetExtras()->m_last_sync_time = time(NULL);
453 (*m_current_device)->GetExtras()->Save(
454 wxGetApp().GetGlobalConfig(),
455 (*m_current_device)->GetConfigGroup()->GetGroupName());
459 void SyncStatusDlg::KillSync()
461 m_jailexec.KillApp(m_killingjail);
462 m_killingjail = true;
464 // jump to the end of the sync roster, so we don't start the
465 // next device
466 m_current_device = m_next_device = m_subset.end();
469 void SyncStatusDlg::StartNextSync()
471 m_killingjail = false;
473 // anything to do?
474 if( m_next_device == m_subset.end() ) {
475 Print("No more devices to sync.", *wxBLACK);
476 SetClose();
477 return;
479 else {
480 SetRunning();
483 // grab all required information we need to sync
484 m_current_device = m_next_device;
485 DeviceEntry &device = *(*m_next_device);
486 const DeviceExtras *extras = device.GetExtras();
487 m_device_id = device.GetPin().Str() + " (" + device.GetDeviceName() + ")";
489 if( !device.IsConfigured() ) {
490 Print(m_device_id + " is not configured, skipping.", *wxRED);
491 ShortPrint("Skipping unconfigured: " + m_device_id);
492 ++m_next_device;
493 m_current_device = m_subset.end();
494 StartNextSync();
495 return;
498 OpenSync::API *engine = device.GetEngine();
499 OpenSync::Config::Group *group = device.GetConfigGroup();
500 string group_name = group->GetGroupName();
502 string statusmsg = "Starting sync for: " + m_device_id;
503 Print(statusmsg, *wxBLACK);
504 ShortPrint(statusmsg);
506 // for each plugin / app, perform the pre-sync steps
507 for( OpenSync::Config::Group::iterator i = group->begin();
508 i != group->end();
509 ++i )
511 ConfigUI::ptr ui = ConfigUI::CreateConfigUI((*i)->GetAppName());
512 if( ui.get() ) {
513 ui->PreSyncAppInit();
517 // initialize sync jail process
518 if( m_jailexec.IsAppRunning() ) {
519 string msg = "ERROR: App running in StartNextSync()";
520 Print(msg, *wxRED);
521 ShortPrint(msg);
522 SetClose();
523 return;
526 // bsyncjail is in pkglibexecdir
527 wxFileName path(wxTheApp->argv[0]);
528 wxString command(BARRYDESKTOP_PKGLIBEXECDIR, wxConvUTF8);
529 command += path.GetPathSeparator();
530 command += _T("bsyncjail ");
531 command += wxString(engine->GetVersion(), wxConvUTF8);
532 command += _T(" ");
533 command += wxString(group_name.c_str(), wxConvUTF8);
534 command += _T(" ");
535 ostringstream sync_code;
536 sync_code << dec << extras->m_sync_types;
537 command += wxString(sync_code.str().c_str(), wxConvUTF8);
539 if( !m_jailexec.Run(NULL, "bsyncjail", command) ) {
540 Print("ERROR: unable to start bsyncjail: " + string(command.utf8_str()), *wxRED);
541 ShortPrint("ERROR: unable to start bsyncjail");
542 SetClose();
543 return;
546 // sync is underway... advance to the next device
547 ++m_next_device;
550 void SyncStatusDlg::OnSlowSync()
552 Print("Slow sync detected! Killing sync automatically.", *wxRED);
553 KillSync();
555 Print("Slow syncs are known to be unreliable.", *wxBLACK);
556 Print("Do a 1 Way Reset, and sync again.", *wxBLACK);
559 void SyncStatusDlg::OnInitDialog(wxInitDialogEvent &event)
561 barryverbose("OnInitDialog");
562 StartNextSync();
565 void SyncStatusDlg::OnRunApp(wxCommandEvent &event)
567 if( m_subset.size() != 1 )
568 return;
570 if( m_ui.get() && m_ui->IsAppRunning() ) {
571 wxMessageBox(_T("The application is already running."),
572 _T("Run Application"), wxOK | wxICON_ERROR);
573 return;
576 OpenSync::Config::Group *group = m_subset[0]->GetConfigGroup();
577 if( !group )
578 return;
580 m_ui = ConfigUI::CreateConfigUI(group->GetAppNames());
581 if( !m_ui.get() )
582 return;
584 m_ui->RunApp(this);
587 void SyncStatusDlg::OnSyncAgain(wxCommandEvent &event)
589 m_next_device = m_subset.begin();
590 StartNextSync();
593 void SyncStatusDlg::OnKillClose(wxCommandEvent &event)
595 if( m_jailexec.IsAppRunning() ) {
596 int choice;
597 if( m_killingjail ) {
598 choice = wxMessageBox(_T("Already killing sync. Kill again?"),
599 _T("Kill Sync"), wxYES_NO | wxICON_QUESTION);
601 else {
602 choice = wxMessageBox(_T("This will kill the syncing child process, and will likely require a configuration reset.\n\nKill sync process anyway?"),
603 _T("Kill Sync"), wxYES_NO | wxICON_QUESTION);
606 if( choice == wxYES ) {
607 KillSync();
609 // print a warning so the user know's what's going on
610 Print("Killing sync... this may take a little while...", *wxRED);
611 Print("Remember to re-plug your device.", *wxRED);
613 // let the terminate call clean up the buttons
614 return;
617 else {
618 EndModal(0);
622 void SyncStatusDlg::OnShowDetails(wxCommandEvent &event)
624 if( m_status_edit->IsShown() ) {
625 m_status_edit->Hide();
626 m_details_button->SetLabel(_T("Show Details"));
628 else {
629 m_status_edit->Show();
630 m_details_button->SetLabel(_T("Hide Details"));
632 m_topsizer->Fit(this);
634 // try to position the window in a readable spot
635 wxSize size = GetSize();
636 wxPoint pos = GetScreenPosition();
637 int screen_height = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y);
638 if( (pos.y + size.GetHeight()) > screen_height &&
639 size.GetHeight() < screen_height )
641 int wiggle_room = screen_height - size.GetHeight();
642 int y = wiggle_room / 2;
643 Move(pos.x, y);
647 wxConnectionBase* SyncStatusDlg::OnAcceptConnection(const wxString &topic)
649 wxConnectionBase *con = 0;
651 if( topic == TOPIC_STATUS && m_status_edit )
652 con = new StatusConnection(*this, *m_status_edit);
653 else if( topic == TOPIC_CONFLICT )
654 con = new ConflictConnection(*this);
656 if( con )
657 m_connections.push_back( dynamic_cast<OptOut::Element*> (con) );
659 return con;
662 void SyncStatusDlg::OnExecTerminated(wxProcessEvent &event)
664 ostringstream oss;
665 if( m_killingjail )
666 oss << "Sync terminated: ";
667 else
668 oss << "Sync finished: ";
669 oss << m_device_id;
670 if( m_jailexec.GetChildExitCode() )
671 oss << " with error " << m_jailexec.GetChildExitCode();
672 Print(oss.str(), *wxBLACK);
673 ShortPrint(oss.str());
674 UpdateLastSyncTime();
676 m_current_device = m_subset.end();
678 StartNextSync();
681 void SyncStatusDlg::OnTimer(wxTimerEvent &event)
683 Throb();