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>
34 using namespace OpenSync
;
36 DEFINE_EVENT_TYPE(MET_THREAD_FINISHED
)
37 DEFINE_EVENT_TYPE(MET_CHECK_DEST_PIN
)
38 DEFINE_EVENT_TYPE(MET_SET_STATUS_MSG
)
39 DEFINE_EVENT_TYPE(MET_PROMPT_PASSWORD
)
40 DEFINE_EVENT_TYPE(MET_ERROR_MSG
)
42 BEGIN_EVENT_TABLE(MigrateDlg
, wxDialog
)
43 EVT_BUTTON (Dialog_Migrate_MigrateNowButton
,
44 MigrateDlg::OnMigrateNow
)
45 EVT_BUTTON (Dialog_Migrate_CancelButton
,
47 EVT_CLOSE (MigrateDlg::OnCloseWindow
)
48 EVT_COMMAND (wxID_ANY
, MET_THREAD_FINISHED
,
49 MigrateDlg::OnThreadFinished
)
50 EVT_COMMAND (wxID_ANY
, MET_CHECK_DEST_PIN
,
51 MigrateDlg::OnCheckDestPin
)
52 EVT_COMMAND (wxID_ANY
, MET_SET_STATUS_MSG
,
53 MigrateDlg::OnSetStatusMsg
)
54 EVT_COMMAND (wxID_ANY
, MET_PROMPT_PASSWORD
,
55 MigrateDlg::OnPromptPassword
)
56 EVT_COMMAND (wxID_ANY
, MET_ERROR_MSG
,
57 MigrateDlg::OnErrorMsg
)
61 class EventDesktopConnector
: public Barry::DesktopConnector
66 EventDesktopConnector(MigrateDlg
*dlg
, const char *password
,
67 const std::string
&locale
, const Barry::ProbeResult
&result
)
68 : Barry::DesktopConnector(password
, locale
, result
)
73 virtual bool PasswordPrompt(const Barry::BadPassword
&bp
,
74 std::string
&password_result
);
77 bool EventDesktopConnector::PasswordPrompt(const Barry::BadPassword
&bp
,
78 std::string
&password_result
)
80 // ping the parent and wait for finish
81 wxCommandEvent
event(MET_PROMPT_PASSWORD
, wxID_ANY
);
82 event
.SetEventObject(m_dlg
);
83 event
.SetInt(bp
.remaining_tries());
84 m_dlg
->AddPendingEvent(event
);
85 m_dlg
->WaitForEvent();
87 password_result
= m_dlg
->GetPassword().utf8_str();
89 // assume that a blank password means the user wishes to quit...
90 // wxWidgets doesn't seem to handle this very well?
91 return password_result
.size() > 0;
95 //////////////////////////////////////////////////////////////////////////////
98 MigrateDlg::MigrateDlg(wxWindow
*parent
,
99 const Barry::Probe::Results
&results
,
100 int current_device_index
)
101 : wxDialog(parent
, Dialog_GroupCfg
, _W("Migrate Device"))
103 , m_current_device_index(current_device_index
)
104 , m_migrate_thread_created(false)
105 , m_abort_flag(false)
106 , m_thread_running(false)
112 , m_write_mode_combo(0)
113 , m_migrate_button(0)
122 void MigrateDlg::WaitForEvent()
127 void MigrateDlg::CreateLayout()
129 m_topsizer
= new wxBoxSizer(wxVERTICAL
);
131 // AddDescriptionSizer(m_topsizer);
132 AddMainSizer(m_topsizer
);
133 AddStatusSizer(m_topsizer
);
135 SetSizer(m_topsizer
);
136 m_topsizer
->SetSizeHints(this);
137 m_topsizer
->Layout();
140 void MigrateDlg::AddDescriptionSizer(wxSizer
*sizer
)
143 new wxStaticText(this, wxID_ANY
,
144 _W("Migrate device data from source device to target device.")),
145 0, wxALIGN_CENTRE
| wxALIGN_CENTRE_VERTICAL
|
146 wxTOP
| wxLEFT
| wxRIGHT
, 10);
149 void MigrateDlg::AddMainSizer(wxSizer
*sizer
)
151 // add 3 main sections together into one sizer
152 wxSizer
*main
= new wxBoxSizer(wxHORIZONTAL
);
153 Main_AddSourceSizer(main
);
154 Main_AddButtonSizer(main
);
155 Main_AddDestSizer(main
);
157 // add main sizer to top level sizer
158 sizer
->Add(main
, 0, wxEXPAND
| wxTOP
| wxLEFT
| wxRIGHT
, 10);
161 void MigrateDlg::AddStatusSizer(wxSizer
*sizer
)
163 sizer
->Add( new wxStaticLine(this),
164 0, wxEXPAND
| wxTOP
| wxLEFT
| wxRIGHT
, 10);
167 m_status
= new wxStaticText(this, wxID_ANY
, _W("Ready...")),
168 0, wxEXPAND
| wxLEFT
| wxRIGHT
, 10);
169 // Reduce font size for status text
170 wxFont font
= m_status
->GetFont();
171 font
.SetPointSize(font
.GetPointSize() - 1);
172 m_status
->SetFont( font
);
175 m_progress
= new wxGauge(this, wxID_ANY
, 100),
176 0, wxEXPAND
| wxLEFT
| wxRIGHT
| wxBOTTOM
, 10);
179 void MigrateDlg::Main_AddSourceSizer(wxSizer
*sizer
)
181 wxArrayString devices
;
182 for( Barry::Probe::Results::const_iterator i
= m_results
.begin();
183 i
!= m_results
.end(); ++i
)
185 devices
.Add(wxString(i
->GetDisplayName().c_str(), wxConvUTF8
));
188 wxSizer
*source
= new wxStaticBoxSizer(
189 new wxStaticBox(this, wxID_ANY
, _W("Source device")),
193 m_source_combo
= new wxChoice(this, wxID_ANY
,
194 wxDefaultPosition
, wxSize(225, -1), devices
),
197 if( m_current_device_index
>= 0 )
198 m_source_combo
->SetSelection(m_current_device_index
);
200 sizer
->Add(source
, 0, wxEXPAND
, 0);
203 void MigrateDlg::Main_AddButtonSizer(wxSizer
*sizer
)
205 wxSizer
*buttons
= new wxBoxSizer(wxVERTICAL
);
206 buttons
->Add( m_migrate_button
= new wxButton(this,
207 Dialog_Migrate_MigrateNowButton
, _W("Migrate Now")),
208 0, wxALIGN_CENTRE
, 0);
209 m_migrate_button
->SetDefault();
210 buttons
->AddSpacer(10);
211 buttons
->Add( new wxButton(this, Dialog_Migrate_CancelButton
,
213 0, wxALIGN_CENTRE
, 0);
215 sizer
->Add(buttons
, 1, wxALIGN_CENTRE
| wxLEFT
| wxRIGHT
, 10);
218 void MigrateDlg::Main_AddDestSizer(wxSizer
*sizer
)
220 wxArrayString devices
;
221 devices
.Add(_W("Prompt to plug in later..."));
222 for( Barry::Probe::Results::const_iterator i
= m_results
.begin();
223 i
!= m_results
.end(); ++i
)
225 devices
.Add(wxString(i
->GetDisplayName().c_str(), wxConvUTF8
));
228 wxSizer
*dest
= new wxStaticBoxSizer(
229 new wxStaticBox(this, wxID_ANY
, _W("Destination device")),
233 m_dest_combo
= new wxChoice(this, wxID_ANY
,
234 wxDefaultPosition
, wxSize(225, -1), devices
),
236 m_dest_combo
->SetSelection(0);
239 wxArrayString write_modes
;
240 // TRANSLATORS: these 4 strings are write-mode options in the
241 // Migrate Device dialog
242 write_modes
.Add(_W("Erase all, then restore"));
243 write_modes
.Add(_W("Add new, and overwrite existing"));
244 write_modes
.Add(_W("Add only, don't overwrite existing"));
245 write_modes
.Add(_W("Add every record as a new entry (may cause duplicates)"));
247 dest
->Add( new wxStaticText(this, wxID_ANY
, _W("Write Mode:")),
248 0, wxTOP
| wxLEFT
| wxRIGHT
, 5);
249 dest
->Add( m_write_mode_combo
= new wxChoice(this, wxID_ANY
,
250 wxDefaultPosition
, wxSize(225, -1), write_modes
),
252 m_write_mode_combo
->SetSelection(0);
254 // dest->Add( m_wipe_check = wxCheckBox(maybe a checkbox for "wipe device before restore"));
256 sizer
->Add(dest
, 0, wxEXPAND
, 0);
259 void MigrateDlg::EnableControls(bool enable
)
261 m_source_combo
->Enable(enable
);
262 m_dest_combo
->Enable(enable
);
263 m_write_mode_combo
->Enable(enable
);
264 m_migrate_button
->Enable(enable
);
265 //m_wipe_check->Enable(enable);
268 void MigrateDlg::DoSafeClose()
270 // if migrate thread is running, try to close it down first...
271 // do not exit the dialog until the thread is properly stopped!
272 if( m_thread_running
) {
275 m_status
->SetLabel(_W("Waiting for thread to close..."));
277 if( m_migrate_thread_created
) {
279 pthread_join(m_migrate_thread
, &junk
);
280 m_migrate_thread_created
= false;
284 // all activity is stopped, so close dialog
285 EndModal(wxID_CANCEL
);
288 void MigrateDlg::SendEvent(int event_type
)
290 wxCommandEvent
event(event_type
, wxID_ANY
);
291 event
.SetEventObject(this);
292 AddPendingEvent(event
);
295 void MigrateDlg::SendStatusEvent(const wxString
&msg
, int pos
, int max
)
297 wxCommandEvent
event(MET_SET_STATUS_MSG
, wxID_ANY
);
298 event
.SetEventObject(this);
300 event
.SetString(msg
);
314 AddPendingEvent(event
);
317 void MigrateDlg::SendErrorMsgEvent(const wxString
&msg
)
319 wxCommandEvent
event(MET_ERROR_MSG
, wxID_ANY
);
320 event
.SetEventObject(this);
321 event
.SetString(msg
);
322 AddPendingEvent(event
);
325 void MigrateDlg::OnMigrateNow(wxCommandEvent
&event
)
327 // gather info from dialog
328 int source_index
= m_source_combo
->GetSelection();
329 int dest_index
= m_dest_combo
->GetSelection();
330 int write_mode_index
= m_write_mode_combo
->GetSelection();
333 if( source_index
== wxNOT_FOUND
|| dest_index
== wxNOT_FOUND
||
334 write_mode_index
== wxNOT_FOUND
)
336 wxMessageBox(_W("Please select a source and destination device, as well as the write mode."),
337 _W("Migration Options Needed"), wxOK
| wxICON_ERROR
);
341 // do not migrate from one PIN to the same PIN
342 if( source_index
== (dest_index
- 1) ) {
343 wxMessageBox(_W("Cannot migrate from and to the same PIN."),
344 _W("Migration Options Error"), wxOK
| wxICON_ERROR
);
348 // set the migration arguments
349 m_source_device
= &m_results
[source_index
];
350 if( dest_index
> 0 ) {
351 m_dest_device
= &m_results
[dest_index
-1];
354 m_dest_device
= 0; // an invalid dest causes a prompt
356 switch( write_mode_index
)
359 m_write_mode
= Barry::DeviceParser::ERASE_ALL_WRITE_ALL
;
362 m_write_mode
= Barry::DeviceParser::INDIVIDUAL_OVERWRITE
;
365 m_write_mode
= Barry::DeviceParser::ADD_BUT_NO_OVERWRITE
;
368 m_write_mode
= Barry::DeviceParser::ADD_WITH_NEW_ID
;
371 wxMessageBox(_W("Invalid write mode. This should never happen. Contact the developers."),
372 _W("Internal Logic Error"), wxOK
| wxICON_ERROR
);
376 // disable all buttons and controls except cancel
377 EnableControls(false);
379 // turn off the stop flag
380 m_abort_flag
= false;
381 m_thread_running
= false;
383 // fire up migrate thread, and let the thread close the dialog when
384 // done (can we EndModal() from a thread?)
385 int ret
= pthread_create(&m_migrate_thread
, NULL
,
386 &MigrateDlg::MigrateThread
, this);
389 EnableControls(true);
393 m_migrate_thread_created
= true;
396 // thread started... let it finish
399 void MigrateDlg::OnCancel(wxCommandEvent
&event
)
404 void MigrateDlg::OnCloseWindow(wxCloseEvent
&event
)
409 void MigrateDlg::OnThreadFinished(wxCommandEvent
&event
)
411 if( m_migrate_thread_created
) {
412 m_status
->SetLabel(_W("Waiting for thread..."));
414 pthread_join(m_migrate_thread
, &junk
);
415 m_migrate_thread_created
= false;
419 // user cancelled in some way, restore GUI
420 EnableControls(true);
421 m_status
->SetLabel(_W("Cancelled by user..."));
424 // if we were not aborted, then this is success, and we
425 // can close the original dialog
426 EndModal(wxID_CANCEL
);
430 void MigrateDlg::OnCheckDestPin(wxCommandEvent
&event
)
432 ScopeSignaler
done(m_waiter
);
434 if( m_dest_device
&& m_dest_device
->m_pin
.Valid() )
435 return; // nothing to do
437 // no destination pin was available, so user may need to plugin
438 // the new device right now, before continuing
439 int response
= wxMessageBox(_W("Please plug in the target device now."),
440 _W("Ready for Writing"), wxOK
| wxCANCEL
| wxICON_INFORMATION
,
442 if( response
!= wxOK
) {
450 m_status
->SetLabel(_W("Scanning USB for devices..."));
452 // pause for 2 seconds to let any new devices settle down
456 // rescan the USB and try to find the new device
458 m_new_results
= probe
.GetResults();
461 // now prompt the user... create a list of PIN display names
462 wxArrayString devices
;
463 for( Barry::Probe::Results::const_iterator i
= m_new_results
.begin();
464 i
!= m_new_results
.end(); ++i
)
466 devices
.Add(wxString(i
->GetDisplayName().c_str(), wxConvUTF8
));
469 m_status
->SetLabel(_W("User input..."));
473 // prompt the user with this list
474 int choice
= wxGetSingleChoiceIndex(_W("Please select the target device to write to:"),
475 _W("Destination PIN"), devices
, this);
482 // found the new PIN to use!
483 m_dest_device
= &m_new_results
[choice
];
485 // check if user needs to choose again
486 if( m_dest_device
->m_pin
== m_source_device
->m_pin
) {
487 wxMessageBox(_W("Cannot use the same device PIN as migration destination."),
488 _W("Invalid Device Selection"),
489 wxOK
| wxICON_ERROR
, this);
492 } while( m_dest_device
->m_pin
== m_source_device
->m_pin
);
495 void MigrateDlg::OnSetStatusMsg(wxCommandEvent
&event
)
497 if( event
.GetString().size() ) {
498 m_status
->SetLabel(event
.GetString());
501 if( (unsigned int)event
.GetInt() != 0xffff ) {
502 unsigned int value
= (unsigned int) event
.GetInt();
503 unsigned int pos
= (value
& 0xff00) >> 8;
504 unsigned int max
= (value
& 0xff);
507 m_progress
->SetValue(pos
);
511 m_progress
->SetRange(max
);
516 void MigrateDlg::OnPromptPassword(wxCommandEvent
&event
)
518 ScopeSignaler
done(m_waiter
);
520 // create prompt based on exception data
521 wxString prompt
= wxString::Format(
522 _W("Please enter device password: (%d tries remaining)"),
525 // ask user for device password
526 m_password
= wxGetPasswordFromUser(prompt
,
527 _W("Device Password"), _T(""), this);
530 void MigrateDlg::OnErrorMsg(wxCommandEvent
&event
)
532 ScopeSignaler
done(m_waiter
);
533 wxMessageBox(event
.GetString(), _W("Migration Error"),
534 wxOK
| wxICON_ERROR
, this);
537 void* MigrateDlg::MigrateThread(void *arg
)
539 MigrateDlg
*us
= (MigrateDlg
*) arg
;
542 us
->m_thread_running
= true;
548 // make sure we have a destination PIN
549 if( !us
->m_abort_flag
)
552 // restore to dest PIN
553 if( !us
->m_abort_flag
)
556 catch( std::exception
&e
) {
557 us
->SendErrorMsgEvent(wxString(e
.what(), wxConvUTF8
));
561 // invalidate the device selection pointers, since
562 // m_new_results may not always exist
563 us
->m_source_device
= us
->m_dest_device
= 0;
566 us
->m_thread_running
= false;
568 // send event to let main GUI thread we're finished
569 us
->SendEvent(MET_THREAD_FINISHED
);
574 class PeekParser
: public Barry::Parser
576 std::string m_dbname
;
579 virtual void ParseRecord(const Barry::DBData
&data
,
580 const Barry::IConverter
*ic
)
582 m_dbname
= data
.GetDBName();
585 const std::string
& GetDBName() const { return m_dbname
; }
589 // This is called from the thread
590 void MigrateDlg::BackupSource()
592 // connect to the source device
593 SendStatusEvent(_W("Connecting..."));
594 EventDesktopConnector
connect(this, "", "utf-8", *m_source_device
);
595 if( !connect
.Reconnect(2) ) {
601 // fetch DBDB, for list of databases to backup... back them all up
602 // remember to save this DBDB into the class, so it is available
603 // for the restore stage
604 m_source_dbdb
= connect
.GetDesktop().GetDBDB();
605 unsigned int total
= m_source_dbdb
.GetTotalRecordCount();
607 // create DeviceBuilder for fetching all
608 Barry::DeviceBuilder
builder(connect
.GetDesktop());
609 builder
.Add(m_source_dbdb
);
611 // calculate the default backup path location, based on user name
612 // (see backup GUI for code?)
613 Barry::ConfigFile
cfg(m_source_device
->m_pin
);
614 Barry::ConfigFile::CheckPath(cfg
.GetPath());
615 m_backup_tarfile
= cfg
.GetPath() + "/" +
616 Barry::MakeBackupFilename(m_source_device
->m_pin
, "migrate");
618 // and create tarball output parser
619 Barry::Backup
parser(m_backup_tarfile
);
622 Barry::Pipe
pipe(builder
);
626 // create tee parser, so we can see what's being written
627 Barry::TeeParser tee
;
632 unsigned int count
= 0;
633 SendStatusEvent(_T(""), 0, 100);
635 // cycle through all databases
636 while( !builder
.EndOfFile() ) {
637 if( !pipe
.PumpEntry(tee
) )
642 // ok, first entry has been pumped, so we can use
643 // peeker to create the status message, and only once
645 oss
<< _C("Backing up database: ")
646 << peeker
.GetDBName()
649 // calculate 1 to 100 percentage, based on number of
650 // databases being backed up, and update status bar too
651 int percent
= count
/ (float)total
* 100.0;
653 // send status event once
654 SendStatusEvent(wxString(oss
.str().c_str(),wxConvUTF8
), percent
);
656 // backup the rest of the database, but don't update status
659 while( pipe
.PumpEntry(tee
) ) {
663 int percent
= count
/ (float)total
* 100.0;
664 SendStatusEvent(_T(""), percent
);
668 // on each record (pump cycle?), as often as possible,
669 // check the m_abort_flag, and abort if necessary,
670 // updating the status message
672 SendStatusEvent(_W("Backup aborted by user..."));
678 // close all files, etc.
682 // This is called from the thread
683 void MigrateDlg::CheckDestPin()
685 SendEvent(MET_CHECK_DEST_PIN
);
689 // This is called from the thread
690 void MigrateDlg::RestoreToDest()
692 // connect to the dest device
693 SendStatusEvent(_W("Connecting to target..."));
694 EventDesktopConnector
connect(this, "", "utf-8", *m_dest_device
);
695 if( !connect
.Reconnect(2) ) {
701 // fetch DBDB of dest device, for list of databases we can restore
702 // to... compare with the backup DBDB to create a list of similarly
703 // named databases which we can restore....
704 Barry::DatabaseDatabase dest_dbdb
= connect
.GetDesktop().GetDBDB();
706 // create the restore builder object, to read from tarball
707 Barry::Restore
builder(m_backup_tarfile
);
709 // add all database names from the _dest_ DBDB to the Restore
710 // filter... this way, only databases that exist on the new
711 // device will get restored
712 builder
.Add(dest_dbdb
);
713 unsigned int total
= builder
.GetRecordTotal();// counts filtered records
715 // create device parser, to write to device
716 Barry::DeviceParser
parser(connect
.GetDesktop(), m_write_mode
);
719 Barry::Pipe
pipe(builder
);
722 unsigned int count
= 0;
723 SendStatusEvent(_T(""), 0, 100);
725 // cycle through all databases
726 Barry::DBData meta
; // record meta data of next tar record
727 while( !builder
.EndOfFile() ) try {
728 if( !builder
.GetNextMeta(meta
) )
731 // ok, first entry has been pumped, so we can use
732 // peeker to create the status message, and only once
734 oss
<< _C("Writing database: ")
738 // for debugging purposes in the field, display the names
739 // of the databases we restore
740 cerr
<< oss
.str() << endl
;
742 // calculate 1 to 100 percentage, based on number of
743 // databases being backed up, and update status bar too
744 int percent
= count
/ (float)total
* 100.0;
746 // send status event once
747 SendStatusEvent(wxString(oss
.str().c_str(),wxConvUTF8
), percent
);
749 // restore the rest of the database, but don't update status
752 while( pipe
.PumpEntry(parser
) ) {
756 int percent
= count
/ (float)total
* 100.0;
757 SendStatusEvent(_T(""), percent
);
761 // on each record (pump cycle?), as often as possible,
762 // check the m_abort_flag, and abort if necessary,
763 // updating the status message
765 SendStatusEvent(_W("Restore aborted by user..."));
770 catch( Barry::ReturnCodeError
&e
) {
771 cerr
<< _C("Unable to clear or write to database: ")
773 << ": " << e
.what() << endl
;
774 cerr
<< _C("Continuing to process remaining records...") << endl
;
776 // skip the problematic database, and keep on trying
777 builder
.SkipCurrentDB();