3 /// Mode derived class for syncing
7 Copyright (C) 2009-2012, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "Mode_Sync.h"
23 #include "BaseFrame.h"
24 #include "GroupCfgDlg.h"
25 #include "SyncStatusDlg.h"
26 #include "barrydesktop.h"
27 #include "windowids.h"
32 BEGIN_EVENT_TABLE(SyncMode
, wxEvtHandler
)
33 EVT_BUTTON (SyncMode_SyncNowButton
, SyncMode::OnSyncNow
)
34 EVT_BUTTON (SyncMode_ConfigureButton
, SyncMode::OnConfigure
)
35 EVT_BUTTON (SyncMode_RunAppButton
, SyncMode::OnRunApp
)
36 EVT_BUTTON (SyncMode_1WayResetButton
, SyncMode::On1WayReset
)
37 EVT_LIST_ITEM_SELECTED(SyncMode_DeviceList
, SyncMode::OnListSelChange
)
38 EVT_LIST_ITEM_DESELECTED(SyncMode_DeviceList
, SyncMode::OnListSelChange
)
39 EVT_LIST_ITEM_ACTIVATED(SyncMode_DeviceList
, SyncMode::OnConfigureDevice
)
42 //////////////////////////////////////////////////////////////////////////////
45 SyncMode::SyncMode(wxWindow
*parent
)
50 wxSize client_size
= parent
->GetClientSize();
52 // create our list of devices
53 m_device_set
.reset( new DeviceSet(wxGetApp().GetGlobalConfig(),
54 wxGetApp().GetOpenSync(),
55 wxGetApp().GetResults()) );
56 barryverbose(*m_device_set
);
58 // eliminate all duplicate device entries
59 DeviceSet::subset_type subset
;
61 subset
= m_device_set
->FindDuplicates();
63 // build list of choices
64 wxArrayString choices
;
65 DeviceSet::subset_type::iterator i
= subset
.begin();
66 for( ; i
!= subset
.end(); ++i
) {
67 string desc
= (*i
)->GetIdentifyingString();
68 choices
.Add( wxString(desc
.c_str(), wxConvUTF8
) );
71 // let the user choose
72 // FIXME - the width of the choice dialog is
73 // determined by the length of the string...
74 // which is less than ideal
75 int choice
= wxGetSingleChoiceIndex(_T("Multiple configurations have been found with the same PIN. Please select\nthe configuration that Barry Desktop should work with."),
79 // remove everything except keep
81 subset
.erase(subset
.begin() + choice
);
84 m_device_set
->KillDuplicates(subset
);
86 barryverbose(*m_device_set
);
88 } while( subset
.size() );
91 // create the window controls we need
94 m_topsizer
.reset( new wxBoxSizer(wxVERTICAL
) );
96 // make space for the main header, which is not part of our
98 m_topsizer
->AddSpacer(MAIN_HEADER_OFFSET
);
102 // Select the device(s) you want to sync and press Sync Now
103 // Press Configure... to configure the currently selected device
104 // Press Run App to start the application that the device syncs with
105 // Press 1-Way Reset to recover from a broken sync, copying all
106 // device data to application, or vice versa.
108 m_topsizer
->AddSpacer(5);
110 m_sync_now_button
.reset( new wxButton(parent
,
111 SyncMode_SyncNowButton
, _T("Sync Now")));
112 wxSize sync_button_size
= m_sync_now_button
->GetClientSize();
113 int wrapwidth
= client_size
.GetWidth() - 20 - sync_button_size
.GetWidth();
115 wxBoxSizer
*infosizer
= new wxBoxSizer(wxHORIZONTAL
);
116 wxBoxSizer
*linesizer
= new wxBoxSizer(wxVERTICAL
);
119 #define MAKE_INFO_LABEL(a, b) \
120 m_label[a].reset( new wxStaticText(parent, -1, b, \
121 wxPoint(15, 100)) ); \
122 m_label[a]->Wrap(wrapwidth); \
123 linesizer->Add(m_label[a].get(), 0, wxEXPAND, 0); \
124 linesizer->AddSpacer(4);
125 MAKE_INFO_LABEL(0, _T("Select the device(s) you want to sync and press Sync Now."));
126 MAKE_INFO_LABEL(1, _T("Use Configure to configure the currently selected device."));
127 MAKE_INFO_LABEL(2, _T("Use Run App to start the application that the device syncs with."));
128 MAKE_INFO_LABEL(3, _T("Use 1-Way Reset to recover from a broken sync, copying all device data to application, or vice versa."));
130 infosizer
->Add( linesizer
, 1, wxALIGN_LEFT
, 0 );
131 infosizer
->Add( m_sync_now_button
.get(), 0, wxALIGN_RIGHT
, 0 );
132 m_topsizer
->Add( infosizer
,
133 0, wxEXPAND
| wxLEFT
| wxBOTTOM
| wxRIGHT
, 10 );
135 // status, final spacing
136 m_topsizer
->AddSpacer(10);
139 wxStaticBoxSizer
*box
= new wxStaticBoxSizer(wxHORIZONTAL
, parent
,
141 m_device_list
.reset (new wxListCtrl(parent
, SyncMode_DeviceList
,
142 wxDefaultPosition
, wxDefaultSize
,
143 wxLC_REPORT
/*| wxLC_VRULES*/) );
144 box
->Add( m_device_list
.get(), 1, wxEXPAND
| wxALL
, 4 );
145 m_topsizer
->Add(box
, 1, wxEXPAND
| wxLEFT
| wxRIGHT
| wxBOTTOM
, 10 );
147 // add bottom buttons - these go in the bottom FOOTER area
148 // so their heights must be fixed to MAIN_HEADER_OFFSET
149 // minus a border of 5px top and bottom
150 wxSize
footer(-1, MAIN_HEADER_OFFSET
- 5 - 5);
151 wxBoxSizer
*buttons
= new wxBoxSizer(wxHORIZONTAL
);
152 m_run_app_button
.reset( new wxButton(parent
,
153 SyncMode_RunAppButton
, _T("Run App"),
154 wxDefaultPosition
, footer
));
155 m_configure_button
.reset( new wxButton(parent
,
156 SyncMode_ConfigureButton
, _T("Configure..."),
157 wxDefaultPosition
, footer
) );
158 m_1way_reset_button
.reset( new wxButton(parent
,
159 SyncMode_1WayResetButton
, _T("1 Way Reset..."),
160 wxDefaultPosition
, footer
) );
161 buttons
->Add(m_run_app_button
.get(), 0, wxRIGHT
, 5 );
162 buttons
->Add(m_configure_button
.get(), 0, wxRIGHT
, 5 );
163 buttons
->Add(m_1way_reset_button
.get(), 0, wxRIGHT
, 5 );
164 m_topsizer
->Add(buttons
, 0, wxALL
| wxALIGN_RIGHT
, 5 );
166 // recalc size of children
167 m_topsizer
->SetDimension(0, 0,
168 client_size
.GetWidth(), client_size
.GetHeight());
170 // insert list columns based on the new list size
171 wxSize list_size
= m_device_list
->GetSize();
172 int timestamp_width
= GetMaxTimestampWidth(m_device_list
.get());
173 // FIXME - for some reason, even with the width calculated,
174 // when displayed in the listctrl, there's not enough space...
175 // possibly because there's space between columns... I don't
176 // know how to calculate the column inter-space size, so add
177 // a constant here :-(
178 timestamp_width
+= 8;
179 int usable_width
= list_size
.GetWidth() - timestamp_width
;
180 m_device_list
->InsertColumn(0, _T("PIN"),
181 wxLIST_FORMAT_LEFT
, usable_width
* 0.16);
182 m_device_list
->InsertColumn(1, _T("Name"),
183 wxLIST_FORMAT_LEFT
, usable_width
* 0.33);
184 m_device_list
->InsertColumn(2, _T("Connected"),
185 wxLIST_FORMAT_CENTRE
, usable_width
* 0.16);
186 m_device_list
->InsertColumn(3, _T("Application"),
187 wxLIST_FORMAT_CENTRE
, usable_width
* 0.18);
188 m_device_list
->InsertColumn(4, _T("Engine"),
189 wxLIST_FORMAT_CENTRE
, usable_width
* 0.17);
190 m_device_list
->InsertColumn(5, _T("Last Sync"),
191 wxLIST_FORMAT_CENTRE
, timestamp_width
);
195 // attempt to re-select the devices as we last saw them
196 ReselectDevices(m_device_set
->String2Subset(wxGetApp().GetGlobalConfig().GetKey("SelectedDevices")));
199 // connect ourselves to the parent's event handling chain
200 // do this last, so that we are guaranteed our destructor
201 // will run, in case of exceptions
202 m_parent
->PushEventHandler(this);
205 SyncMode::~SyncMode()
207 m_parent
->PopEventHandler();
209 // save selected devices for later
210 wxGetApp().GetGlobalConfig().SetKey("SelectedDevices",
211 DeviceSet::Subset2String(GetSelectedDevices()));
214 std::string
SyncMode::Timestamp(time_t last_sync
)
218 if( localtime_r(&last_sync
, &local
) != NULL
) {
220 strftime(timestamp
, sizeof(timestamp
), "%b %d, %H:%M", &local
);
226 int SyncMode::GetMaxTimestampWidth(wxWindow
*win
)
229 DeviceSet::const_iterator i
= m_device_set
->begin();
230 for( ; i
!= m_device_set
->end(); ++i
) {
233 if( i
->GetExtras() ) {
234 time_t last_sync
= i
->GetExtras()->m_last_sync_time
;
236 win
->GetTextExtent(wxString(Timestamp(last_sync
).c_str(), wxConvUTF8
), &this_width
, &this_height
);
240 max_width
= max(max_width
, this_width
);
246 void SyncMode::FillDeviceList()
249 m_device_list
->DeleteAllItems();
251 DeviceSet::const_iterator i
= m_device_set
->begin();
252 for( int index
= 0; i
!= m_device_set
->end(); ++i
, index
++ ) {
254 wxString
text(i
->GetPin().Str().c_str(), wxConvUTF8
);
255 long item
= m_device_list
->InsertItem(index
, text
);
258 text
= wxString(i
->GetDeviceName().c_str(), wxConvUTF8
);
259 m_device_list
->SetItem(item
, 1, text
);
262 text
= i
->IsConnected() ? _T("Yes") : _T("No");
263 m_device_list
->SetItem(item
, 2, text
);
266 if( i
->IsConfigured() ) {
267 text
= wxString(i
->GetAppNames().c_str(), wxConvUTF8
);
270 text
= _T("(No config)");
272 m_device_list
->SetItem(item
, 3, text
);
276 text
= wxString(i
->GetEngine()->GetVersion(), wxConvUTF8
);
279 m_device_list
->SetItem(item
, 4, text
);
282 if( i
->GetExtras() ) {
283 time_t last_sync
= i
->GetExtras()->m_last_sync_time
;
285 wxString
ts(Timestamp(last_sync
).c_str(), wxConvUTF8
);
286 m_device_list
->SetItem(item
, 5, ts
);
294 void SyncMode::UpdateButtons()
296 int selected_count
= m_device_list
->GetSelectedItemCount();
298 // update the SyncNow button (only on if anything is selected)
299 m_sync_now_button
->Enable(selected_count
> 0);
300 m_configure_button
->Enable(selected_count
== 1);
301 m_run_app_button
->Enable(selected_count
== 1);
302 m_1way_reset_button
->Enable(selected_count
== 1);
304 // if only one item is selected, enable RunApp button
305 bool enable_run_app
= false;
306 if( selected_count
== 1 ) {
307 // good, only one is selected, find out which one
309 item
= m_device_list
->GetNextItem(item
, wxLIST_NEXT_ALL
,
310 wxLIST_STATE_SELECTED
);
313 DeviceEntry
&entry
= (*m_device_set
)[item
];
314 if( entry
.GetAppNames().size() ) {
315 // has application configured!
316 enable_run_app
= true;
320 m_run_app_button
->Enable(enable_run_app
);
323 DeviceSet::subset_type
SyncMode::GetSelectedDevices()
325 DeviceSet::subset_type subset
;
329 item
= m_device_list
->GetNextItem(item
, wxLIST_NEXT_ALL
,
330 wxLIST_STATE_SELECTED
);
333 subset
.push_back(m_device_set
->begin() + item
);
335 } while( item
!= -1 );
340 void SyncMode::ReselectDevices(const DeviceSet::subset_type
&set
)
342 for( long item
= m_device_list
->GetNextItem(-1); item
!= -1;
343 item
= m_device_list
->GetNextItem(item
) )
345 bool selected
= DeviceSet::FindPin(set
, (*m_device_set
)[item
].GetPin()) != set
.end();
347 m_device_list
->SetItemState(item
,
348 selected
? wxLIST_STATE_SELECTED
: 0,
349 wxLIST_STATE_SELECTED
);
353 void SyncMode::ConfigureDevice(int device_index
)
355 DeviceEntry
&entry
= (*m_device_set
)[device_index
];
356 ConfigureDevice(entry
);
359 void SyncMode::ConfigureDevice(DeviceEntry
&entry
)
361 // make sure it's not already running
362 if( m_cui
.get() && m_cui
->IsAppRunning() ) {
363 wxMessageBox(_T("An application is currently running."),
364 _T("Run App Error"), wxOK
| wxICON_ERROR
);
368 GroupCfgDlg
dlg(m_parent
, entry
, wxGetApp().GetOpenSync());
369 if( dlg
.ShowModal() == wxID_OK
&&
371 dlg
.GetGroup().get() &&
372 dlg
.GetExtras().get() )
374 bool skip_rewrite
= false;
375 bool delete_old
= false;
377 // does old group exist?
378 if( entry
.GetEngine() &&
379 entry
.GetConfigGroup() &&
380 entry
.GetConfigGroup()->GroupExists(*entry
.GetEngine()) )
382 // yes, is the new group equal?
383 string v1
= entry
.GetEngine()->GetVersion();
384 string v2
= dlg
.GetEngine()->GetVersion();
385 skip_rewrite
= (v1
== v2
&&
386 dlg
.GetGroup()->Compare(*entry
.GetConfigGroup()));
389 // config is the same, don't bother saving again
390 barryverbose("Config is the same, skipping save");
393 // clean up after ourselves... if the new
394 // config uses a different engine, delete
395 // the config on the old engine
396 if( entry
.GetEngine() != dlg
.GetEngine() ) {
402 if( !skip_rewrite
) try {
404 OpenSync::API
*eng
= dlg
.GetEngine();
405 DeviceEntry::group_ptr grp
= dlg
.GetGroup();
407 // make sure that the save will be successful
408 if( grp
->GroupExists(*eng
) ) {
409 if( !grp
->GroupMatchesExistingConfig(*eng
) ) {
410 if( WarnAbout1WayReset() ) {
411 eng
->DeleteGroup(grp
->GetGroupName());
412 grp
->DisconnectMembers();
420 // group we want to save has the
421 // same set of plugins and member IDs
422 // as the one already there...
423 // so do not disconnect members
427 // we are saving a brand new group, so make
428 // sure that all members are new
429 grp
->DisconnectMembers();
433 grp
->Save(*dlg
.GetEngine());
435 // clean up the old engine's group, so we don't leave
436 // garbage behind... do this after a successful
437 // save, so that we don't delete existing knowledge
438 // before we've crossed over
440 barryverbose("Engine change detected in config: deleting old config '" << entry
.GetConfigGroup()->GetGroupName() << "' from engine " << entry
.GetEngine()->GetVersion() << " in order to save it to engine " << dlg
.GetEngine()->GetVersion());
441 entry
.GetEngine()->DeleteGroup(entry
.GetConfigGroup()->GetGroupName());
445 catch( OpenSync::Config::SaveError
&se
) {
446 barryverbose("Exception during save: " << se
.what());
447 wxString msg
= _T("Unable to save configuration for this device.\nError: ");
448 msg
+= wxString(se
.what(), wxConvUTF8
);
449 wxMessageBox(msg
, _T("OpenSync Save Error"),
450 wxOK
| wxICON_ERROR
);
454 // save the extras... this is cheap, so no need to check
456 dlg
.GetExtras()->Save(wxGetApp().GetGlobalConfig(),
457 dlg
.GetGroup()->GetGroupName());
459 // update the device set
460 entry
.SetConfigGroup(dlg
.GetGroup(), dlg
.GetEngine(),
462 entry
.SetDeviceName(dlg
.GetDeviceName());
469 void SyncMode::CheckConfigured(DeviceSet::subset_type
&subset
)
471 for( DeviceSet::subset_type::iterator i
= subset
.begin();
475 DeviceEntry
&device
= *(*i
);
476 if( !device
.IsConnected() )
478 if( device
.IsConfigured() )
482 msg
<< "Selected device " << device
.GetPin().Str()
483 << " (" << device
.GetDeviceName() << ")"
484 << " is not yet configured. Configure now?";
486 int response
= wxMessageBox(
487 wxString(msg
.str().c_str(),wxConvUTF8
),
488 _T("Configure Now?"), wxYES_NO
, m_parent
);
489 if( response
== wxYES
) {
490 ConfigureDevice(device
);
495 void SyncMode::RefillList()
497 DeviceSet::subset_type subset
= GetSelectedDevices();
499 ReselectDevices(subset
);
502 int SyncMode::GetSelectedDevice()
504 if( m_device_list
->GetSelectedItemCount() != 1 ) {
505 wxMessageBox(_T("Please select one device from the list."),
506 _T("Device List"), wxOK
| wxICON_ERROR
);
510 // find selected device
512 item
= m_device_list
->GetNextItem(item
, wxLIST_NEXT_ALL
,
513 wxLIST_STATE_SELECTED
);
518 // Returns an index into the config group for the given device index
519 // that represents the authoritative side of the sync. This means
520 // that during the 1-Way Reset, the plugin at this index must NOT
521 // be zapped! All the others must be zapped.
523 // Returns -1 if the user cancels, or if not enough data to decide.
525 int SyncMode::GetAuthoritativeSide(int device_index
)
527 // grab the device entry and group config
528 DeviceEntry
&entry
= (*m_device_set
)[device_index
];
529 OpenSync::Config::Group
*group
= entry
.GetConfigGroup();
535 "Which device / application should be considered\n"
538 "All data in non-authoritative devices / applications\n"
539 "will be deleted in order to setup a straight copy on\n"
542 // build list of devices / applications
544 OpenSync::Config::Group::iterator gi
= group
->begin();
545 for( ; gi
!= group
->end(); ++gi
) {
546 // the Barry plugin is special
547 if( dynamic_cast<OpenSync::Config::Barry
*>( (*gi
).get() ) ) {
548 // this is a Barry plugin, so display the
549 // device name, not the plugin name
550 string device_name
= entry
.GetPin().Str();
551 if( entry
.GetDeviceName().size() )
552 device_name
+= " (" + entry
.GetDeviceName() + ")";
554 list
.Add( wxString(device_name
.c_str(), wxConvUTF8
) );
557 // add the application name
558 list
.Add( wxString((*gi
)->GetAppName().c_str(),
564 int choice
= wxGetSingleChoiceIndex(intro
,
565 _T("Select Authoritative Device / Application"),
570 bool SyncMode::ZapConflicts(int device_index
, int authoritative_side
)
572 // grab the device entry and group config
573 DeviceEntry
&entry
= (*m_device_set
)[device_index
];
574 OpenSync::Config::Group
*group
= entry
.GetConfigGroup();
578 // cycle through list of sync plugins, zapping each
579 // non-authoritative one
580 OpenSync::Config::Group::iterator gi
= group
->begin();
581 for( int i
= 0; gi
!= group
->end(); ++gi
, ++i
) {
583 // skip the authoritative plugin!
584 if( i
== authoritative_side
)
587 OpenSync::Config::Plugin
&plugin
= *(*gi
);
589 // create a configUI object, for zapping
590 ConfigUI::ptr ui
= ConfigUI::CreateConfigUI(plugin
.GetAppName());
592 // no possibility for zapping here... so just
593 // assume that it doesn't need it for now...
594 // worst that can happen, should be it slow-syncs
599 bool success
= ui
->ZapData(m_parent
, *gi
, entry
.GetEngine());
601 // if the user cancels one, cancel all the rest
606 // if we reach this, we succeeded
610 void SyncMode::RewriteConfig(int device_index
)
612 // grab the device entry and group config
613 DeviceEntry
&entry
= (*m_device_set
)[device_index
];
614 OpenSync::Config::Group
*group
= entry
.GetConfigGroup();
615 OpenSync::API
*engine
= entry
.GetEngine();
616 if( !group
|| !engine
)
619 string group_name
= group
->GetGroupName();
622 // delete the existing group name
623 engine
->DeleteGroup(group_name
);
625 // disconnect the plugins from the group, to
626 // make them "new" again
627 group
->DisconnectMembers();
630 group
->Save(*engine
);
633 barryverbose(group_name
<< " group config rewritten");
636 catch( std::runtime_error
&re
) {
638 oss
<< "Unable to rewrite config! Start over manually. "
639 "Error: " << re
.what();
640 wxString
msg(oss
.str().c_str(), wxConvUTF8
);
642 wxMessageBox(msg
, _T("Error Rewriting Config"),
643 wxOK
| wxICON_ERROR
, m_parent
);
646 // if we get here, the group is in an undefined state,
647 // so delete it to make sure nothing odd is left behind
649 engine
->DeleteGroup(group_name
);
651 catch( std::runtime_error
&re
) {
652 barryverbose("Error while deleting undefined group '" << group_name
<< "': " << re
.what());
656 bool SyncMode::WarnAbout1WayReset()
658 int answer
= wxMessageBox( _T("The sync config you are about to save "
659 "is sufficiently different from the existing one that "
660 "a 1-Way Reset will be required. You will need to "
661 "perform the reset at your earliest convenience, before "
662 "your next sync.\n\n"
664 _T("1-Way Reset Warning"),
665 wxYES_NO
| wxICON_QUESTION
, m_parent
);
666 return answer
== wxYES
;
669 void SyncMode::OnSyncNow(wxCommandEvent
&event
)
671 DeviceSet::subset_type subset
= GetSelectedDevices();
672 if( subset
.size() == 0 )
673 return; // nothing to do
675 // make sure an app is not running
676 if( m_cui
.get() && m_cui
->IsAppRunning() ) {
677 wxMessageBox(_T("An application is currently running."),
678 _T("Sync Error"), wxOK
| wxICON_ERROR
);
682 // check that all selections are configured
683 CheckConfigured(subset
);
686 SyncStatusDlg
dlg(m_parent
, subset
);
689 // update the timestamps
693 void SyncMode::OnConfigure(wxCommandEvent
&event
)
695 int item
= GetSelectedDevice();
697 ConfigureDevice(item
);
700 void SyncMode::OnRunApp(wxCommandEvent
&event
)
702 // make sure it's not already running
703 if( m_cui
.get() && m_cui
->IsAppRunning() ) {
704 wxMessageBox(_T("An application is already running."),
705 _T("Run App Error"), wxOK
| wxICON_ERROR
);
709 // find selected device
710 int item
= GetSelectedDevice();
714 // retrieve device's group config
715 DeviceEntry
&entry
= (*m_device_set
)[item
];
716 OpenSync::Config::Plugin
*plugin
= 0;
717 if( entry
.GetConfigGroup() )
718 plugin
= entry
.GetConfigGroup()->GetNonBarryPlugin();
723 m_cui
= ConfigUI::CreateConfigUI(plugin
->GetAppName());
725 m_cui
->RunApp(m_parent
);
728 void SyncMode::On1WayReset(wxCommandEvent
&event
)
730 int item
= GetSelectedDevice();
734 // let user pick the authoritative side of the sync
735 int side
= GetAuthoritativeSide(item
);
739 // zap the data of all remaining sync elements
740 if( !ZapConflicts(item
, side
) )
743 // rewrite the config
746 // reload the deviceset
749 // tell the user all's well
750 wxMessageBox(_T("1-Way Reset is complete, and ready to sync."),
751 _T("Reset Complete"), wxOK
| wxICON_INFORMATION
, m_parent
);
754 void SyncMode::OnListSelChange(wxListEvent
&event
)
759 void SyncMode::OnConfigureDevice(wxListEvent
&event
)
761 ConfigureDevice(event
.GetIndex());