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, 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 <wx/filename.h>
37 //////////////////////////////////////////////////////////////////////////////
38 // StatusConnection class
40 StatusConnection::StatusConnection(SyncStatusDlg
&dlg
,
47 bool StatusConnection::OnPoke(const wxString
&topic
,
53 if( topic
!= TOPIC_STATUS
)
56 wxString
msg(data
, size
);
58 if( item
== STATUS_ITEM_ERROR
) {
59 m_dlg
.Print(msg
, *wxRED
);
60 m_dlg
.ShortPrint(msg
);
62 else if( item
== STATUS_ITEM_ENTRY
) {
63 m_dlg
.Print(msg
, *wxBLUE
);
64 m_dlg
.ShortPrint("Syncing entries...");
66 else if( item
== STATUS_ITEM_MAPPING
) {
67 m_dlg
.Print(msg
, *wxBLUE
);
69 else if( item
== STATUS_ITEM_ENGINE
) {
70 wxString key
= ENGINE_STATUS_SLOW_SYNC
;
71 if( msg
.substr(0, key
.size()) == key
) {
75 m_dlg
.Print(msg
, *wxGREEN
);
76 m_dlg
.ShortPrint(msg
);
79 else if( item
== STATUS_ITEM_MEMBER
) {
80 m_dlg
.Print(msg
, *wxCYAN
);
84 m_dlg
.Print(msg
, *wxBLACK
);
90 //////////////////////////////////////////////////////////////////////////////
91 // ConflictConnection class
93 ConflictConnection::ConflictConnection(SyncStatusDlg
&dlg
)
95 , m_asking_user(false)
96 , m_current_sequenceID(-1)
97 , m_current_offset(-1)
98 , m_expected_total_changes(0)
100 // check if there's a favoured plugin name from the DeviceEntry config
101 if( m_dlg
.GetCurrentDevice() &&
102 m_dlg
.GetCurrentDevice()->GetExtras() )
104 m_always
.m_favour_plugin_name
= m_dlg
.GetCurrentDevice()->
105 GetExtras()->m_favour_plugin_name
;
109 bool ConflictConnection::OnPoke(const wxString
&topic
,
110 const wxString
&item
,
115 barryverbose("Conflict::OnPoke: " << topic
.utf8_str() << ", "
118 if( topic
!= TOPIC_CONFLICT
)
121 // if currently handling a user request, don't change
122 // the state machine... the client shouldn't be poking
123 // if he just Request'd anyway
127 wxString
msg(data
, size
);
128 istringstream
iss(string(msg
.utf8_str()));
130 if( item
== CONFLICT_ITEM_START
) {
131 // overwrite any existing sequence
133 iss
>> m_current_sequenceID
135 >> m_expected_total_changes
136 >> m_supported_commands
;
137 if( !iss
|| m_current_offset
!= 0 || m_expected_total_changes
< 2) {
138 // invalid start command, throw it away
139 m_current_sequenceID
= -1;
140 m_current_offset
= -1;
141 m_expected_total_changes
= 0;
145 barryverbose("New conflict item: " << m_current_sequenceID
146 << ", " << m_current_offset
<< ", "
147 << "expected changes: " << m_expected_total_changes
148 << ", supported commands: " << m_supported_commands
);
150 else if( item
== CONFLICT_ITEM_CHANGE
) {
151 int sequenceID
= 0, offset
= 0;
152 OpenSync::SyncChange change
;
153 iss
>> sequenceID
>> offset
>> change
.id
>> ws
;
154 getline(iss
, change
.plugin_name
);
155 getline(iss
, change
.uid
);
156 change
.member_id
= 0;
158 if( !iss
|| sequenceID
!= m_current_sequenceID
|| offset
!= (m_current_offset
+ 1) ) {
162 m_current_offset
= offset
;
164 // grab remaining "printable data"
165 copy((istreambuf_iterator
<char>(iss
)),
166 (istreambuf_iterator
<char>()),
167 back_inserter(change
.printable_data
));
169 m_changes
.push_back(change
);
170 barryverbose("New conflict change: " << m_current_sequenceID
171 << ", " << m_current_offset
<< ", data: "
172 << change
.printable_data
);
178 wxChar
* ConflictConnection::OnRequest(const wxString
&topic
,
179 const wxString
&item
,
183 // make sure we are in a valid sequence
184 if( m_current_sequenceID
== -1 || m_current_offset
== -1 || m_expected_total_changes
< 2) {
185 barryverbose("Conflict: not in a valid sequence: "
186 << m_current_sequenceID
<< ", "
187 << m_current_offset
<< ", "
188 << m_expected_total_changes
);
192 // make sure we have a valid set of changes
193 if( m_current_offset
!= m_expected_total_changes
|| (size_t)m_expected_total_changes
!= m_changes
.size() ) {
194 barryverbose("Conflict: not a valid set of changes: "
195 << m_current_offset
<< ", "
196 << m_expected_total_changes
<< ", "
197 << m_changes
.size());
201 m_asking_user
= true;
203 // ask the user what to do
204 ConflictDlg
dlg(&m_dlg
, m_supported_commands
, m_changes
, m_always
);
210 m_asking_user
= false;
212 // did the user ask to kill the sync?
213 if( dlg
.IsKillSync() ) {
219 // prepare response for client
221 oss
<< m_current_sequenceID
<< " " << dlg
.GetCommand();
222 m_buf
.buf(oss
.str());
223 // oddly, this is the size in bytes, not in wxChars
224 *size
= (m_buf
.size() + 1) * sizeof(wxChar
);
229 //////////////////////////////////////////////////////////////////////////////
232 BEGIN_EVENT_TABLE(SyncStatusDlg
, wxDialog
)
233 EVT_INIT_DIALOG (SyncStatusDlg::OnInitDialog
)
234 EVT_BUTTON (Dialog_SyncStatus_RunAppButton
,
235 SyncStatusDlg::OnRunApp
)
236 EVT_BUTTON (Dialog_SyncStatus_SyncAgainButton
,
237 SyncStatusDlg::OnSyncAgain
)
238 EVT_BUTTON (Dialog_SyncStatus_KillCloseButton
,
239 SyncStatusDlg::OnKillClose
)
240 EVT_BUTTON (Dialog_SyncStatus_ShowDetailsButton
,
241 SyncStatusDlg::OnShowDetails
)
242 EVT_END_PROCESS (Dialog_SyncStatus_SyncTerminated
,
243 SyncStatusDlg::OnExecTerminated
)
244 EVT_TIMER (Dialog_SyncStatus_Timer
,
245 SyncStatusDlg::OnTimer
)
248 SyncStatusDlg::SyncStatusDlg(wxWindow
*parent
,
249 const DeviceSet::subset_type
&subset
)
250 : wxDialog(parent
, Dialog_SyncStatus
, _T("Device Sync Progress"))
251 , TermCatcher(this, Dialog_SyncStatus_SyncTerminated
)
253 , m_next_device(m_subset
.begin())
255 , m_killingjail(false)
256 , m_timer(this, Dialog_SyncStatus_Timer
)
262 , m_syncagain_button(0)
263 , m_killclose_button(0)
270 // create the IPC server
271 wxServer::Create(SERVER_SERVICE_NAME
);
274 SyncStatusDlg::~SyncStatusDlg()
276 // make sure bsyncjail dies if we do
277 m_jailexec
.KillApp();
280 void SyncStatusDlg::CreateLayout()
282 m_topsizer
= new wxBoxSizer(wxVERTICAL
);
283 AddStatusSizer(m_topsizer
);
284 AddButtonSizer(m_topsizer
);
286 SetSizer(m_topsizer
);
287 m_topsizer
->SetSizeHints(this);
288 m_topsizer
->Layout();
291 void SyncStatusDlg::AddStatusSizer(wxSizer
*sizer
)
293 wxSizer
*ss
= new wxStaticBoxSizer(
294 new wxStaticBox(this, wxID_ANY
, _T("Sync Progress")),
298 // add a set of short status and progress throbber
299 wxSizer
*shorts
= new wxBoxSizer(wxHORIZONTAL
);
301 m_short_status
= new wxStaticText(this, wxID_ANY
, _T(""),
302 wxDefaultPosition
, wxSize(350, -1),
304 1, wxALIGN_CENTRE_VERTICAL
| wxALL
, 2);
306 m_throbber
= new wxGauge(this, wxID_ANY
, 0),
307 0, wxALL
| wxEXPAND
, 2);
308 ss
->Add( shorts
, 0, wxEXPAND
, 0);
311 m_status_edit
= new wxTextCtrl(this, wxID_ANY
, _T(""),
312 wxDefaultPosition
, wxSize(475, 450),
313 wxTE_MULTILINE
| wxTE_READONLY
| wxTE_RICH
),
314 0, wxALL
| wxEXPAND
, 2);
315 // start with the details hidden
316 m_status_edit
->Hide();
318 sizer
->Add(ss
, 1, wxTOP
| wxLEFT
| wxRIGHT
| wxEXPAND
, 5);
321 void SyncStatusDlg::AddButtonSizer(wxSizer
*sizer
)
323 wxSizer
*button
= new wxBoxSizer(wxHORIZONTAL
);
326 m_details_button
= new wxButton(this,
327 Dialog_SyncStatus_ShowDetailsButton
,
331 button
->Add( -1, -1, 1 );
333 if( m_subset
.size() == 1 ) {
335 m_runapp_button
= new wxButton(this,
336 Dialog_SyncStatus_RunAppButton
,
341 m_syncagain_button
= new wxButton(this,
342 Dialog_SyncStatus_SyncAgainButton
,
346 m_killclose_button
= new wxButton(this,
347 Dialog_SyncStatus_KillCloseButton
,
351 sizer
->Add(button
, 0, wxALL
| wxEXPAND
, 5);
354 void SyncStatusDlg::SetRunning()
356 if( m_runapp_button
)
357 m_runapp_button
->Enable(false);
358 m_syncagain_button
->Enable(false);
359 m_killclose_button
->SetLabel(_T("Kill Sync"));
360 m_killclose_button
->Enable(true);
363 m_throbber
->SetRange(10);
364 m_throbber
->SetValue(0);
368 void SyncStatusDlg::SetClose()
370 if( m_runapp_button
)
371 m_runapp_button
->Enable(true);
372 m_syncagain_button
->Enable(true);
373 m_killclose_button
->SetLabel(_T("Close"));
374 m_killclose_button
->Enable(true);
377 m_throbber
->SetRange(10);
378 m_throbber
->SetValue(10);
382 void SyncStatusDlg::Print(const std::string
&msg
, const wxColour
&colour
)
384 Print(wxString(msg
.c_str(), wxConvUTF8
), colour
);
387 void SyncStatusDlg::Print(const wxString
&msg
, const wxColour
&colour
)
389 m_status_edit
->SetDefaultStyle(wxTextAttr(colour
));
390 m_status_edit
->AppendText(_T("\n") + msg
);
393 void SyncStatusDlg::ShortPrint(const std::string
&msg
)
395 ShortPrint(wxString(msg
.c_str(), wxConvUTF8
));
398 void SyncStatusDlg::ShortPrint(const wxString
&msg
)
400 m_short_status
->SetLabel(msg
);
403 void SyncStatusDlg::Throb()
408 void SyncStatusDlg::StartTimer()
413 void SyncStatusDlg::StopTimer()
418 DeviceEntry
* SyncStatusDlg::GetCurrentDevice()
420 if( m_current_device
== m_subset
.end() )
422 return &(*(*m_current_device
));
425 void SyncStatusDlg::UpdateTitle()
427 if( m_next_device
== m_subset
.end() ) {
428 SetTitle(_T("Sync Progress Dialog"));
432 oss
<< "Syncing: " << (*m_next_device
)->GetPin().str()
433 << " with " << (*m_next_device
)->GetAppNames();
434 wxString
label(oss
.str().c_str(), wxConvUTF8
);
439 void SyncStatusDlg::KillSync()
441 m_jailexec
.KillApp(m_killingjail
);
442 m_killingjail
= true;
444 // jump to the end of the sync roster, so we don't start the
446 m_current_device
= m_next_device
= m_subset
.end();
449 void SyncStatusDlg::StartNextSync()
451 m_killingjail
= false;
454 if( m_next_device
== m_subset
.end() ) {
455 Print("No more devices to sync.", *wxBLACK
);
463 // grab all required information we need to sync
464 m_current_device
= m_next_device
;
465 DeviceEntry
&device
= *(*m_next_device
);
466 m_device_id
= device
.GetPin().str() + " (" + device
.GetDeviceName() + ")";
468 if( !device
.IsConfigured() ) {
469 Print(m_device_id
+ " is not configured, skipping.", *wxRED
);
471 m_current_device
= m_subset
.end();
476 OpenSync::API
*engine
= device
.GetEngine();
477 OpenSync::Config::Group
*group
= device
.GetConfigGroup();
478 string group_name
= group
->GetGroupName();
480 string statusmsg
= "Starting sync for: " + m_device_id
;
481 Print(statusmsg
, *wxBLACK
);
482 ShortPrint(statusmsg
);
484 // for each plugin / app, perform the pre-sync steps
485 for( OpenSync::Config::Group::iterator i
= group
->begin();
489 ConfigUI::ptr ui
= ConfigUI::CreateConfigUI((*i
)->GetAppName());
491 ui
->PreSyncAppInit();
495 // initialize sync jail process
496 if( m_jailexec
.IsAppRunning() ) {
497 string msg
= "ERROR: App running in StartNextSync()";
504 // assume that bsyncjail is in the same directory as barrydesktop
505 wxFileName
path(wxTheApp
->argv
[0]);
506 wxString command
= path
.GetPath();
507 command
+= path
.GetPathSeparator();
508 command
+= _T("bsyncjail ");
509 command
+= wxString(engine
->GetVersion(), wxConvUTF8
);
511 command
+= wxString(group_name
.c_str(), wxConvUTF8
);
513 if( !m_jailexec
.Run(NULL
, "bsyncjail", command
) ) {
514 Print("ERROR: unable to start bsyncjail: " + string(command
.utf8_str()), *wxRED
);
515 ShortPrint("ERROR: unable to start bsyncjail");
520 // sync is underway... advance to the next device
524 void SyncStatusDlg::OnSlowSync()
526 Print("Slow sync detected! Killing sync automatically.", *wxRED
);
529 Print("Slow syncs are known to be unreliable.", *wxBLACK
);
530 Print("Do a 1 Way Reset, and sync again.", *wxBLACK
);
533 void SyncStatusDlg::OnInitDialog(wxInitDialogEvent
&event
)
535 barryverbose("OnInitDialog");
539 void SyncStatusDlg::OnRunApp(wxCommandEvent
&event
)
541 if( m_subset
.size() != 1 )
544 if( m_ui
.get() && m_ui
->IsAppRunning() ) {
545 wxMessageBox(_T("The application is already running."),
546 _T("Run Application"), wxOK
| wxICON_ERROR
);
550 OpenSync::Config::Group
*group
= m_subset
[0]->GetConfigGroup();
554 m_ui
= ConfigUI::CreateConfigUI(group
->GetAppNames());
561 void SyncStatusDlg::OnSyncAgain(wxCommandEvent
&event
)
563 m_next_device
= m_subset
.begin();
567 void SyncStatusDlg::OnKillClose(wxCommandEvent
&event
)
569 if( m_jailexec
.IsAppRunning() ) {
571 if( m_killingjail
) {
572 choice
= wxMessageBox(_T("Already killing sync. Kill again?"),
573 _T("Kill Sync"), wxYES_NO
| wxICON_QUESTION
);
576 choice
= wxMessageBox(_T("This will kill the syncing child process, and will likely require a configuration reset.\n\nKill sync process anyway?"),
577 _T("Kill Sync"), wxYES_NO
| wxICON_QUESTION
);
580 if( choice
== wxYES
) {
583 // print a warning so the user know's what's going on
584 Print("Killing sync... this may take a little while...", *wxRED
);
585 Print("Remember to re-plug your device.", *wxRED
);
587 // let the terminate call clean up the buttons
596 void SyncStatusDlg::OnShowDetails(wxCommandEvent
&event
)
598 if( m_status_edit
->IsShown() ) {
599 m_status_edit
->Hide();
600 m_details_button
->SetLabel(_T("Show Details"));
603 m_status_edit
->Show();
604 m_details_button
->SetLabel(_T("Hide Details"));
606 m_topsizer
->Fit(this);
608 // try to position the window in a readable spot
609 wxSize size
= GetSize();
610 wxPoint pos
= GetScreenPosition();
611 int screen_height
= wxSystemSettings::GetMetric(wxSYS_SCREEN_Y
);
612 if( (pos
.y
+ size
.GetHeight()) > screen_height
&&
613 size
.GetHeight() < screen_height
)
615 int wiggle_room
= screen_height
- size
.GetHeight();
616 int y
= wiggle_room
/ 2;
621 wxConnectionBase
* SyncStatusDlg::OnAcceptConnection(const wxString
&topic
)
623 wxConnectionBase
*con
= 0;
625 if( topic
== TOPIC_STATUS
&& m_status_edit
)
626 con
= new StatusConnection(*this, *m_status_edit
);
627 else if( topic
== TOPIC_CONFLICT
)
628 con
= new ConflictConnection(*this);
631 m_connections
.push_back( dynamic_cast<OptOut::Element
*> (con
) );
636 void SyncStatusDlg::OnExecTerminated(wxProcessEvent
&event
)
640 oss
<< "Sync terminated: ";
642 oss
<< "Sync finished: ";
644 Print(oss
.str(), *wxBLACK
);
645 ShortPrint(oss
.str());
647 m_current_device
= m_subset
.end();
652 void SyncStatusDlg::OnTimer(wxTimerEvent
&event
)