desktop: reduce some string copying in favour of _T() strings
[barry.git] / desktop / src / SyncStatusDlg.cc
blob90f0c261c0d05fb81258abcfbfb55633ac9ecf9c
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(_T("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: " << string(topic.utf8_str()) << ", "
117 << string(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 wxDefaultPosition, wxDefaultSize,
259 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
260 , TermCatcher(this, Dialog_SyncStatus_SyncTerminated)
261 , m_subset(subset)
262 , m_next_device(m_subset.begin())
263 , m_jailexec(this)
264 , m_killingjail(false)
265 , m_timer(this, Dialog_SyncStatus_Timer)
266 , m_topsizer(0)
267 , m_short_status(0)
268 , m_throbber(0)
269 , m_status_edit(0)
270 , m_runapp_button(0)
271 , m_syncagain_button(0)
272 , m_killclose_button(0)
273 , m_details_button(0)
274 , m_repositioned(false)
276 wxBusyCursor wait;
278 // setup the raw GUI
279 CreateLayout();
281 // create the IPC server
282 wxServer::Create(SERVER_SERVICE_NAME);
285 SyncStatusDlg::~SyncStatusDlg()
287 // make sure bsyncjail dies if we do
288 m_jailexec.KillApp();
291 void SyncStatusDlg::CreateLayout()
293 m_topsizer = new wxBoxSizer(wxVERTICAL);
294 AddStatusSizer(m_topsizer);
295 AddButtonSizer(m_topsizer);
297 SetSizer(m_topsizer);
298 m_topsizer->SetSizeHints(this);
299 m_topsizer->Layout();
302 void SyncStatusDlg::AddStatusSizer(wxSizer *sizer)
304 wxSizer *ss = new wxStaticBoxSizer(
305 new wxStaticBox(this, wxID_ANY, _T("Sync Progress")),
306 wxVERTICAL
309 // add a set of short status and progress throbber
310 wxSizer *shorts = new wxBoxSizer(wxHORIZONTAL);
311 shorts->Add(
312 m_short_status = new wxStaticText(this, wxID_ANY, _T(""),
313 wxDefaultPosition, wxSize(350, -1),
314 wxALIGN_LEFT),
315 1, wxALIGN_CENTRE_VERTICAL | wxALL, 2);
316 shorts->Add(
317 m_throbber = new wxGauge(this, wxID_ANY, 0),
318 0, wxALL | wxEXPAND, 2);
319 ss->Add( shorts, 0, wxEXPAND, 0);
321 ss->Add(
322 m_status_edit = new wxTextCtrl(this, wxID_ANY, _T(""),
323 wxDefaultPosition, wxSize(475, 450),
324 wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH),
325 0, wxALL | wxEXPAND, 2);
326 // start with the details hidden
327 m_status_edit->Hide();
329 sizer->Add(ss, 1, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, 5);
332 void SyncStatusDlg::AddButtonSizer(wxSizer *sizer)
334 wxSizer *button = new wxBoxSizer(wxHORIZONTAL);
336 button->Add(
337 m_details_button = new wxButton(this,
338 Dialog_SyncStatus_ShowDetailsButton,
339 _T("Show Details")),
340 0, wxALL, 3);
342 button->Add( -1, -1, 1 );
344 if( m_subset.size() == 1 ) {
345 button->Add(
346 m_runapp_button = new wxButton(this,
347 Dialog_SyncStatus_RunAppButton,
348 _T("Run App")),
349 0, wxALL, 3);
351 button->Add(
352 m_syncagain_button = new wxButton(this,
353 Dialog_SyncStatus_SyncAgainButton,
354 _T("Sync Again")),
355 0, wxALL, 3);
356 button->Add(
357 m_killclose_button = new wxButton(this,
358 Dialog_SyncStatus_KillCloseButton,
359 _T("Kill Sync")),
360 0, wxALL, 3);
362 sizer->Add(button, 0, wxALL | wxEXPAND, 5);
365 void SyncStatusDlg::SetRunning()
367 if( m_runapp_button )
368 m_runapp_button->Enable(false);
369 m_syncagain_button->Enable(false);
370 m_killclose_button->SetLabel(_T("Kill Sync"));
371 m_killclose_button->Enable(true);
372 UpdateTitle();
374 m_throbber->SetRange(10);
375 m_throbber->SetValue(0);
376 StartTimer();
379 void SyncStatusDlg::SetClose()
381 if( m_runapp_button )
382 m_runapp_button->Enable(true);
383 m_syncagain_button->Enable(true);
384 m_killclose_button->SetLabel(_T("Close"));
385 m_killclose_button->Enable(true);
386 UpdateTitle();
388 m_throbber->SetRange(10);
389 m_throbber->SetValue(10);
390 StopTimer();
393 void SyncStatusDlg::PrintStd(const std::string &msg, const wxColour &colour)
395 Print(wxString(msg.c_str(), wxConvUTF8), colour);
398 void SyncStatusDlg::Print(const wxString &msg, const wxColour &colour)
400 m_status_edit->SetDefaultStyle(wxTextAttr(colour));
401 m_status_edit->AppendText(_T("\n") + msg);
404 void SyncStatusDlg::ShortPrintStd(const std::string &msg)
406 ShortPrint(wxString(msg.c_str(), wxConvUTF8));
409 void SyncStatusDlg::ShortPrint(const wxString &msg)
411 m_short_status->SetLabel(msg);
414 void SyncStatusDlg::Throb()
416 m_throbber->Pulse();
419 void SyncStatusDlg::StartTimer()
421 m_timer.Start(250);
424 void SyncStatusDlg::StopTimer()
426 m_timer.Stop();
429 DeviceEntry* SyncStatusDlg::GetCurrentDevice()
431 if( m_current_device == m_subset.end() )
432 return 0;
433 return &(*(*m_current_device));
436 void SyncStatusDlg::UpdateTitle()
438 if( m_next_device == m_subset.end() ) {
439 SetTitle(_T("Sync Progress Dialog"));
441 else {
442 ostringstream oss;
443 oss << "Syncing: " << (*m_next_device)->GetPin().Str()
444 << " with " << (*m_next_device)->GetAppNames();
445 wxString label(oss.str().c_str(), wxConvUTF8);
446 SetTitle(label);
450 void SyncStatusDlg::UpdateLastSyncTime()
452 if( m_current_device != m_subset.end() &&
453 (*m_current_device)->GetConfigGroup() &&
454 (*m_current_device)->GetExtras() )
456 (*m_current_device)->GetExtras()->m_last_sync_time = time(NULL);
457 (*m_current_device)->GetExtras()->Save(
458 wxGetApp().GetGlobalConfig(),
459 (*m_current_device)->GetConfigGroup()->GetGroupName());
463 void SyncStatusDlg::KillSync()
465 m_jailexec.KillApp(m_killingjail);
466 m_killingjail = true;
468 // jump to the end of the sync roster, so we don't start the
469 // next device
470 m_current_device = m_next_device = m_subset.end();
473 void SyncStatusDlg::StartNextSync()
475 m_killingjail = false;
477 // anything to do?
478 if( m_next_device == m_subset.end() ) {
479 Print(_T("No more devices to sync."), *wxBLACK);
480 SetClose();
481 return;
483 else {
484 SetRunning();
487 // grab all required information we need to sync
488 m_current_device = m_next_device;
489 DeviceEntry &device = *(*m_next_device);
490 const DeviceExtras *extras = device.GetExtras();
491 m_device_id = device.GetPin().Str() + " (" + device.GetDeviceName() + ")";
493 if( !device.IsConfigured() ) {
494 PrintStd(m_device_id + " is not configured, skipping.", *wxRED);
495 ShortPrintStd("Skipping unconfigured: " + m_device_id);
496 ++m_next_device;
497 m_current_device = m_subset.end();
498 StartNextSync();
499 return;
502 OpenSync::API *engine = device.GetEngine();
503 OpenSync::Config::Group *group = device.GetConfigGroup();
504 string group_name = group->GetGroupName();
506 string statusmsg = "Starting sync for: " + m_device_id;
507 PrintStd(statusmsg, *wxBLACK);
508 ShortPrintStd(statusmsg);
510 // for each plugin / app, perform the pre-sync steps
511 for( OpenSync::Config::Group::iterator i = group->begin();
512 i != group->end();
513 ++i )
515 ConfigUI::ptr ui = ConfigUI::CreateConfigUI((*i)->GetAppName());
516 if( ui.get() ) {
517 ui->PreSyncAppInit();
521 // initialize sync jail process
522 if( m_jailexec.IsAppRunning() ) {
523 wxString msg(_T("ERROR: App running in StartNextSync()"));
524 Print(msg, *wxRED);
525 ShortPrint(msg);
526 SetClose();
527 return;
530 // bsyncjail is in pkglibexecdir
531 wxFileName path(wxTheApp->argv[0]);
532 wxString command(BARRYDESKTOP_PKGLIBEXECDIR, wxConvUTF8);
533 command += path.GetPathSeparator();
534 command += _T("bsyncjail ");
535 command += wxString(engine->GetVersion(), wxConvUTF8);
536 command += _T(" ");
537 command += wxString(group_name.c_str(), wxConvUTF8);
538 command += _T(" ");
539 ostringstream sync_code;
540 sync_code << dec << extras->m_sync_types;
541 command += wxString(sync_code.str().c_str(), wxConvUTF8);
543 if( !m_jailexec.Run(NULL, "bsyncjail", command) ) {
544 Print(_T("ERROR: unable to start bsyncjail: ") + command, *wxRED);
545 ShortPrint(_T("ERROR: unable to start bsyncjail"));
546 SetClose();
547 return;
550 // sync is underway... advance to the next device
551 ++m_next_device;
554 void SyncStatusDlg::OnSlowSync()
556 Print(_T("Slow sync detected! Killing sync automatically."), *wxRED);
557 KillSync();
559 Print(_T("Slow syncs are known to be unreliable."), *wxBLACK);
560 Print(_T("Do a 1 Way Reset, and sync again."), *wxBLACK);
563 void SyncStatusDlg::OnInitDialog(wxInitDialogEvent &event)
565 barryverbose("OnInitDialog");
566 StartNextSync();
569 void SyncStatusDlg::OnRunApp(wxCommandEvent &event)
571 if( m_subset.size() != 1 )
572 return;
574 if( m_ui.get() && m_ui->IsAppRunning() ) {
575 wxMessageBox(_T("The application is already running."),
576 _T("Run Application"), wxOK | wxICON_ERROR);
577 return;
580 OpenSync::Config::Group *group = m_subset[0]->GetConfigGroup();
581 if( !group )
582 return;
584 m_ui = ConfigUI::CreateConfigUI(group->GetAppNames());
585 if( !m_ui.get() )
586 return;
588 m_ui->RunApp(this);
591 void SyncStatusDlg::OnSyncAgain(wxCommandEvent &event)
593 m_next_device = m_subset.begin();
594 StartNextSync();
597 void SyncStatusDlg::OnKillClose(wxCommandEvent &event)
599 if( m_jailexec.IsAppRunning() ) {
600 int choice;
601 if( m_killingjail ) {
602 choice = wxMessageBox(_T("Already killing sync. Kill again?"),
603 _T("Kill Sync"), wxYES_NO | wxICON_QUESTION);
605 else {
606 choice = wxMessageBox(_T("This will kill the syncing child process, and will likely require a configuration reset.\n\nKill sync process anyway?"),
607 _T("Kill Sync"), wxYES_NO | wxICON_QUESTION);
610 if( choice == wxYES ) {
611 KillSync();
613 Print(_T("Killing sync... this may take a little while..."), *wxRED);
614 Print(_T("Remember to re-plug your device."), *wxRED);
615 // let the terminate call clean up the buttons
616 return;
619 else {
620 EndModal(0);
624 void SyncStatusDlg::OnShowDetails(wxCommandEvent &event)
626 if( m_status_edit->IsShown() ) {
627 m_status_edit->Hide();
628 m_details_button->SetLabel(_T("Show Details"));
630 else {
631 if( !m_repositioned ) {
632 // try to position the window in a readable spot...
633 // do this first, so that the resize
634 wxSize size = GetSize();
635 wxPoint pos = GetScreenPosition();
636 int screen_height = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y);
637 int new_height = size.GetHeight() + 470;
638 if( (pos.y + new_height) > screen_height &&
639 new_height < screen_height )
641 int wiggle_room = screen_height - new_height;
642 int y = wiggle_room / 2;
643 Move(pos.x, y);
646 m_repositioned = true;
649 m_status_edit->Show();
650 m_details_button->SetLabel(_T("Hide Details"));
652 m_topsizer->Fit(this);
655 wxConnectionBase* SyncStatusDlg::OnAcceptConnection(const wxString &topic)
657 wxConnectionBase *con = 0;
659 if( topic == TOPIC_STATUS && m_status_edit )
660 con = new StatusConnection(*this, *m_status_edit);
661 else if( topic == TOPIC_CONFLICT )
662 con = new ConflictConnection(*this);
664 if( con )
665 m_connections.push_back( dynamic_cast<OptOut::Element*> (con) );
667 return con;
670 void SyncStatusDlg::OnExecTerminated(wxProcessEvent &event)
672 ostringstream oss;
673 if( m_killingjail )
674 oss << "Sync terminated: ";
675 else
676 oss << "Sync finished: ";
677 oss << m_device_id;
678 if( m_jailexec.GetChildExitCode() )
679 oss << " with error " << m_jailexec.GetChildExitCode();
680 PrintStd(oss.str(), *wxBLACK);
681 ShortPrintStd(oss.str());
682 UpdateLastSyncTime();
684 m_current_device = m_subset.end();
686 StartNextSync();
689 void SyncStatusDlg::OnTimer(wxTimerEvent &event)
691 Throb();