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.
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"
27 #include "barrydesktop.h"
28 #include <wx/filename.h>
38 //////////////////////////////////////////////////////////////////////////////
39 // StatusConnection class
41 StatusConnection::StatusConnection(SyncStatusDlg
&dlg
,
48 bool StatusConnection::OnPoke(const wxString
&topic
,
54 if( topic
!= TOPIC_STATUS
)
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
) {
76 m_dlg
.Print(msg
, *wxGREEN
);
77 m_dlg
.ShortPrint(msg
);
80 else if( item
== STATUS_ITEM_MEMBER
) {
81 m_dlg
.Print(msg
, *wxCYAN
);
85 m_dlg
.Print(msg
, *wxBLACK
);
91 //////////////////////////////////////////////////////////////////////////////
92 // ConflictConnection class
94 ConflictConnection::ConflictConnection(SyncStatusDlg
&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
,
116 barryverbose("Conflict::OnPoke: " << string(topic
.utf8_str()) << ", "
117 << string(item
.utf8_str()));
119 if( topic
!= TOPIC_CONFLICT
)
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
128 wxString
msg(data
, size
);
129 istringstream
iss(string(msg
.utf8_str()));
131 if( item
== CONFLICT_ITEM_START
) {
132 // overwrite any existing sequence
134 iss
>> m_current_sequenceID
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;
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) ) {
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
);
179 wxChar
* ConflictConnection::OnRequest(const wxString
&topic
,
180 const wxString
&item
,
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
);
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());
202 m_asking_user
= true;
204 // ask the user what to do
205 if( !m_dlg
.GetCurrentDevice() ) {
206 barryverbose("Conflict: current device is null");
209 OpenSync::API
*engine
= m_dlg
.GetCurrentDevice()->GetEngine();
210 ConflictDlg
dlg(&m_dlg
, *engine
, m_supported_commands
,
211 m_changes
, m_always
);
217 m_asking_user
= false;
219 // did the user ask to kill the sync?
220 if( dlg
.IsKillSync() ) {
226 // prepare response for client
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
);
236 //////////////////////////////////////////////////////////////////////////////
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
)
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
)
262 , m_next_device(m_subset
.begin())
264 , m_killingjail(false)
265 , m_timer(this, Dialog_SyncStatus_Timer
)
271 , m_syncagain_button(0)
272 , m_killclose_button(0)
273 , m_details_button(0)
274 , m_repositioned(false)
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")),
309 // add a set of short status and progress throbber
310 wxSizer
*shorts
= new wxBoxSizer(wxHORIZONTAL
);
312 m_short_status
= new wxStaticText(this, wxID_ANY
, _T(""),
313 wxDefaultPosition
, wxSize(350, -1),
315 1, wxALIGN_CENTRE_VERTICAL
| wxALL
, 2);
317 m_throbber
= new wxGauge(this, wxID_ANY
, 0),
318 0, wxALL
| wxEXPAND
, 2);
319 ss
->Add( shorts
, 0, wxEXPAND
, 0);
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
);
337 m_details_button
= new wxButton(this,
338 Dialog_SyncStatus_ShowDetailsButton
,
342 button
->Add( -1, -1, 1 );
344 if( m_subset
.size() == 1 ) {
346 m_runapp_button
= new wxButton(this,
347 Dialog_SyncStatus_RunAppButton
,
352 m_syncagain_button
= new wxButton(this,
353 Dialog_SyncStatus_SyncAgainButton
,
357 m_killclose_button
= new wxButton(this,
358 Dialog_SyncStatus_KillCloseButton
,
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);
374 m_throbber
->SetRange(10);
375 m_throbber
->SetValue(0);
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);
388 m_throbber
->SetRange(10);
389 m_throbber
->SetValue(10);
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()
419 void SyncStatusDlg::StartTimer()
424 void SyncStatusDlg::StopTimer()
429 DeviceEntry
* SyncStatusDlg::GetCurrentDevice()
431 if( m_current_device
== m_subset
.end() )
433 return &(*(*m_current_device
));
436 void SyncStatusDlg::UpdateTitle()
438 if( m_next_device
== m_subset
.end() ) {
439 SetTitle(_T("Sync Progress Dialog"));
443 oss
<< "Syncing: " << (*m_next_device
)->GetPin().Str()
444 << " with " << (*m_next_device
)->GetAppNames();
445 wxString
label(oss
.str().c_str(), wxConvUTF8
);
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
470 m_current_device
= m_next_device
= m_subset
.end();
473 void SyncStatusDlg::StartNextSync()
475 m_killingjail
= false;
478 if( m_next_device
== m_subset
.end() ) {
479 Print(_T("No more devices to sync."), *wxBLACK
);
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
);
497 m_current_device
= m_subset
.end();
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();
515 ConfigUI::ptr ui
= ConfigUI::CreateConfigUI((*i
)->GetAppName());
517 ui
->PreSyncAppInit();
521 // initialize sync jail process
522 if( m_jailexec
.IsAppRunning() ) {
523 wxString
msg(_T("ERROR: App running in StartNextSync()"));
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
);
537 command
+= wxString(group_name
.c_str(), wxConvUTF8
);
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"));
550 // sync is underway... advance to the next device
554 void SyncStatusDlg::OnSlowSync()
556 Print(_T("Slow sync detected! Killing sync automatically."), *wxRED
);
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");
569 void SyncStatusDlg::OnRunApp(wxCommandEvent
&event
)
571 if( m_subset
.size() != 1 )
574 if( m_ui
.get() && m_ui
->IsAppRunning() ) {
575 wxMessageBox(_T("The application is already running."),
576 _T("Run Application"), wxOK
| wxICON_ERROR
);
580 OpenSync::Config::Group
*group
= m_subset
[0]->GetConfigGroup();
584 m_ui
= ConfigUI::CreateConfigUI(group
->GetAppNames());
591 void SyncStatusDlg::OnSyncAgain(wxCommandEvent
&event
)
593 m_next_device
= m_subset
.begin();
597 void SyncStatusDlg::OnKillClose(wxCommandEvent
&event
)
599 if( m_jailexec
.IsAppRunning() ) {
601 if( m_killingjail
) {
602 choice
= wxMessageBox(_T("Already killing sync. Kill again?"),
603 _T("Kill Sync"), wxYES_NO
| wxICON_QUESTION
);
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
) {
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
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"));
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;
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);
665 m_connections
.push_back( dynamic_cast<OptOut::Element
*> (con
) );
670 void SyncStatusDlg::OnExecTerminated(wxProcessEvent
&event
)
674 oss
<< "Sync terminated: ";
676 oss
<< "Sync finished: ";
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();
689 void SyncStatusDlg::OnTimer(wxTimerEvent
&event
)