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-2013, 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>
39 //////////////////////////////////////////////////////////////////////////////
40 // StatusConnection class
42 StatusConnection::StatusConnection(SyncStatusDlg
&dlg
,
49 bool StatusConnection::OnPoke(const wxString
&topic
,
55 if( topic
!= TOPIC_STATUS
)
58 wxString
msg(data
, size
);
60 if( item
== STATUS_ITEM_ERROR
) {
61 m_dlg
.Print(msg
, *wxRED
);
62 m_dlg
.ShortPrint(msg
);
64 else if( item
== STATUS_ITEM_ENTRY
) {
65 m_dlg
.Print(msg
, *wxBLUE
);
66 m_dlg
.ShortPrint(_W("Syncing entries..."));
68 else if( item
== STATUS_ITEM_MAPPING
) {
69 m_dlg
.Print(msg
, *wxBLUE
);
71 else if( item
== STATUS_ITEM_ENGINE
) {
72 wxString key
= ENGINE_STATUS_SLOW_SYNC
;
73 if( msg
.substr(0, key
.size()) == key
) {
77 m_dlg
.Print(msg
, *wxGREEN
);
78 m_dlg
.ShortPrint(msg
);
81 else if( item
== STATUS_ITEM_MEMBER
) {
82 m_dlg
.Print(msg
, *wxCYAN
);
86 m_dlg
.Print(msg
, *wxBLACK
);
92 //////////////////////////////////////////////////////////////////////////////
93 // ConflictConnection class
95 ConflictConnection::ConflictConnection(SyncStatusDlg
&dlg
)
97 , m_asking_user(false)
98 , m_current_sequenceID(-1)
99 , m_current_offset(-1)
100 , m_expected_total_changes(0)
102 // check if there's a favoured plugin name from the DeviceEntry config
103 if( m_dlg
.GetCurrentDevice() &&
104 m_dlg
.GetCurrentDevice()->GetExtras() )
106 m_always
.m_favour_plugin_name
= m_dlg
.GetCurrentDevice()->
107 GetExtras()->m_favour_plugin_name
;
111 bool ConflictConnection::OnPoke(const wxString
&topic
,
112 const wxString
&item
,
117 barryverbose("Conflict::OnPoke: " << string(topic
.utf8_str()) << ", "
118 << string(item
.utf8_str()));
120 if( topic
!= TOPIC_CONFLICT
)
123 // if currently handling a user request, don't change
124 // the state machine... the client shouldn't be poking
125 // if he just Request'd anyway
129 wxString
msg(data
, size
);
130 istringstream
iss(string(msg
.utf8_str()));
132 if( item
== CONFLICT_ITEM_START
) {
133 // overwrite any existing sequence
135 iss
>> m_current_sequenceID
137 >> m_expected_total_changes
138 >> m_supported_commands
;
139 if( !iss
|| m_current_offset
!= 0 || m_expected_total_changes
< 2) {
140 // invalid start command, throw it away
141 m_current_sequenceID
= -1;
142 m_current_offset
= -1;
143 m_expected_total_changes
= 0;
147 barryverbose("New conflict item: " << m_current_sequenceID
148 << ", " << m_current_offset
<< ", "
149 << "expected changes: " << m_expected_total_changes
150 << ", supported commands: " << m_supported_commands
);
152 else if( item
== CONFLICT_ITEM_CHANGE
) {
153 int sequenceID
= 0, offset
= 0;
154 OpenSync::SyncChange change
;
155 iss
>> sequenceID
>> offset
>> change
.id
>> ws
;
156 getline(iss
, change
.plugin_name
);
157 getline(iss
, change
.uid
);
158 change
.member_id
= 0;
160 if( !iss
|| sequenceID
!= m_current_sequenceID
|| offset
!= (m_current_offset
+ 1) ) {
164 m_current_offset
= offset
;
166 // grab remaining "printable data"
167 copy((istreambuf_iterator
<char>(iss
)),
168 (istreambuf_iterator
<char>()),
169 back_inserter(change
.printable_data
));
171 m_changes
.push_back(change
);
172 barryverbose("New conflict change: " << m_current_sequenceID
173 << ", " << m_current_offset
<< ", data: "
174 << change
.printable_data
);
180 wxChar
* ConflictConnection::OnRequest(const wxString
&topic
,
181 const wxString
&item
,
185 // make sure we are in a valid sequence
186 if( m_current_sequenceID
== -1 || m_current_offset
== -1 || m_expected_total_changes
< 2) {
187 barryverbose("Conflict: not in a valid sequence: "
188 << m_current_sequenceID
<< ", "
189 << m_current_offset
<< ", "
190 << m_expected_total_changes
);
194 // make sure we have a valid set of changes
195 if( m_current_offset
!= m_expected_total_changes
|| (size_t)m_expected_total_changes
!= m_changes
.size() ) {
196 barryverbose("Conflict: not a valid set of changes: "
197 << m_current_offset
<< ", "
198 << m_expected_total_changes
<< ", "
199 << m_changes
.size());
203 m_asking_user
= true;
205 // ask the user what to do
206 if( !m_dlg
.GetCurrentDevice() ) {
207 barryverbose("Conflict: current device is null");
210 OpenSync::API
*engine
= m_dlg
.GetCurrentDevice()->GetEngine();
211 ConflictDlg
dlg(&m_dlg
, *engine
, m_supported_commands
,
212 m_changes
, m_always
);
218 m_asking_user
= false;
220 // did the user ask to kill the sync?
221 if( dlg
.IsKillSync() ) {
227 // prepare response for client
229 oss
<< m_current_sequenceID
<< " " << dlg
.GetCommand();
230 m_buf
.buf(oss
.str());
231 // oddly, this is the size in bytes, not in wxChars
232 *size
= (m_buf
.size() + 1) * sizeof(wxChar
);
237 //////////////////////////////////////////////////////////////////////////////
240 BEGIN_EVENT_TABLE(SyncStatusDlg
, wxDialog
)
241 EVT_INIT_DIALOG (SyncStatusDlg::OnInitDialog
)
242 EVT_BUTTON (Dialog_SyncStatus_RunAppButton
,
243 SyncStatusDlg::OnRunApp
)
244 EVT_BUTTON (Dialog_SyncStatus_SyncAgainButton
,
245 SyncStatusDlg::OnSyncAgain
)
246 EVT_BUTTON (Dialog_SyncStatus_KillCloseButton
,
247 SyncStatusDlg::OnKillClose
)
248 EVT_BUTTON (Dialog_SyncStatus_ShowDetailsButton
,
249 SyncStatusDlg::OnShowDetails
)
250 EVT_END_PROCESS (Dialog_SyncStatus_SyncTerminated
,
251 SyncStatusDlg::OnExecTerminated
)
252 EVT_TIMER (Dialog_SyncStatus_Timer
,
253 SyncStatusDlg::OnTimer
)
256 SyncStatusDlg::SyncStatusDlg(wxWindow
*parent
,
257 const DeviceSet::subset_type
&subset
)
258 : wxDialog(parent
, Dialog_SyncStatus
, _W("Device Sync Progress"),
259 wxDefaultPosition
, wxDefaultSize
,
260 wxDEFAULT_DIALOG_STYLE
| wxRESIZE_BORDER
)
261 , TermCatcher(this, Dialog_SyncStatus_SyncTerminated
)
263 , m_next_device(m_subset
.begin())
265 , m_killingjail(false)
266 , m_timer(this, Dialog_SyncStatus_Timer
)
272 , m_syncagain_button(0)
273 , m_killclose_button(0)
274 , m_details_button(0)
275 , m_repositioned(false)
282 // create the IPC server
283 wxServer::Create(SERVER_SERVICE_NAME
);
286 SyncStatusDlg::~SyncStatusDlg()
288 // make sure bsyncjail dies if we do
289 m_jailexec
.KillApp();
292 void SyncStatusDlg::CreateLayout()
294 m_topsizer
= new wxBoxSizer(wxVERTICAL
);
295 AddStatusSizer(m_topsizer
);
296 AddButtonSizer(m_topsizer
);
298 SetSizer(m_topsizer
);
299 m_topsizer
->SetSizeHints(this);
300 m_topsizer
->Layout();
303 void SyncStatusDlg::AddStatusSizer(wxSizer
*sizer
)
305 wxSizer
*ss
= new wxStaticBoxSizer(
306 new wxStaticBox(this, wxID_ANY
, _W("Sync Progress")),
310 // add a set of short status and progress throbber
311 wxSizer
*shorts
= new wxBoxSizer(wxHORIZONTAL
);
313 m_short_status
= new wxStaticText(this, wxID_ANY
, _T(""),
314 wxDefaultPosition
, wxSize(350, -1),
316 1, wxALIGN_CENTRE_VERTICAL
| wxALL
, 2);
318 m_throbber
= new wxGauge(this, wxID_ANY
, 0),
319 0, wxALL
| wxEXPAND
, 2);
320 ss
->Add( shorts
, 0, wxEXPAND
, 0);
323 m_status_edit
= new wxTextCtrl(this, wxID_ANY
, _T(""),
324 wxDefaultPosition
, wxSize(475, 450),
325 wxTE_MULTILINE
| wxTE_READONLY
| wxTE_RICH
),
326 0, wxALL
| wxEXPAND
, 2);
327 // start with the details hidden
328 m_status_edit
->Hide();
330 sizer
->Add(ss
, 1, wxTOP
| wxLEFT
| wxRIGHT
| wxEXPAND
, 5);
333 void SyncStatusDlg::AddButtonSizer(wxSizer
*sizer
)
335 wxSizer
*button
= new wxBoxSizer(wxHORIZONTAL
);
338 m_details_button
= new wxButton(this,
339 Dialog_SyncStatus_ShowDetailsButton
,
343 button
->Add( -1, -1, 1 );
345 if( m_subset
.size() == 1 ) {
347 m_runapp_button
= new wxButton(this,
348 Dialog_SyncStatus_RunAppButton
,
353 m_syncagain_button
= new wxButton(this,
354 Dialog_SyncStatus_SyncAgainButton
,
358 m_killclose_button
= new wxButton(this,
359 Dialog_SyncStatus_KillCloseButton
,
363 sizer
->Add(button
, 0, wxALL
| wxEXPAND
, 5);
366 void SyncStatusDlg::SetRunning()
368 if( m_runapp_button
)
369 m_runapp_button
->Enable(false);
370 m_syncagain_button
->Enable(false);
371 m_killclose_button
->SetLabel(_W("Kill Sync"));
372 m_killclose_button
->Enable(true);
375 m_throbber
->SetRange(10);
376 m_throbber
->SetValue(0);
380 void SyncStatusDlg::SetClose()
382 if( m_runapp_button
)
383 m_runapp_button
->Enable(true);
384 m_syncagain_button
->Enable(true);
385 m_killclose_button
->SetLabel(_W("Close"));
386 m_killclose_button
->Enable(true);
389 m_throbber
->SetRange(10);
390 m_throbber
->SetValue(10);
394 void SyncStatusDlg::PrintStd(const std::string
&msg
, const wxColour
&colour
)
396 Print(wxString(msg
.c_str(), wxConvUTF8
), colour
);
399 void SyncStatusDlg::Print(const wxString
&msg
, const wxColour
&colour
)
401 m_status_edit
->SetDefaultStyle(wxTextAttr(colour
));
402 m_status_edit
->AppendText(_T("\n") + msg
);
405 void SyncStatusDlg::ShortPrintStd(const std::string
&msg
)
407 ShortPrint(wxString(msg
.c_str(), wxConvUTF8
));
410 void SyncStatusDlg::ShortPrint(const wxString
&msg
)
412 m_short_status
->SetLabel(msg
);
415 void SyncStatusDlg::Throb()
420 void SyncStatusDlg::StartTimer()
425 void SyncStatusDlg::StopTimer()
430 DeviceEntry
* SyncStatusDlg::GetCurrentDevice()
432 if( m_current_device
== m_subset
.end() )
434 return &(*(*m_current_device
));
437 void SyncStatusDlg::UpdateTitle()
439 if( m_next_device
== m_subset
.end() ) {
440 SetTitle(_W("Sync Progress Dialog"));
444 oss
<< _C("Syncing: ") << (*m_next_device
)->GetPin().Str()
445 << _C(" with ") << (*m_next_device
)->GetAppNames();
446 wxString
label(oss
.str().c_str(), wxConvUTF8
);
451 void SyncStatusDlg::UpdateLastSyncTime()
453 if( m_current_device
!= m_subset
.end() &&
454 (*m_current_device
)->GetConfigGroup() &&
455 (*m_current_device
)->GetExtras() )
457 (*m_current_device
)->GetExtras()->m_last_sync_time
= time(NULL
);
458 (*m_current_device
)->GetExtras()->Save(
459 wxGetApp().GetGlobalConfig(),
460 (*m_current_device
)->GetConfigGroup()->GetGroupName());
464 void SyncStatusDlg::KillSync()
466 m_jailexec
.KillApp(m_killingjail
);
467 m_killingjail
= true;
469 // jump to the end of the sync roster, so we don't start the
471 m_current_device
= m_next_device
= m_subset
.end();
474 void SyncStatusDlg::StartNextSync()
476 m_killingjail
= false;
479 if( m_next_device
== m_subset
.end() ) {
480 Print(_W("No more devices to sync."), *wxBLACK
);
488 // grab all required information we need to sync
489 m_current_device
= m_next_device
;
490 DeviceEntry
&device
= *(*m_next_device
);
491 const DeviceExtras
*extras
= device
.GetExtras();
492 m_device_id
= device
.GetPin().Str() + " (" + device
.GetDeviceName() + ")";
494 if( !device
.IsConfigured() ) {
495 PrintStd(m_device_id
+ _C(" is not configured, skipping."), *wxRED
);
496 ShortPrintStd(_C("Skipping unconfigured: ") + m_device_id
);
498 m_current_device
= m_subset
.end();
503 OpenSync::API
*engine
= device
.GetEngine();
504 OpenSync::Config::Group
*group
= device
.GetConfigGroup();
505 string group_name
= group
->GetGroupName();
507 string statusmsg
= _C("Starting sync for: ") + m_device_id
;
508 PrintStd(statusmsg
, *wxBLACK
);
509 ShortPrintStd(statusmsg
);
511 // for each plugin / app, perform the pre-sync steps
512 for( OpenSync::Config::Group::iterator i
= group
->begin();
516 ConfigUI::ptr ui
= ConfigUI::CreateConfigUI((*i
)->GetAppName());
518 ui
->PreSyncAppInit();
522 // initialize sync jail process
523 if( m_jailexec
.IsAppRunning() ) {
524 wxString
msg(_W("ERROR: App running in StartNextSync()"));
531 // bsyncjail is in pkglibexecdir
532 wxFileName
path(wxTheApp
->argv
[0]);
533 wxString
command(BARRYDESKTOP_PKGLIBEXECDIR
, wxConvUTF8
);
534 command
+= path
.GetPathSeparator();
535 command
+= _T("bsyncjail ");
536 command
+= wxString(engine
->GetVersion(), wxConvUTF8
);
538 command
+= wxString(group_name
.c_str(), wxConvUTF8
);
540 ostringstream sync_code
;
541 sync_code
<< dec
<< extras
->m_sync_types
;
542 command
+= wxString(sync_code
.str().c_str(), wxConvUTF8
);
544 if( !m_jailexec
.Run(NULL
, "bsyncjail", command
) ) {
545 Print(_W("ERROR: unable to start bsyncjail: ") + command
, *wxRED
);
546 ShortPrint(_W("ERROR: unable to start bsyncjail"));
551 // sync is underway... advance to the next device
555 void SyncStatusDlg::OnSlowSync()
557 Print(_W("Slow sync detected! Killing sync automatically."), *wxRED
);
560 Print(_W("Slow syncs are known to be unreliable."), *wxBLACK
);
561 Print(_W("Do a 1 Way Reset, and sync again."), *wxBLACK
);
564 void SyncStatusDlg::OnInitDialog(wxInitDialogEvent
&event
)
566 barryverbose("OnInitDialog");
570 void SyncStatusDlg::OnRunApp(wxCommandEvent
&event
)
572 if( m_subset
.size() != 1 )
575 if( m_ui
.get() && m_ui
->IsAppRunning() ) {
576 wxMessageBox(_W("The application is already running."),
577 _W("Run Application"), wxOK
| wxICON_ERROR
, this);
581 OpenSync::Config::Group
*group
= m_subset
[0]->GetConfigGroup();
585 m_ui
= ConfigUI::CreateConfigUI(group
->GetAppNames());
592 void SyncStatusDlg::OnSyncAgain(wxCommandEvent
&event
)
594 m_next_device
= m_subset
.begin();
598 void SyncStatusDlg::OnKillClose(wxCommandEvent
&event
)
600 if( m_jailexec
.IsAppRunning() ) {
602 if( m_killingjail
) {
603 choice
= wxMessageBox(_W("Already killing sync. Kill again?"),
604 _W("Kill Sync"), wxYES_NO
| wxICON_QUESTION
, this);
607 choice
= wxMessageBox(_W("This will kill the syncing child process, and will likely require a configuration reset.\n\nKill sync process anyway?"),
608 _W("Kill Sync"), wxYES_NO
| wxICON_QUESTION
, this);
611 if( choice
== wxYES
) {
614 Print(_W("Killing sync... this may take a little while..."), *wxRED
);
615 Print(_W("Remember to re-plug your device."), *wxRED
);
616 // let the terminate call clean up the buttons
625 void SyncStatusDlg::OnShowDetails(wxCommandEvent
&event
)
627 if( m_status_edit
->IsShown() ) {
628 m_status_edit
->Hide();
629 m_details_button
->SetLabel(_W("Show Details"));
632 if( !m_repositioned
) {
633 // try to position the window in a readable spot...
634 // do this first, so that the resize
635 wxSize size
= GetSize();
636 wxPoint pos
= GetScreenPosition();
637 int screen_height
= wxSystemSettings::GetMetric(wxSYS_SCREEN_Y
);
638 int new_height
= size
.GetHeight() + 470;
639 if( (pos
.y
+ new_height
) > screen_height
&&
640 new_height
< screen_height
)
642 int wiggle_room
= screen_height
- new_height
;
643 int y
= wiggle_room
/ 2;
647 m_repositioned
= true;
650 m_status_edit
->Show();
651 m_details_button
->SetLabel(_W("Hide Details"));
653 m_topsizer
->Fit(this);
656 wxConnectionBase
* SyncStatusDlg::OnAcceptConnection(const wxString
&topic
)
658 wxConnectionBase
*con
= 0;
660 if( topic
== TOPIC_STATUS
&& m_status_edit
)
661 con
= new StatusConnection(*this, *m_status_edit
);
662 else if( topic
== TOPIC_CONFLICT
)
663 con
= new ConflictConnection(*this);
666 m_connections
.push_back( dynamic_cast<OptOut::Element
*> (con
) );
671 void SyncStatusDlg::OnExecTerminated(wxProcessEvent
&event
)
675 oss
<< _C("Sync terminated: ");
677 oss
<< _C("Sync finished: ");
679 if( m_jailexec
.GetChildExitCode() )
680 oss
<< _C(" with error code ") << m_jailexec
.GetChildExitCode();
681 PrintStd(oss
.str(), *wxBLACK
);
682 ShortPrintStd(oss
.str());
683 UpdateLastSyncTime();
685 m_current_device
= m_subset
.end();
690 void SyncStatusDlg::OnTimer(wxTimerEvent
&event
)