desktop: implemented device connect and password prompt in MigrateDlg
[barry/progweb.git] / desktop / src / MigrateDlg.cc
blob86523a5858c377afdd2583046750efb16ec181d3
1 ///
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.
5 ///
7 /*
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"
25 #include "configui.h"
26 #include "barrydesktop.h"
27 #include <string>
28 #include <wx/statline.h>
30 using namespace std;
31 using namespace OpenSync;
33 DEFINE_EVENT_TYPE(MET_THREAD_FINISHED)
34 DEFINE_EVENT_TYPE(MET_CHECK_DEST_PIN)
35 DEFINE_EVENT_TYPE(MET_SET_STATUS_MSG)
36 DEFINE_EVENT_TYPE(MET_PROMPT_PASSWORD)
37 DEFINE_EVENT_TYPE(MET_ERROR_MSG)
39 BEGIN_EVENT_TABLE(MigrateDlg, wxDialog)
40 EVT_BUTTON (Dialog_Migrate_MigrateNowButton,
41 MigrateDlg::OnMigrateNow)
42 EVT_BUTTON (Dialog_Migrate_CancelButton,
43 MigrateDlg::OnCancel)
44 EVT_CLOSE (MigrateDlg::OnCloseWindow)
45 EVT_COMMAND (wxID_ANY, MET_THREAD_FINISHED,
46 MigrateDlg::OnThreadFinished)
47 EVT_COMMAND (wxID_ANY, MET_CHECK_DEST_PIN,
48 MigrateDlg::OnCheckDestPin)
49 EVT_COMMAND (wxID_ANY, MET_SET_STATUS_MSG,
50 MigrateDlg::OnSetStatusMsg)
51 EVT_COMMAND (wxID_ANY, MET_PROMPT_PASSWORD,
52 MigrateDlg::OnPromptPassword)
53 EVT_COMMAND (wxID_ANY, MET_ERROR_MSG,
54 MigrateDlg::OnErrorMsg)
55 END_EVENT_TABLE()
58 class EventDesktopConnector : public Barry::DesktopConnector
60 MigrateDlg *m_dlg;
62 public:
63 EventDesktopConnector(MigrateDlg *dlg, const char *password,
64 const std::string &locale, const Barry::ProbeResult &result)
65 : Barry::DesktopConnector(password, locale, result)
66 , m_dlg(dlg)
70 virtual bool PasswordPrompt(const Barry::BadPassword &bp,
71 std::string &password_result);
74 bool EventDesktopConnector::PasswordPrompt(const Barry::BadPassword &bp,
75 std::string &password_result)
77 // ping the parent and wait for finish
78 wxCommandEvent event(MET_PROMPT_PASSWORD, wxID_ANY);
79 event.SetEventObject(m_dlg);
80 event.SetInt(bp.remaining_tries());
81 m_dlg->AddPendingEvent(event);
82 m_dlg->WaitForEvent();
84 password_result = m_dlg->GetPassword().utf8_str();
86 // assume that a blank password means the user wishes to quit...
87 // wxWidgets doesn't seem to handle this very well?
88 return password_result.size() > 0;
92 //////////////////////////////////////////////////////////////////////////////
93 // MigrateDlg class
95 MigrateDlg::MigrateDlg(wxWindow *parent,
96 const Barry::Probe::Results &results,
97 int current_device_index)
98 : wxDialog(parent, Dialog_GroupCfg, _T("Migrate Device"))
99 , m_results(results)
100 , m_current_device_index(current_device_index)
101 , m_migrate_thread_created(false)
102 , m_migrate_thread(NULL)
103 , m_abort_flag(false)
104 , m_thread_running(false)
105 , m_source_device(0)
106 , m_dest_device(0)
107 , m_topsizer(0)
108 , m_source_combo(0)
109 , m_dest_combo(0)
110 , m_write_mode_combo(0)
111 , m_migrate_button(0)
112 , m_wipe_check(0)
113 , m_status(0)
114 , m_progress(0)
116 // setup the raw GUI
117 CreateLayout();
120 void MigrateDlg::WaitForEvent()
122 m_waiter.Wait();
125 void MigrateDlg::CreateLayout()
127 m_topsizer = new wxBoxSizer(wxVERTICAL);
129 // AddDescriptionSizer(m_topsizer);
130 AddMainSizer(m_topsizer);
131 AddStatusSizer(m_topsizer);
133 SetSizer(m_topsizer);
134 m_topsizer->SetSizeHints(this);
135 m_topsizer->Layout();
138 void MigrateDlg::AddDescriptionSizer(wxSizer *sizer)
140 sizer->Add(
141 new wxStaticText(this, wxID_ANY,
142 _T("Migrate device data from source device to target device.")),
143 0, wxALIGN_CENTRE | wxALIGN_CENTRE_VERTICAL |
144 wxTOP | wxLEFT | wxRIGHT, 10);
147 void MigrateDlg::AddMainSizer(wxSizer *sizer)
149 // add 3 main sections together into one sizer
150 wxSizer *main = new wxBoxSizer(wxHORIZONTAL);
151 Main_AddSourceSizer(main);
152 Main_AddButtonSizer(main);
153 Main_AddDestSizer(main);
155 // add main sizer to top level sizer
156 sizer->Add(main, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
159 void MigrateDlg::AddStatusSizer(wxSizer *sizer)
161 sizer->Add( new wxStaticLine(this),
162 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
164 sizer->Add(
165 m_status = new wxStaticText(this, wxID_ANY, _T("Ready...")),
166 0, wxEXPAND | wxLEFT | wxRIGHT, 10);
167 // Reduce font size for status text
168 wxFont font = m_status->GetFont();
169 font.SetPointSize(font.GetPointSize() - 1);
170 m_status->SetFont( font );
172 sizer->Add(
173 m_progress = new wxGauge(this, wxID_ANY, 100),
174 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
177 void MigrateDlg::Main_AddSourceSizer(wxSizer *sizer)
179 wxArrayString devices;
180 for( Barry::Probe::Results::const_iterator i = m_results.begin();
181 i != m_results.end(); ++i )
183 devices.Add(wxString(i->GetDisplayName().c_str(), wxConvUTF8));
186 wxSizer *source = new wxStaticBoxSizer(
187 new wxStaticBox(this, wxID_ANY, _T("Source device")),
188 wxVERTICAL
190 source->Add(
191 m_source_combo = new wxChoice(this, wxID_ANY,
192 wxDefaultPosition, wxSize(225, -1), devices),
193 0, wxALL, 5);
195 if( m_current_device_index >= 0 )
196 m_source_combo->SetSelection(m_current_device_index);
198 sizer->Add(source, 0, wxEXPAND, 0);
201 void MigrateDlg::Main_AddButtonSizer(wxSizer *sizer)
203 wxSizer *buttons = new wxBoxSizer(wxVERTICAL);
204 buttons->Add( m_migrate_button = new wxButton(this,
205 Dialog_Migrate_MigrateNowButton, _T("Migrate Now")),
206 0, wxALIGN_CENTRE, 0);
207 m_migrate_button->SetDefault();
208 buttons->AddSpacer(10);
209 buttons->Add( new wxButton(this, Dialog_Migrate_CancelButton,
210 _T("Cancel")),
211 0, wxALIGN_CENTRE, 0);
213 sizer->Add(buttons, 1, wxALIGN_CENTRE | wxLEFT | wxRIGHT, 10);
216 void MigrateDlg::Main_AddDestSizer(wxSizer *sizer)
218 wxArrayString devices;
219 devices.Add(_T("Prompt to plug in later..."));
220 for( Barry::Probe::Results::const_iterator i = m_results.begin();
221 i != m_results.end(); ++i )
223 devices.Add(wxString(i->GetDisplayName().c_str(), wxConvUTF8));
226 wxSizer *dest = new wxStaticBoxSizer(
227 new wxStaticBox(this, wxID_ANY, _T("Destination device")),
228 wxVERTICAL
230 dest->Add(
231 m_dest_combo = new wxChoice(this, wxID_ANY,
232 wxDefaultPosition, wxSize(225, -1), devices),
233 0, wxALL, 5);
234 m_dest_combo->SetSelection(0);
237 wxArrayString write_modes;
238 write_modes.Add(_T("Erase all, then restore"));
239 write_modes.Add(_T("Add new, and overwrite existing"));
240 write_modes.Add(_T("Add only, don't overwrite existing"));
241 write_modes.Add(_T("Add every record as a new entry (may cause duplicates)"));
243 dest->Add( new wxStaticText(this, wxID_ANY, _T("Write Mode:")),
244 0, wxTOP | wxLEFT | wxRIGHT, 5);
245 dest->Add( m_write_mode_combo = new wxChoice(this, wxID_ANY,
246 wxDefaultPosition, wxSize(225, -1), write_modes),
247 0, wxALL, 5);
248 m_write_mode_combo->SetSelection(0);
250 // dest->Add( m_wipe_check = wxCheckBox(maybe a checkbox for "wipe device before restore"));
252 sizer->Add(dest, 0, wxEXPAND, 0);
255 void MigrateDlg::EnableControls(bool enable)
257 m_source_combo->Enable(enable);
258 m_dest_combo->Enable(enable);
259 m_write_mode_combo->Enable(enable);
260 m_migrate_button->Enable(enable);
261 //m_wipe_check->Enable(enable);
264 void MigrateDlg::DoSafeClose()
266 // if migrate thread is running, try to close it down first...
267 // do not exit the dialog until the thread is properly stopped!
268 if( m_thread_running ) {
269 m_abort_flag = true;
271 m_status->SetLabel(_T("Waiting for thread to close..."));
273 if( m_migrate_thread_created ) {
274 void *junk;
275 pthread_join(m_migrate_thread, &junk);
276 m_migrate_thread_created = false;
280 // all activity is stopped, so close dialog
281 EndModal(wxID_CANCEL);
284 void MigrateDlg::SendEvent(int event_type)
286 wxCommandEvent event(event_type, wxID_ANY);
287 event.SetEventObject(this);
288 AddPendingEvent(event);
291 void MigrateDlg::SendStatusEvent(const wxString &msg, int pos, int max)
293 wxCommandEvent event(MET_SET_STATUS_MSG, wxID_ANY);
294 event.SetEventObject(this);
296 event.SetString(msg);
298 int value = 0;
299 if( pos == -1 )
300 value |= 0xff00;
301 else
302 value |= (pos << 8);
304 if( max == -1 )
305 value |= 0xff;
306 else
307 value |= max & 0xff;
308 event.SetInt(value);
310 AddPendingEvent(event);
313 void MigrateDlg::SendErrorMsgEvent(const wxString &msg)
315 wxCommandEvent event(MET_ERROR_MSG, wxID_ANY);
316 event.SetEventObject(this);
317 event.SetString(msg);
318 AddPendingEvent(event);
321 void MigrateDlg::OnMigrateNow(wxCommandEvent &event)
323 // gather info from dialog
324 int source_index = m_source_combo->GetSelection();
325 int dest_index = m_dest_combo->GetSelection();
326 int write_mode_index = m_write_mode_combo->GetSelection();
328 // validate options
329 if( source_index == wxNOT_FOUND || dest_index == wxNOT_FOUND ||
330 write_mode_index == wxNOT_FOUND )
332 wxMessageBox(_T("Please select a source and destination device, as well as the write mode."),
333 _T("Migration Options Needed"), wxOK | wxICON_ERROR);
334 return;
337 // do not migrate from one PIN to the same PIN
338 if( source_index == (dest_index - 1) ) {
339 wxMessageBox(_T("Cannot migrate from and to the same PIN."),
340 _T("Migration Options Error"), wxOK | wxICON_ERROR);
341 return;
344 // set the migration arguments
345 m_source_device = &m_results[source_index];
346 if( dest_index > 0 ) {
347 m_dest_device = &m_results[dest_index-1];
349 else {
350 m_dest_device = 0; // an invalid dest causes a prompt
352 switch( write_mode_index )
354 case 0:
355 m_write_mode = Barry::DeviceParser::ERASE_ALL_WRITE_ALL;
356 break;
357 case 1:
358 m_write_mode = Barry::DeviceParser::INDIVIDUAL_OVERWRITE;
359 break;
360 case 2:
361 m_write_mode = Barry::DeviceParser::ADD_BUT_NO_OVERWRITE;
362 break;
363 case 3:
364 m_write_mode = Barry::DeviceParser::ADD_WITH_NEW_ID;
365 break;
366 default:
367 wxMessageBox(_T("Invalid write mode. This should never happen. Contact the developers."),
368 _T("Internal Logic Error"), wxOK | wxICON_ERROR);
369 return;
372 // disable all buttons and controls except cancel
373 EnableControls(false);
375 // turn off the stop flag
376 m_abort_flag = false;
377 m_thread_running = false;
379 // fire up migrate thread, and let the thread close the dialog when
380 // done (can we EndModal() from a thread?)
381 int ret = pthread_create(&m_migrate_thread, NULL,
382 &MigrateDlg::MigrateThread, this);
383 if( ret != 0 ) {
384 // go back to normal
385 EnableControls(true);
386 return;
388 else {
389 m_migrate_thread_created = true;
392 // thread started... let it finish
395 void MigrateDlg::OnCancel(wxCommandEvent &event)
397 DoSafeClose();
400 void MigrateDlg::OnCloseWindow(wxCloseEvent &event)
402 DoSafeClose();
405 void MigrateDlg::OnThreadFinished(wxCommandEvent &event)
407 if( m_migrate_thread_created ) {
408 m_status->SetLabel(_T("Waiting for thread..."));
409 void *junk;
410 pthread_join(m_migrate_thread, &junk);
411 m_migrate_thread_created = false;
414 if( m_abort_flag ) {
415 // user cancelled in some way, restore GUI
416 EnableControls(true);
417 m_status->SetLabel(_T("Cancelled by user..."));
419 else {
420 // if we were not aborted, then this is success, and we
421 // can close the original dialog
422 EndModal(wxID_CANCEL);
426 void MigrateDlg::OnCheckDestPin(wxCommandEvent &event)
428 ScopeSignaler done(m_waiter);
430 if( m_dest_device && m_dest_device->m_pin.Valid() )
431 return; // nothing to do
433 // no destination pin was available, so user may need to plugin
434 // the new device right now, before continuing
435 int response = wxMessageBox(_T("Please plug in the target device now."),
436 _T("Ready for Writing"), wxOK | wxCANCEL | wxICON_INFORMATION,
437 this);
438 if( response != wxOK ) {
439 // user cancelled
440 m_abort_flag = true;
441 return;
445 wxBusyCursor wait;
446 m_status->SetLabel(_T("Scanning USB for devices..."));
448 // pause for 2 seconds to let any new devices settle down
449 wxGetApp().Yield();
450 wxSleep(2);
452 // rescan the USB and try to find the new device
453 Barry::Probe probe;
454 m_new_results = probe.GetResults();
457 // now prompt the user... create a list of PIN display names
458 wxArrayString devices;
459 for( Barry::Probe::Results::const_iterator i = m_new_results.begin();
460 i != m_new_results.end(); ++i )
462 devices.Add(wxString(i->GetDisplayName().c_str(), wxConvUTF8));
465 m_status->SetLabel(_T("User input..."));
467 do {
469 // prompt the user with this list
470 int choice = wxGetSingleChoiceIndex(_T("Please select the target device to write to:"),
471 _T("Destination PIN"), devices, this);
472 if( choice == -1 ) {
473 // user cancelled
474 m_abort_flag = true;
475 return;
478 // found the new PIN to use!
479 m_dest_device = &m_new_results[choice];
481 // check if user needs to choose again
482 if( m_dest_device->m_pin == m_source_device->m_pin ) {
483 wxMessageBox(_T("Cannot use the same device PIN as migration destination."),
484 _T("Invalid Device Selection"),
485 wxOK | wxICON_ERROR, this);
488 } while( m_dest_device->m_pin == m_source_device->m_pin );
491 void MigrateDlg::OnSetStatusMsg(wxCommandEvent &event)
493 if( event.GetString().size() ) {
494 m_status->SetLabel(event.GetString());
497 if( (unsigned int)event.GetInt() != 0xffff ) {
498 unsigned int value = (unsigned int) event.GetInt();
499 unsigned int pos = (value & 0xff00) >> 8;
500 unsigned int max = (value & 0xff);
502 if( pos != 0xff ) {
503 m_progress->SetValue(pos);
506 if( max != 0xff ) {
507 m_progress->SetRange(max);
512 void MigrateDlg::OnPromptPassword(wxCommandEvent &event)
514 ScopeSignaler done(m_waiter);
516 // create prompt based on exception data
517 ostringstream oss;
518 oss << "Please enter device password: ("
519 << event.GetInt()
520 << " tries remaining)";
521 wxString prompt(oss.str().c_str(), wxConvUTF8);
523 // ask user for device password
524 m_password = wxGetPasswordFromUser(prompt,
525 _T("Device Password"), _T(""), this);
528 void MigrateDlg::OnErrorMsg(wxCommandEvent &event)
530 ScopeSignaler done(m_waiter);
531 wxMessageBox(event.GetString(), _T("Migration Error"),
532 wxOK | wxICON_ERROR, this);
535 void* MigrateDlg::MigrateThread(void *arg)
537 MigrateDlg *us = (MigrateDlg*) arg;
539 // we are running!
540 us->m_thread_running = true;
542 try {
543 // backup source PIN
544 us->BackupSource();
546 // make sure we have a destination PIN
547 if( !us->m_abort_flag )
548 us->CheckDestPin();
550 // restore to dest PIN
551 if( !us->m_abort_flag )
552 us->RestoreToDest();
554 catch( std::exception &e ) {
555 us->SendErrorMsgEvent(wxString(e.what(), wxConvUTF8));
556 us->m_waiter.Wait();
559 // invalidate the device selection pointers, since
560 // m_new_results may not always exist
561 us->m_source_device = us->m_dest_device = 0;
563 // we are done!
564 us->m_thread_running = false;
566 // send event to let main GUI thread we're finished
567 us->SendEvent(MET_THREAD_FINISHED);
569 return 0;
572 // This is called from the thread
573 void MigrateDlg::BackupSource()
575 // connect to the source device
576 SendStatusEvent(_T("Connecting..."));
577 EventDesktopConnector connect(this, "", "utf-8", *m_source_device);
578 if( !connect.Reconnect(2) ) {
579 // user cancelled
580 m_abort_flag = true;
581 return;
584 // calculate the default backup path location, based on user name
585 // (see backup GUI for code?)
587 // fetch DBDB, for list of databases to backup... back them all up
588 // remember to save this DBDB into the class, so it is available
589 // for the restore stage
591 // cycle through all databases
592 // status message "Backing up database: XXXXX..."
593 // calculate 1 to 100 percentage, based on number of
594 // databases being backed up, and update status bar too
596 // backup this database
597 // on each record (pump cycle?), as often as possible,
598 // check the m_abort_flag, and abort if necessary,
599 // updating the status message
601 // close all files, etc.
602 // save backup filename, for restore stage
605 // This is called from the thread
606 void MigrateDlg::CheckDestPin()
608 SendEvent(MET_CHECK_DEST_PIN);
609 m_waiter.Wait();
612 // This is called from the thread
613 void MigrateDlg::RestoreToDest()
615 // connect to the dest device
617 // fetch DBDB of dest device, for list of databases we can restore
618 // to... compare with the backup DBDB to create a list of similarly
619 // named databases which we can restore....
621 // cycle through all databases
622 // status message "Writing database: XXXXX..."
623 // calculate 1 to 100 percentage, based on number of
624 // databases being restored up, and update status bar too
626 // restore this database
627 // on each record (pump cycle?), as often as possible,
628 // check the m_abort_flag, and abort if necessary,
629 // updating the status message
631 // close all files, etc.