gui: added user-friendly error messages for Probe failures
[barry/progweb.git] / gui / src / BackupWindow.cc
blobdb631244112a36ce98cef987db2d9e1d328e28a1
1 ///
2 /// \file BackupWindow.cc
3 /// GUI window class
4 ///
6 /*
7 Copyright (C) 2007-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 "BackupWindow.h"
23 #include "PasswordDlg.h"
24 #include "PromptDlg.h"
25 #include "ConfigDlg.h"
26 #include "util.h"
27 #include "i18n.h"
28 #include <gtkmm/aboutdialog.h>
29 #include <iostream>
30 #include <iomanip>
31 #include <sstream>
32 #include <string>
33 #include <unistd.h>
35 BackupWindow::BackupWindow(BaseObjectType *cobject,
36 const Glib::RefPtr<Gnome::Glade::Xml> &xml)
37 : Gtk::Window(cobject)
38 , m_xml(xml)
39 , m_pStatusbar(0)
40 , m_pBackupButton(0)
41 , m_pRestoreButton(0)
42 , m_pDisconnectButton(0)
43 , m_pReloadButton(0)
44 , m_pDeviceLabel(0)
45 , m_pDeviceList(0)
46 , m_device_count(0)
47 , m_pActive(0)
48 , m_scanned(false)
49 , m_last_status_id(0)
51 m_signal_update.connect(
52 sigc::mem_fun(*this, &BackupWindow::treeview_update));
54 // setup menu signals
55 Gtk::MenuItem *pItem = 0;
56 m_xml->get_widget("menu_file_quit", pItem);
57 pItem->signal_activate().connect(
58 sigc::mem_fun(*this, &BackupWindow::on_file_quit));
60 m_xml->get_widget("menu_help_about", pItem);
61 pItem->signal_activate().connect(
62 sigc::mem_fun(*this, &BackupWindow::on_help_about));
64 // get various widget pointers we will use later
65 m_xml->get_widget("DeviceLabel", m_pDeviceLabel);
66 m_xml->get_widget("DeviceList", m_pDeviceList);
67 m_xml->get_widget("BackupButton", m_pBackupButton);
68 m_xml->get_widget("RestoreButton", m_pRestoreButton);
69 m_xml->get_widget("ConfigButton", m_pConfigButton);
70 m_xml->get_widget("DisconnectButton", m_pDisconnectButton);
71 m_xml->get_widget("DisconnectAllButton", m_pDisconnectAllButton);
72 m_xml->get_widget("ReloadButton", m_pReloadButton);
73 m_xml->get_widget("Statusbar", m_pStatusbar);
75 // set up device list
76 m_pListStore = Gtk::ListStore::create(m_columns);
77 m_pDeviceList->set_model(m_pListStore);
79 m_pDeviceList->append_column(_("PIN"), m_columns.m_pin);
80 m_pDeviceList->append_column(_("Name"), m_columns.m_name);
81 m_pDeviceList->append_column(_("Status"), m_columns.m_status);
82 Gtk::CellRendererProgress* cell = new Gtk::CellRendererProgress;
83 m_pDeviceList->append_column(_("Progress"), *cell);
84 Gtk::TreeViewColumn* pColumn = m_pDeviceList->get_column(3);
85 pColumn->add_attribute(cell->property_value(), m_columns.m_percentage);
87 m_pDeviceList->get_column(0)->set_min_width(60);
88 m_pDeviceList->get_column(1)->set_min_width(100);
89 m_pDeviceList->get_column(2)->set_min_width(75);
91 for( unsigned int i = 0; i < 4; ++i )
92 m_pDeviceList->get_column(i)->set_resizable();
94 // set up device list selection
95 m_pDeviceSelection = m_pDeviceList->get_selection();
97 // setup widget signals
98 m_pBackupButton->signal_clicked().connect(
99 sigc::mem_fun(*this, &BackupWindow::on_backup));
100 m_pRestoreButton->signal_clicked().connect(
101 sigc::mem_fun(*this, &BackupWindow::on_restore));
102 m_pConfigButton->signal_clicked().connect(
103 sigc::mem_fun(*this, &BackupWindow::on_config));
104 m_pDisconnectButton->signal_clicked().connect(
105 sigc::mem_fun(*this, &BackupWindow::on_disconnect));
106 m_pDisconnectAllButton->signal_clicked().connect(
107 sigc::mem_fun(*this, &BackupWindow::on_disconnect_all));
108 m_pReloadButton->signal_clicked().connect(
109 sigc::mem_fun(*this, &BackupWindow::on_reload));
110 m_pDeviceSelection->signal_changed().connect(
111 sigc::mem_fun(*this, &BackupWindow::on_device_change));
113 // setup startup device scan
114 Glib::signal_timeout().connect(
115 sigc::mem_fun(*this, &BackupWindow::on_startup), 500);
117 // workaround: normally this should say "Ready" but since
118 // the initial Scan() happens right away, and the statusbar
119 // doesn't seem to update the screen until the handler is
120 // finished, we update the status bar here instead
121 StatusbarSet(_("Scanning for devices..."));
123 // do this last so that any exceptions in the constructor
124 // won't cause a connected signal handler to a non-object
125 // (i.e. ~BackupWindow() won't get called if constructor throws)
126 m_signal_handler_connection = Glib::add_exception_handler(
127 sigc::mem_fun(*this, &BackupWindow::signal_exception_handler) );
130 BackupWindow::~BackupWindow()
132 // disconnect the signal, as we're going out of business
133 m_signal_handler_connection.disconnect();
136 void BackupWindow::Scan()
138 StatusbarSet(_("Scanning for devices..."));
140 m_pListStore->clear();
141 m_threads.clear();
143 m_bus.Probe();
145 // display any errors from the probe
146 if( m_bus.GetFailCount() ) {
147 std::ostringstream oss;
148 oss << _("There were some potential BlackBerry devices that could not be probed. Please check your system's USB permissions if this happens regularly.\n\n");
149 for( int i = 0; i < m_bus.GetFailCount(); i++ ) {
150 oss << m_bus.GetFailMsg(i) << "\n";
153 Gtk::MessageDialog msg(oss.str());
154 msg.run();
157 m_device_count = m_bus.ProbeCount();
159 if( m_device_count == 0 )
160 m_pDeviceLabel->set_label(_("No devices."));
161 else if( m_device_count == 1 )
162 m_pDeviceLabel->set_label(_("1 device:"));
163 else
165 std::ostringstream oss;
166 oss << m_device_count << _(" devices:");
167 m_pDeviceLabel->set_label(oss.str());
170 m_threads.resize(m_device_count);
171 for( unsigned int id = 0; id < m_device_count; ++id ) {
172 Device dev = m_bus.Get(id);
173 Gtk::TreeModel::iterator row = m_pListStore->append();
174 (*row)[m_columns.m_id] = id;
176 m_threads[id].reset(new Thread(dev, &m_signal_update));
179 // all devices loaded
180 m_scanned = true;
182 StatusbarSet(_("All devices loaded."));
184 // if one or more device plugged in,
185 // activate the first one
186 Gtk::TreeModel::iterator iter = m_pListStore->children().begin();
187 if( iter )
188 m_pDeviceSelection->select(iter);
191 bool BackupWindow::Connect(Thread *thread)
193 if( thread->Connected() )
194 return true;
196 StatusbarSet(_("Connecting to Device..."));
197 static int tries(0);
199 CheckDeviceName(thread);
201 if( !thread->Connect() ) {
202 if( thread->PasswordRequired() ) {
203 bool connected = false;
204 while( !connected && !thread->PasswordOutOfTries() ) {
205 PasswordDlg dlg(thread->PasswordRemainingTries());
206 if( dlg.run() == Gtk::RESPONSE_OK ) {
207 connected = thread->Connect(dlg.GetPassword());
208 if( !connected ) {
209 // low level error
210 Gtk::MessageDialog msg(thread->LastInterfaceError());
211 msg.run();
212 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
213 return false;
216 else { // user cancelled
217 thread->Reset();
218 StatusbarSet(_("Connection cancelled."));
219 return false;
222 if( thread->PasswordOutOfTries() )
224 Gtk::MessageDialog msg(thread->BadPasswordError());
225 msg.run();
226 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
227 return false;
230 else if( thread->BadSize() ) {
231 ++tries;
232 if( tries < 3 ) {
233 std::cerr << thread->BadSizeError() << std::endl;
234 thread->Reset();
235 sleep(2);
236 return Connect(thread);
238 else {
239 Gtk::MessageDialog msg(thread->BadSizeError());
240 msg.run();
241 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
242 return false;
245 else {
246 Gtk::MessageDialog msg(thread->LastInterfaceError());
247 msg.run();
248 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
249 return false;
252 tries = 0;
253 StatusbarSet(_("Connected to ") + thread->GetFullname() + ".");
254 return true;
257 void BackupWindow::Disconnect(Thread *thread)
259 if( thread->Working() ) {
260 Gtk::MessageDialog dialog(*this, thread->GetFullname() + _(" is working, "
261 "disconnecting from it may cause data corruption, are you sure to proceed?"),
262 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
263 if( dialog.run() == Gtk::RESPONSE_CANCEL )
264 return;
267 if( thread->Connected() ) {
268 thread->Disconnect();
269 StatusbarSet(_("Disconnected from ") + thread->GetFullname() + ".");
271 else {
272 StatusbarSet(_("Not connected."));
276 void BackupWindow::CheckDeviceName(Thread *thread)
278 if( !thread->HasDeviceName() ) {
279 PromptDlg dlg;
280 dlg.SetPrompt(_("Unnamed device found (PIN: ") + thread->GetPIN().Str() + ").\r\n " + _("Please enter a name for it:"));
281 if( dlg.run() == Gtk::RESPONSE_OK ) {
282 thread->SetDeviceName(dlg.GetAnswer());
284 else {
285 thread->SetDeviceName(" ");
287 if( !thread->Save() ) {
288 Gtk::MessageDialog msg(_("Error saving config: ") +
289 thread->LastConfigError());
290 msg.run();
295 Thread *BackupWindow::GetActive()
297 Gtk::TreeModel::iterator row = m_pDeviceSelection->get_selected();
298 if( row ) {
299 unsigned int id = (*row)[m_columns.m_id];
300 return m_threads[id].get();
302 else {
303 return 0;
307 void BackupWindow::StatusbarSet(const Glib::ustring& text)
309 guint remove_id = m_last_status_id;
310 if( m_pStatusbar ) {
311 m_last_status_id = m_pStatusbar->push(text);
312 if( remove_id )
313 m_pStatusbar->remove_message(remove_id);
317 /// Returns true if ok to proceed (either nothing is currently 'working',
318 /// or the user confirmed to do it anyway)
319 bool BackupWindow::CheckWorking()
321 bool working(false);
322 for( unsigned int i = 0; i < m_device_count; ++i) {
323 if( m_threads[i]->Working() ) {
324 working = true;
325 break;
329 if( working ) {
330 Gtk::MessageDialog dialog(*this, _("One or more devices are working, "
331 "disconnecting now may cause data corruption. "
332 "Proceed anyway?"),
333 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
334 if( dialog.run() != Gtk::RESPONSE_OK )
335 return false;
338 return true;
341 void BackupWindow::signal_exception_handler()
343 try {
344 throw;
346 catch( Glib::Exception &e ) {
347 // This usually just means a missing .glade file,
348 // so we try to carry on.
349 std::cerr << "Glib::Exception caught in main: " << std::endl;
350 std::cerr << e.what() << std::endl;
351 Gtk::MessageDialog msg(e.what());
352 msg.run();
354 catch( ... ) {
355 // anything else, terminate window and pass on to next handler
356 // (which should be in main.cc)
357 hide();
358 throw;
363 //////////////////////////////////////////////////////////////////////////////
364 // signal handlers
366 void BackupWindow::treeview_update()
368 for( Gtk::TreeModel::iterator i = m_pListStore->children().begin();
369 i != m_pListStore->children().end(); ++i ) {
370 unsigned int id = (*i)[m_columns.m_id];
371 Thread *thread = m_threads[id].get();
372 (*i)[m_columns.m_pin] = thread->GetPIN().Str();
373 (*i)[m_columns.m_name] = thread->GetDeviceName();
374 (*i)[m_columns.m_status] = thread->Status();
375 unsigned int finished(thread->GetRecordFinished()), total(thread->GetRecordTotal());
376 unsigned int percentage(0);
377 if( total == 0 || finished == total )
378 percentage = 100;
379 else {
380 percentage = 100 * finished / total;
381 if( percentage == 100 ) // never say 100% unless finished
382 percentage = 99;
384 (*i)[m_columns.m_percentage] = percentage;
385 if( thread->CheckFinishedMarker() ) {
386 std::string op;
388 if( thread->GetThreadState() & THREAD_STATE_BACKUP )
389 op = _("Backup on");
390 else if( thread->GetThreadState() & THREAD_STATE_RESTORE )
391 op = _("Restore on");
392 else
393 op = _("Operation on");
395 StatusbarSet(op + thread->GetFullname() + _(" finished!"));
397 // thread really finished, so force percentage to 100
398 (*i)[m_columns.m_percentage] = 100;
400 if( (thread->GetThreadState() & THREAD_STATE_BACKUP) &&
401 finished != total )
403 if( finished < total ) {
404 // in some cases, not all records are backed
405 // up, possibly due to international chars
406 // in the Blackberry data which somehow
407 // forces the device to use a different
408 // low level protocol... here we need to
409 // warn the user that not all data was
410 // included in the backup
411 std::ostringstream oss;
412 oss << _("Warning\n\nNot all records were processed on device: ") << thread->GetFullname()
413 << _("\n\nOnly ") << finished << _(" of ") << total
414 << _(" records were backed up.\n\nIt is suspected that due to international characters in these records, the BlackBerry uses a different low-level protocol, which Barry Backup does not yet support. Alternatively, there may be hidden records that cannot be edited in your device. If this occurs in a database such as Address Book, sometimes a restore of just that database may fix the issue.");
416 oss << _("\n\nSummary:\n");
417 std::vector<std::string> msgs = thread->CompareTotals();
418 for( std::vector<std::string>::const_iterator i = msgs.begin() ; i != msgs.end(); ++i )
419 oss << *i << "\n";
420 Gtk::MessageDialog msg(oss.str());
421 msg.run();
423 else if( finished > total ) {
424 std::ostringstream oss;
425 oss << _("Warning: Record counts are not the same on device: ") << thread->GetFullname() << _("\nIt claims to have ") << total << _(" records, but actually retrieved ") << finished << _("\n\nSummary:\n");
426 std::vector<std::string> msgs = thread->CompareTotals();
427 for( std::vector<std::string>::const_iterator i = msgs.begin() ; i != msgs.end(); ++i )
428 oss << *i << "\n";
429 Gtk::MessageDialog msg(oss.str());
430 msg.run();
437 void BackupWindow::on_backup()
439 Thread *thread = GetActive();
441 if( !thread || !Connect(thread) )
442 return;
444 // already working?
445 if( thread->Working() ) {
446 Gtk::MessageDialog msg(_("Thread already in progress."));
447 msg.run();
448 return;
451 // make sure our target directory exists
452 if( !Barry::ConfigFile::CheckPath(thread->GetPath()) ) {
453 Gtk::MessageDialog msg(_("Could not create directory: ") + thread->GetPath());
454 msg.run();
455 return;
458 // anything to do?
459 if( thread->GetBackupList().size() == 0 ) {
460 Gtk::MessageDialog msg(_("No databases selected in configuration."));
461 msg.run();
462 return;
465 // prompt for a backup label, if so configured
466 std::string backupLabel;
467 if( thread->PromptBackupLabel() ) {
468 PromptDlg dlg;
469 dlg.SetPrompt(_("Please enter a label for this backup (blank is ok):"));
470 if( dlg.run() == Gtk::RESPONSE_OK ) {
471 backupLabel = dlg.GetAnswer();
473 else {
474 // user cancelled
475 return;
479 // start the thread
480 if( !thread->Backup(backupLabel) ) {
481 Gtk::MessageDialog msg(_("Error starting backup thread: ") +
482 thread->LastInterfaceError());
483 msg.run();
485 else {
486 StatusbarSet(_("Backup of ") + thread->GetFullname() + _(" in progress..."));
490 bool BackupWindow::PromptForRestoreTarball(std::string &restoreFilename,
491 const std::string &start_path)
493 char buffer[PATH_MAX];
494 char *buf = getcwd(buffer, PATH_MAX);
496 // start at the base path given... if it fails, just open
497 // the dialog where we are
498 (void)chdir(start_path.c_str());
500 Gtk::FileChooserDialog dlg(*this, _("Select backup to restore from"));
501 dlg.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
502 dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
503 int result = dlg.run();
505 if( buf )
506 (void)chdir(buf);
508 if( result != Gtk::RESPONSE_OK )
509 return false;
511 restoreFilename = dlg.get_filename();
512 return true;
515 void BackupWindow::on_restore()
517 Thread *thread = GetActive();
519 if( !thread || !Connect(thread) )
520 return;
522 // already working?
523 if( thread->Working() ) {
524 Gtk::MessageDialog msg(_("Thread already in progress."));
525 msg.run();
526 return;
529 std::string restoreFilename;
530 if( !PromptForRestoreTarball(restoreFilename, thread->GetPath()) )
531 return; // nothing to do
533 // start the thread
534 if( !thread->Restore(restoreFilename) ) {
535 Gtk::MessageDialog msg(_("Error starting restore thread: ") +
536 thread->LastInterfaceError());
537 msg.run();
539 else {
540 StatusbarSet(_("Restore of ") + thread->GetFullname() + _(" in progress..."));
544 void BackupWindow::on_disconnect()
546 Thread *thread = GetActive();
547 if( thread )
548 Disconnect(thread);
551 void BackupWindow::on_disconnect_all()
553 for( unsigned int i = 0; i < m_device_count; ++i )
554 Disconnect(m_threads[i].get());
557 void BackupWindow::on_device_change()
559 Thread *thread = GetActive();
560 if( m_pActive )
561 m_pActive->UnsetActive();
562 m_pActive = thread;
563 if( thread && Connect(thread) )
564 thread->SetActive();
567 void BackupWindow::on_config()
569 Thread *thread = GetActive();
571 if( !thread || !Connect(thread) )
572 return;
574 thread->LoadConfig();
576 ConfigDlg dlg(thread->GetDBDB(), *thread);
577 if( dlg.run() == Gtk::RESPONSE_OK ) {
578 thread->SetBackupList(dlg.GetBackupList());
579 thread->SetRestoreList(dlg.GetRestoreList());
580 thread->SetDeviceName(dlg.GetDeviceName());
581 thread->SetBackupPath(dlg.GetBackupPath());
582 thread->SetPromptBackupLabel(dlg.GetPromptBackupLabel());
583 if( !thread->Save() )
584 StatusbarSet(_("Error saving config: ") +
585 thread->LastConfigError());
586 else
587 StatusbarSet(_("Config saved successfully."));
589 thread->LoadConfig();
592 void BackupWindow::on_reload()
594 if( CheckWorking() ) {
595 Scan();
599 void BackupWindow::on_file_quit()
601 if( CheckWorking() ) {
602 hide();
606 void BackupWindow::on_help_about()
608 Gtk::AboutDialog dlg;
609 dlg.set_copyright("Copyright (C) 2007-2012, Net Direct Inc.");
610 dlg.set_license(
611 " This program is free software; you can redistribute it and/or modify\n"
612 " it under the terms of the GNU General Public License as published by\n"
613 " the Free Software Foundation; either version 2 of the License, or\n"
614 " (at your option) any later version.\n"
615 "\n"
616 " This program is distributed in the hope that it will be useful,\n"
617 " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
618 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
619 "\n"
620 " See the GNU General Public License in the COPYING file at the\n"
621 " root directory of this project for more details.\n");
623 std::vector<std::string> authors;
624 authors.push_back("Chris Frey <cdfrey@foursquare.net>");
625 authors.push_back(_("and Barry contributors. See AUTHORS file"));
626 authors.push_back(_("for detailed contribution information."));
628 dlg.set_authors(authors);
630 int logical, major, minor;
631 const char *BarryVersion = Barry::Version(logical, major, minor);
632 dlg.set_name("Barry Backup");
633 dlg.set_version("0.18.0");
634 dlg.set_comments(std::string(_("Using library: ")) + BarryVersion);
635 dlg.set_website("http://netdirect.ca/barry");
636 dlg.run();
639 bool BackupWindow::on_startup()
641 Scan();
642 return false;
645 bool BackupWindow::on_delete_event(GdkEventAny *)
647 if( CheckWorking() )
648 return false; // allow closing of window via window manager
649 else
650 return true; // stop the close