2 /// \file MigrateDlg.cc
3 /// Dialog for the "Migrate Device" main menu mode button...
4 /// going with a dialog instead of a mode class this time.
8 Copyright (C) 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 "MigrateDlg.h"
24 #include "windowids.h"
26 #include "barrydesktop.h"
28 #include <wx/statline.h>
29 #include <barry/barrybackup.h>
33 using namespace OpenSync
;
35 DEFINE_EVENT_TYPE(MET_THREAD_FINISHED
)
36 DEFINE_EVENT_TYPE(MET_CHECK_DEST_PIN
)
37 DEFINE_EVENT_TYPE(MET_SET_STATUS_MSG
)
38 DEFINE_EVENT_TYPE(MET_PROMPT_PASSWORD
)
39 DEFINE_EVENT_TYPE(MET_ERROR_MSG
)
41 BEGIN_EVENT_TABLE(MigrateDlg
, wxDialog
)
42 EVT_BUTTON (Dialog_Migrate_MigrateNowButton
,
43 MigrateDlg::OnMigrateNow
)
44 EVT_BUTTON (Dialog_Migrate_CancelButton
,
46 EVT_CLOSE (MigrateDlg::OnCloseWindow
)
47 EVT_COMMAND (wxID_ANY
, MET_THREAD_FINISHED
,
48 MigrateDlg::OnThreadFinished
)
49 EVT_COMMAND (wxID_ANY
, MET_CHECK_DEST_PIN
,
50 MigrateDlg::OnCheckDestPin
)
51 EVT_COMMAND (wxID_ANY
, MET_SET_STATUS_MSG
,
52 MigrateDlg::OnSetStatusMsg
)
53 EVT_COMMAND (wxID_ANY
, MET_PROMPT_PASSWORD
,
54 MigrateDlg::OnPromptPassword
)
55 EVT_COMMAND (wxID_ANY
, MET_ERROR_MSG
,
56 MigrateDlg::OnErrorMsg
)
60 class EventDesktopConnector
: public Barry::DesktopConnector
65 EventDesktopConnector(MigrateDlg
*dlg
, const char *password
,
66 const std::string
&locale
, const Barry::ProbeResult
&result
)
67 : Barry::DesktopConnector(password
, locale
, result
)
72 virtual bool PasswordPrompt(const Barry::BadPassword
&bp
,
73 std::string
&password_result
);
76 bool EventDesktopConnector::PasswordPrompt(const Barry::BadPassword
&bp
,
77 std::string
&password_result
)
79 // ping the parent and wait for finish
80 wxCommandEvent
event(MET_PROMPT_PASSWORD
, wxID_ANY
);
81 event
.SetEventObject(m_dlg
);
82 event
.SetInt(bp
.remaining_tries());
83 m_dlg
->AddPendingEvent(event
);
84 m_dlg
->WaitForEvent();
86 password_result
= m_dlg
->GetPassword().utf8_str();
88 // assume that a blank password means the user wishes to quit...
89 // wxWidgets doesn't seem to handle this very well?
90 return password_result
.size() > 0;
94 //////////////////////////////////////////////////////////////////////////////
97 MigrateDlg::MigrateDlg(wxWindow
*parent
,
98 const Barry::Probe::Results
&results
,
99 int current_device_index
)
100 : wxDialog(parent
, Dialog_GroupCfg
, _T("Migrate Device"))
102 , m_current_device_index(current_device_index
)
103 , m_migrate_thread_created(false)
104 , m_abort_flag(false)
105 , m_thread_running(false)
111 , m_write_mode_combo(0)
112 , m_migrate_button(0)
121 void MigrateDlg::WaitForEvent()
126 void MigrateDlg::CreateLayout()
128 m_topsizer
= new wxBoxSizer(wxVERTICAL
);
130 // AddDescriptionSizer(m_topsizer);
131 AddMainSizer(m_topsizer
);
132 AddStatusSizer(m_topsizer
);
134 SetSizer(m_topsizer
);
135 m_topsizer
->SetSizeHints(this);
136 m_topsizer
->Layout();
139 void MigrateDlg::AddDescriptionSizer(wxSizer
*sizer
)
142 new wxStaticText(this, wxID_ANY
,
143 _T("Migrate device data from source device to target device.")),
144 0, wxALIGN_CENTRE
| wxALIGN_CENTRE_VERTICAL
|
145 wxTOP
| wxLEFT
| wxRIGHT
, 10);
148 void MigrateDlg::AddMainSizer(wxSizer
*sizer
)
150 // add 3 main sections together into one sizer
151 wxSizer
*main
= new wxBoxSizer(wxHORIZONTAL
);
152 Main_AddSourceSizer(main
);
153 Main_AddButtonSizer(main
);
154 Main_AddDestSizer(main
);
156 // add main sizer to top level sizer
157 sizer
->Add(main
, 0, wxEXPAND
| wxTOP
| wxLEFT
| wxRIGHT
, 10);
160 void MigrateDlg::AddStatusSizer(wxSizer
*sizer
)
162 sizer
->Add( new wxStaticLine(this),
163 0, wxEXPAND
| wxTOP
| wxLEFT
| wxRIGHT
, 10);
166 m_status
= new wxStaticText(this, wxID_ANY
, _T("Ready...")),
167 0, wxEXPAND
| wxLEFT
| wxRIGHT
, 10);
168 // Reduce font size for status text
169 wxFont font
= m_status
->GetFont();
170 font
.SetPointSize(font
.GetPointSize() - 1);
171 m_status
->SetFont( font
);
174 m_progress
= new wxGauge(this, wxID_ANY
, 100),
175 0, wxEXPAND
| wxLEFT
| wxRIGHT
| wxBOTTOM
, 10);
178 void MigrateDlg::Main_AddSourceSizer(wxSizer
*sizer
)
180 wxArrayString devices
;
181 for( Barry::Probe::Results::const_iterator i
= m_results
.begin();
182 i
!= m_results
.end(); ++i
)
184 devices
.Add(wxString(i
->GetDisplayName().c_str(), wxConvUTF8
));
187 wxSizer
*source
= new wxStaticBoxSizer(
188 new wxStaticBox(this, wxID_ANY
, _T("Source device")),
192 m_source_combo
= new wxChoice(this, wxID_ANY
,
193 wxDefaultPosition
, wxSize(225, -1), devices
),
196 if( m_current_device_index
>= 0 )
197 m_source_combo
->SetSelection(m_current_device_index
);
199 sizer
->Add(source
, 0, wxEXPAND
, 0);
202 void MigrateDlg::Main_AddButtonSizer(wxSizer
*sizer
)
204 wxSizer
*buttons
= new wxBoxSizer(wxVERTICAL
);
205 buttons
->Add( m_migrate_button
= new wxButton(this,
206 Dialog_Migrate_MigrateNowButton
, _T("Migrate Now")),
207 0, wxALIGN_CENTRE
, 0);
208 m_migrate_button
->SetDefault();
209 buttons
->AddSpacer(10);
210 buttons
->Add( new wxButton(this, Dialog_Migrate_CancelButton
,
212 0, wxALIGN_CENTRE
, 0);
214 sizer
->Add(buttons
, 1, wxALIGN_CENTRE
| wxLEFT
| wxRIGHT
, 10);
217 void MigrateDlg::Main_AddDestSizer(wxSizer
*sizer
)
219 wxArrayString devices
;
220 devices
.Add(_T("Prompt to plug in later..."));
221 for( Barry::Probe::Results::const_iterator i
= m_results
.begin();
222 i
!= m_results
.end(); ++i
)
224 devices
.Add(wxString(i
->GetDisplayName().c_str(), wxConvUTF8
));
227 wxSizer
*dest
= new wxStaticBoxSizer(
228 new wxStaticBox(this, wxID_ANY
, _T("Destination device")),
232 m_dest_combo
= new wxChoice(this, wxID_ANY
,
233 wxDefaultPosition
, wxSize(225, -1), devices
),
235 m_dest_combo
->SetSelection(0);
238 wxArrayString write_modes
;
239 write_modes
.Add(_T("Erase all, then restore"));
240 write_modes
.Add(_T("Add new, and overwrite existing"));
241 write_modes
.Add(_T("Add only, don't overwrite existing"));
242 write_modes
.Add(_T("Add every record as a new entry (may cause duplicates)"));
244 dest
->Add( new wxStaticText(this, wxID_ANY
, _T("Write Mode:")),
245 0, wxTOP
| wxLEFT
| wxRIGHT
, 5);
246 dest
->Add( m_write_mode_combo
= new wxChoice(this, wxID_ANY
,
247 wxDefaultPosition
, wxSize(225, -1), write_modes
),
249 m_write_mode_combo
->SetSelection(0);
251 // dest->Add( m_wipe_check = wxCheckBox(maybe a checkbox for "wipe device before restore"));
253 sizer
->Add(dest
, 0, wxEXPAND
, 0);
256 void MigrateDlg::EnableControls(bool enable
)
258 m_source_combo
->Enable(enable
);
259 m_dest_combo
->Enable(enable
);
260 m_write_mode_combo
->Enable(enable
);
261 m_migrate_button
->Enable(enable
);
262 //m_wipe_check->Enable(enable);
265 void MigrateDlg::DoSafeClose()
267 // if migrate thread is running, try to close it down first...
268 // do not exit the dialog until the thread is properly stopped!
269 if( m_thread_running
) {
272 m_status
->SetLabel(_T("Waiting for thread to close..."));
274 if( m_migrate_thread_created
) {
276 pthread_join(m_migrate_thread
, &junk
);
277 m_migrate_thread_created
= false;
281 // all activity is stopped, so close dialog
282 EndModal(wxID_CANCEL
);
285 void MigrateDlg::SendEvent(int event_type
)
287 wxCommandEvent
event(event_type
, wxID_ANY
);
288 event
.SetEventObject(this);
289 AddPendingEvent(event
);
292 void MigrateDlg::SendStatusEvent(const wxString
&msg
, int pos
, int max
)
294 wxCommandEvent
event(MET_SET_STATUS_MSG
, wxID_ANY
);
295 event
.SetEventObject(this);
297 event
.SetString(msg
);
311 AddPendingEvent(event
);
314 void MigrateDlg::SendErrorMsgEvent(const wxString
&msg
)
316 wxCommandEvent
event(MET_ERROR_MSG
, wxID_ANY
);
317 event
.SetEventObject(this);
318 event
.SetString(msg
);
319 AddPendingEvent(event
);
322 void MigrateDlg::OnMigrateNow(wxCommandEvent
&event
)
324 // gather info from dialog
325 int source_index
= m_source_combo
->GetSelection();
326 int dest_index
= m_dest_combo
->GetSelection();
327 int write_mode_index
= m_write_mode_combo
->GetSelection();
330 if( source_index
== wxNOT_FOUND
|| dest_index
== wxNOT_FOUND
||
331 write_mode_index
== wxNOT_FOUND
)
333 wxMessageBox(_T("Please select a source and destination device, as well as the write mode."),
334 _T("Migration Options Needed"), wxOK
| wxICON_ERROR
);
338 // do not migrate from one PIN to the same PIN
339 if( source_index
== (dest_index
- 1) ) {
340 wxMessageBox(_T("Cannot migrate from and to the same PIN."),
341 _T("Migration Options Error"), wxOK
| wxICON_ERROR
);
345 // set the migration arguments
346 m_source_device
= &m_results
[source_index
];
347 if( dest_index
> 0 ) {
348 m_dest_device
= &m_results
[dest_index
-1];
351 m_dest_device
= 0; // an invalid dest causes a prompt
353 switch( write_mode_index
)
356 m_write_mode
= Barry::DeviceParser::ERASE_ALL_WRITE_ALL
;
359 m_write_mode
= Barry::DeviceParser::INDIVIDUAL_OVERWRITE
;
362 m_write_mode
= Barry::DeviceParser::ADD_BUT_NO_OVERWRITE
;
365 m_write_mode
= Barry::DeviceParser::ADD_WITH_NEW_ID
;
368 wxMessageBox(_T("Invalid write mode. This should never happen. Contact the developers."),
369 _T("Internal Logic Error"), wxOK
| wxICON_ERROR
);
373 // disable all buttons and controls except cancel
374 EnableControls(false);
376 // turn off the stop flag
377 m_abort_flag
= false;
378 m_thread_running
= false;
380 // fire up migrate thread, and let the thread close the dialog when
381 // done (can we EndModal() from a thread?)
382 int ret
= pthread_create(&m_migrate_thread
, NULL
,
383 &MigrateDlg::MigrateThread
, this);
386 EnableControls(true);
390 m_migrate_thread_created
= true;
393 // thread started... let it finish
396 void MigrateDlg::OnCancel(wxCommandEvent
&event
)
401 void MigrateDlg::OnCloseWindow(wxCloseEvent
&event
)
406 void MigrateDlg::OnThreadFinished(wxCommandEvent
&event
)
408 if( m_migrate_thread_created
) {
409 m_status
->SetLabel(_T("Waiting for thread..."));
411 pthread_join(m_migrate_thread
, &junk
);
412 m_migrate_thread_created
= false;
416 // user cancelled in some way, restore GUI
417 EnableControls(true);
418 m_status
->SetLabel(_T("Cancelled by user..."));
421 // if we were not aborted, then this is success, and we
422 // can close the original dialog
423 EndModal(wxID_CANCEL
);
427 void MigrateDlg::OnCheckDestPin(wxCommandEvent
&event
)
429 ScopeSignaler
done(m_waiter
);
431 if( m_dest_device
&& m_dest_device
->m_pin
.Valid() )
432 return; // nothing to do
434 // no destination pin was available, so user may need to plugin
435 // the new device right now, before continuing
436 int response
= wxMessageBox(_T("Please plug in the target device now."),
437 _T("Ready for Writing"), wxOK
| wxCANCEL
| wxICON_INFORMATION
,
439 if( response
!= wxOK
) {
447 m_status
->SetLabel(_T("Scanning USB for devices..."));
449 // pause for 2 seconds to let any new devices settle down
453 // rescan the USB and try to find the new device
455 m_new_results
= probe
.GetResults();
458 // now prompt the user... create a list of PIN display names
459 wxArrayString devices
;
460 for( Barry::Probe::Results::const_iterator i
= m_new_results
.begin();
461 i
!= m_new_results
.end(); ++i
)
463 devices
.Add(wxString(i
->GetDisplayName().c_str(), wxConvUTF8
));
466 m_status
->SetLabel(_T("User input..."));
470 // prompt the user with this list
471 int choice
= wxGetSingleChoiceIndex(_T("Please select the target device to write to:"),
472 _T("Destination PIN"), devices
, this);
479 // found the new PIN to use!
480 m_dest_device
= &m_new_results
[choice
];
482 // check if user needs to choose again
483 if( m_dest_device
->m_pin
== m_source_device
->m_pin
) {
484 wxMessageBox(_T("Cannot use the same device PIN as migration destination."),
485 _T("Invalid Device Selection"),
486 wxOK
| wxICON_ERROR
, this);
489 } while( m_dest_device
->m_pin
== m_source_device
->m_pin
);
492 void MigrateDlg::OnSetStatusMsg(wxCommandEvent
&event
)
494 if( event
.GetString().size() ) {
495 m_status
->SetLabel(event
.GetString());
498 if( (unsigned int)event
.GetInt() != 0xffff ) {
499 unsigned int value
= (unsigned int) event
.GetInt();
500 unsigned int pos
= (value
& 0xff00) >> 8;
501 unsigned int max
= (value
& 0xff);
504 m_progress
->SetValue(pos
);
508 m_progress
->SetRange(max
);
513 void MigrateDlg::OnPromptPassword(wxCommandEvent
&event
)
515 ScopeSignaler
done(m_waiter
);
517 // create prompt based on exception data
519 oss
<< "Please enter device password: ("
521 << " tries remaining)";
522 wxString
prompt(oss
.str().c_str(), wxConvUTF8
);
524 // ask user for device password
525 m_password
= wxGetPasswordFromUser(prompt
,
526 _T("Device Password"), _T(""), this);
529 void MigrateDlg::OnErrorMsg(wxCommandEvent
&event
)
531 ScopeSignaler
done(m_waiter
);
532 wxMessageBox(event
.GetString(), _T("Migration Error"),
533 wxOK
| wxICON_ERROR
, this);
536 void* MigrateDlg::MigrateThread(void *arg
)
538 MigrateDlg
*us
= (MigrateDlg
*) arg
;
541 us
->m_thread_running
= true;
547 // make sure we have a destination PIN
548 if( !us
->m_abort_flag
)
551 // restore to dest PIN
552 if( !us
->m_abort_flag
)
555 catch( std::exception
&e
) {
556 us
->SendErrorMsgEvent(wxString(e
.what(), wxConvUTF8
));
560 // invalidate the device selection pointers, since
561 // m_new_results may not always exist
562 us
->m_source_device
= us
->m_dest_device
= 0;
565 us
->m_thread_running
= false;
567 // send event to let main GUI thread we're finished
568 us
->SendEvent(MET_THREAD_FINISHED
);
573 class PeekParser
: public Barry::Parser
575 std::string m_dbname
;
578 virtual void ParseRecord(const Barry::DBData
&data
,
579 const Barry::IConverter
*ic
)
581 m_dbname
= data
.GetDBName();
584 const std::string
& GetDBName() const { return m_dbname
; }
588 // This is called from the thread
589 void MigrateDlg::BackupSource()
591 // connect to the source device
592 SendStatusEvent(_T("Connecting..."));
593 EventDesktopConnector
connect(this, "", "utf-8", *m_source_device
);
594 if( !connect
.Reconnect(2) ) {
600 // fetch DBDB, for list of databases to backup... back them all up
601 // remember to save this DBDB into the class, so it is available
602 // for the restore stage
603 m_source_dbdb
= connect
.GetDesktop().GetDBDB();
604 unsigned int total
= m_source_dbdb
.GetTotalRecordCount();
606 // create DeviceBuilder for fetching all
607 Barry::DeviceBuilder
builder(connect
.GetDesktop());
608 builder
.Add(m_source_dbdb
);
610 // calculate the default backup path location, based on user name
611 // (see backup GUI for code?)
612 Barry::ConfigFile
cfg(m_source_device
->m_pin
);
613 Barry::ConfigFile::CheckPath(cfg
.GetPath());
614 m_backup_tarfile
= cfg
.GetPath() + "/" +
615 Barry::MakeBackupFilename(m_source_device
->m_pin
, "migrate");
617 // and create tarball output parser
618 Barry::Backup
parser(m_backup_tarfile
);
621 Barry::Pipe
pipe(builder
);
625 // create tee parser, so we can see what's being written
626 Barry::TeeParser tee
;
631 unsigned int count
= 0;
632 SendStatusEvent(_T(""), 0, 100);
634 // cycle through all databases
635 while( !builder
.EndOfFile() ) {
636 if( !pipe
.PumpEntry(tee
) )
641 // ok, first entry has been pumped, so we can use
642 // peeker to create the status message, and only once
644 oss
<< "Backing up database: " << peeker
.GetDBName() << "...";
646 // calculate 1 to 100 percentage, based on number of
647 // databases being backed up, and update status bar too
648 int percent
= count
/ (float)total
* 100.0;
650 // send status event once
651 SendStatusEvent(wxString(oss
.str().c_str(),wxConvUTF8
), percent
);
653 // backup the rest of the database, but don't update status
656 while( pipe
.PumpEntry(tee
) ) {
660 int percent
= count
/ (float)total
* 100.0;
661 SendStatusEvent(_T(""), percent
);
665 // on each record (pump cycle?), as often as possible,
666 // check the m_abort_flag, and abort if necessary,
667 // updating the status message
669 SendStatusEvent(_T("Backup aborted by user..."));
675 // close all files, etc.
679 // This is called from the thread
680 void MigrateDlg::CheckDestPin()
682 SendEvent(MET_CHECK_DEST_PIN
);
686 // This is called from the thread
687 void MigrateDlg::RestoreToDest()
689 // connect to the dest device
690 SendStatusEvent(_T("Connecting to target..."));
691 EventDesktopConnector
connect(this, "", "utf-8", *m_dest_device
);
692 if( !connect
.Reconnect(2) ) {
698 // fetch DBDB of dest device, for list of databases we can restore
699 // to... compare with the backup DBDB to create a list of similarly
700 // named databases which we can restore....
701 Barry::DatabaseDatabase dest_dbdb
= connect
.GetDesktop().GetDBDB();
703 // create the restore builder object, to read from tarball
704 Barry::Restore
builder(m_backup_tarfile
);
706 // add all database names from the _dest_ DBDB to the Restore
707 // filter... this way, only databases that exist on the new
708 // device will get restored
709 builder
.Add(dest_dbdb
);
710 unsigned int total
= builder
.GetRecordTotal();// counts filtered records
712 // create device parser, to write to device
713 Barry::DeviceParser
parser(connect
.GetDesktop(), m_write_mode
);
716 Barry::Pipe
pipe(builder
);
719 unsigned int count
= 0;
720 SendStatusEvent(_T(""), 0, 100);
722 // cycle through all databases
723 Barry::DBData meta
; // record meta data of next tar record
724 while( !builder
.EndOfFile() ) try {
725 if( !builder
.GetNextMeta(meta
) )
728 // ok, first entry has been pumped, so we can use
729 // peeker to create the status message, and only once
731 oss
<< "Writing database: " << meta
.GetDBName() << "...";
733 // for debugging purposes in the field, display the names
734 // of the databases we restore
735 cerr
<< oss
.str() << endl
;
737 // calculate 1 to 100 percentage, based on number of
738 // databases being backed up, and update status bar too
739 int percent
= count
/ (float)total
* 100.0;
741 // send status event once
742 SendStatusEvent(wxString(oss
.str().c_str(),wxConvUTF8
), percent
);
744 // restore the rest of the database, but don't update status
747 while( pipe
.PumpEntry(parser
) ) {
751 int percent
= count
/ (float)total
* 100.0;
752 SendStatusEvent(_T(""), percent
);
756 // on each record (pump cycle?), as often as possible,
757 // check the m_abort_flag, and abort if necessary,
758 // updating the status message
760 SendStatusEvent(_T("Restore aborted by user..."));
765 catch( Barry::ReturnCodeError
&e
) {
766 cerr
<< "Unable to clear or write to database '" << meta
.GetDBName() << "'"
767 << ": " << e
.what() << endl
;
768 cerr
<< "Continuing to process remaining records..." << endl
;
770 // skip the problematic database, and keep on trying
771 builder
.SkipCurrentDB();