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("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: " << topic
.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 , TermCatcher(this, Dialog_SyncStatus_SyncTerminated
)
260 , m_next_device(m_subset
.begin())
262 , m_killingjail(false)
263 , m_timer(this, Dialog_SyncStatus_Timer
)
269 , m_syncagain_button(0)
270 , m_killclose_button(0)
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")),
305 // add a set of short status and progress throbber
306 wxSizer
*shorts
= new wxBoxSizer(wxHORIZONTAL
);
308 m_short_status
= new wxStaticText(this, wxID_ANY
, _T(""),
309 wxDefaultPosition
, wxSize(350, -1),
311 1, wxALIGN_CENTRE_VERTICAL
| wxALL
, 2);
313 m_throbber
= new wxGauge(this, wxID_ANY
, 0),
314 0, wxALL
| wxEXPAND
, 2);
315 ss
->Add( shorts
, 0, wxEXPAND
, 0);
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
);
333 m_details_button
= new wxButton(this,
334 Dialog_SyncStatus_ShowDetailsButton
,
338 button
->Add( -1, -1, 1 );
340 if( m_subset
.size() == 1 ) {
342 m_runapp_button
= new wxButton(this,
343 Dialog_SyncStatus_RunAppButton
,
348 m_syncagain_button
= new wxButton(this,
349 Dialog_SyncStatus_SyncAgainButton
,
353 m_killclose_button
= new wxButton(this,
354 Dialog_SyncStatus_KillCloseButton
,
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);
370 m_throbber
->SetRange(10);
371 m_throbber
->SetValue(0);
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);
384 m_throbber
->SetRange(10);
385 m_throbber
->SetValue(10);
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()
415 void SyncStatusDlg::StartTimer()
420 void SyncStatusDlg::StopTimer()
425 DeviceEntry
* SyncStatusDlg::GetCurrentDevice()
427 if( m_current_device
== m_subset
.end() )
429 return &(*(*m_current_device
));
432 void SyncStatusDlg::UpdateTitle()
434 if( m_next_device
== m_subset
.end() ) {
435 SetTitle(_T("Sync Progress Dialog"));
439 oss
<< "Syncing: " << (*m_next_device
)->GetPin().Str()
440 << " with " << (*m_next_device
)->GetAppNames();
441 wxString
label(oss
.str().c_str(), wxConvUTF8
);
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
466 m_current_device
= m_next_device
= m_subset
.end();
469 void SyncStatusDlg::StartNextSync()
471 m_killingjail
= false;
474 if( m_next_device
== m_subset
.end() ) {
475 Print("No more devices to sync.", *wxBLACK
);
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
);
493 m_current_device
= m_subset
.end();
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();
511 ConfigUI::ptr ui
= ConfigUI::CreateConfigUI((*i
)->GetAppName());
513 ui
->PreSyncAppInit();
517 // initialize sync jail process
518 if( m_jailexec
.IsAppRunning() ) {
519 string msg
= "ERROR: App running in StartNextSync()";
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
);
533 command
+= wxString(group_name
.c_str(), wxConvUTF8
);
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");
546 // sync is underway... advance to the next device
550 void SyncStatusDlg::OnSlowSync()
552 Print("Slow sync detected! Killing sync automatically.", *wxRED
);
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");
565 void SyncStatusDlg::OnRunApp(wxCommandEvent
&event
)
567 if( m_subset
.size() != 1 )
570 if( m_ui
.get() && m_ui
->IsAppRunning() ) {
571 wxMessageBox(_T("The application is already running."),
572 _T("Run Application"), wxOK
| wxICON_ERROR
);
576 OpenSync::Config::Group
*group
= m_subset
[0]->GetConfigGroup();
580 m_ui
= ConfigUI::CreateConfigUI(group
->GetAppNames());
587 void SyncStatusDlg::OnSyncAgain(wxCommandEvent
&event
)
589 m_next_device
= m_subset
.begin();
593 void SyncStatusDlg::OnKillClose(wxCommandEvent
&event
)
595 if( m_jailexec
.IsAppRunning() ) {
597 if( m_killingjail
) {
598 choice
= wxMessageBox(_T("Already killing sync. Kill again?"),
599 _T("Kill Sync"), wxYES_NO
| wxICON_QUESTION
);
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
) {
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
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"));
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;
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);
657 m_connections
.push_back( dynamic_cast<OptOut::Element
*> (con
) );
662 void SyncStatusDlg::OnExecTerminated(wxProcessEvent
&event
)
666 oss
<< "Sync terminated: ";
668 oss
<< "Sync finished: ";
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();
681 void SyncStatusDlg::OnTimer(wxTimerEvent
&event
)