Bumped copyright dates for 2013
[barry.git] / gui / src / BackupWindow.cc
blobae34919712904e9e4bb59da85c125106ab7e5f45
1 ///
2 /// \file BackupWindow.cc
3 /// GUI window class
4 ///
6 /*
7 Copyright (C) 2007-2013, 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 "config.h"
29 #include <gtkmm/aboutdialog.h>
30 #include <iostream>
31 #include <iomanip>
32 #include <sstream>
33 #include <string>
34 #include <unistd.h>
36 BackupWindow::BackupWindow(BaseObjectType *cobject,
37 const Glib::RefPtr<Gnome::Glade::Xml> &xml)
38 : Gtk::Window(cobject)
39 , m_xml(xml)
40 , m_pStatusbar(0)
41 , m_pBackupButton(0)
42 , m_pRestoreButton(0)
43 , m_pDisconnectButton(0)
44 , m_pReloadButton(0)
45 , m_pDeviceLabel(0)
46 , m_pDeviceList(0)
47 , m_device_count(0)
48 , m_pActive(0)
49 , m_scanned(false)
50 , m_last_status_id(0)
52 m_signal_update.connect(
53 sigc::mem_fun(*this, &BackupWindow::treeview_update));
55 // setup menu signals
56 Gtk::MenuItem *pItem = 0;
57 m_xml->get_widget("menu_file_quit", pItem);
58 pItem->signal_activate().connect(
59 sigc::mem_fun(*this, &BackupWindow::on_file_quit));
61 m_xml->get_widget("menu_help_about", pItem);
62 pItem->signal_activate().connect(
63 sigc::mem_fun(*this, &BackupWindow::on_help_about));
65 // get various widget pointers we will use later
66 m_xml->get_widget("DeviceLabel", m_pDeviceLabel);
67 m_xml->get_widget("DeviceList", m_pDeviceList);
68 m_xml->get_widget("BackupButton", m_pBackupButton);
69 m_xml->get_widget("RestoreButton", m_pRestoreButton);
70 m_xml->get_widget("ConfigButton", m_pConfigButton);
71 m_xml->get_widget("DisconnectButton", m_pDisconnectButton);
72 m_xml->get_widget("DisconnectAllButton", m_pDisconnectAllButton);
73 m_xml->get_widget("ReloadButton", m_pReloadButton);
74 m_xml->get_widget("Statusbar", m_pStatusbar);
76 // set up device list
77 m_pListStore = Gtk::ListStore::create(m_columns);
78 m_pDeviceList->set_model(m_pListStore);
80 m_pDeviceList->append_column(_("PIN"), m_columns.m_pin);
81 m_pDeviceList->append_column(_("Name"), m_columns.m_name);
82 m_pDeviceList->append_column(_("Status"), m_columns.m_status);
83 Gtk::CellRendererProgress* cell = new Gtk::CellRendererProgress;
84 m_pDeviceList->append_column(_("Progress"), *cell);
85 Gtk::TreeViewColumn* pColumn = m_pDeviceList->get_column(3);
86 pColumn->add_attribute(cell->property_value(), m_columns.m_percentage);
88 m_pDeviceList->get_column(0)->set_min_width(60);
89 m_pDeviceList->get_column(1)->set_min_width(100);
90 m_pDeviceList->get_column(2)->set_min_width(75);
92 for( unsigned int i = 0; i < 4; ++i )
93 m_pDeviceList->get_column(i)->set_resizable();
95 // set up device list selection
96 m_pDeviceSelection = m_pDeviceList->get_selection();
98 // setup widget signals
99 m_pBackupButton->signal_clicked().connect(
100 sigc::mem_fun(*this, &BackupWindow::on_backup));
101 m_pRestoreButton->signal_clicked().connect(
102 sigc::mem_fun(*this, &BackupWindow::on_restore));
103 m_pConfigButton->signal_clicked().connect(
104 sigc::mem_fun(*this, &BackupWindow::on_config));
105 m_pDisconnectButton->signal_clicked().connect(
106 sigc::mem_fun(*this, &BackupWindow::on_disconnect));
107 m_pDisconnectAllButton->signal_clicked().connect(
108 sigc::mem_fun(*this, &BackupWindow::on_disconnect_all));
109 m_pReloadButton->signal_clicked().connect(
110 sigc::mem_fun(*this, &BackupWindow::on_reload));
111 m_pDeviceSelection->signal_changed().connect(
112 sigc::mem_fun(*this, &BackupWindow::on_device_change));
114 // setup startup device scan
115 Glib::signal_timeout().connect(
116 sigc::mem_fun(*this, &BackupWindow::on_startup), 500);
118 // workaround: normally this should say "Ready" but since
119 // the initial Scan() happens right away, and the statusbar
120 // doesn't seem to update the screen until the handler is
121 // finished, we update the status bar here instead
122 StatusbarSet(_("Scanning for devices..."));
124 // do this last so that any exceptions in the constructor
125 // won't cause a connected signal handler to a non-object
126 // (i.e. ~BackupWindow() won't get called if constructor throws)
127 m_signal_handler_connection = Glib::add_exception_handler(
128 sigc::mem_fun(*this, &BackupWindow::signal_exception_handler) );
131 BackupWindow::~BackupWindow()
133 // disconnect the signal, as we're going out of business
134 m_signal_handler_connection.disconnect();
137 void BackupWindow::Scan()
139 StatusbarSet(_("Scanning for devices..."));
141 m_pListStore->clear();
142 m_threads.clear();
144 m_bus.Probe();
146 // display any errors from the probe
147 if( m_bus.GetFailCount() ) {
148 std::ostringstream oss;
149 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");
150 for( int i = 0; i < m_bus.GetFailCount(); i++ ) {
151 oss << m_bus.GetFailMsg(i) << "\n";
154 Gtk::MessageDialog msg(oss.str());
155 msg.run();
158 m_device_count = m_bus.ProbeCount();
160 if( m_device_count == 0 )
161 m_pDeviceLabel->set_label(_("No devices."));
162 else if( m_device_count == 1 )
163 m_pDeviceLabel->set_label(_("1 device:"));
164 else
166 std::ostringstream oss;
167 oss << m_device_count << _(" devices:");
168 m_pDeviceLabel->set_label(oss.str());
171 m_threads.resize(m_device_count);
172 for( unsigned int id = 0; id < m_device_count; ++id ) {
173 Device dev = m_bus.Get(id);
174 Gtk::TreeModel::iterator row = m_pListStore->append();
175 (*row)[m_columns.m_id] = id;
177 m_threads[id].reset(new Thread(dev, &m_signal_update));
180 // all devices loaded
181 m_scanned = true;
183 StatusbarSet(_("All devices loaded."));
185 // if one or more device plugged in,
186 // activate the first one
187 Gtk::TreeModel::iterator iter = m_pListStore->children().begin();
188 if( iter )
189 m_pDeviceSelection->select(iter);
192 bool BackupWindow::Connect(Thread *thread)
194 if( thread->Connected() )
195 return true;
197 StatusbarSet(_("Connecting to Device..."));
198 static int tries(0);
200 CheckDeviceName(thread);
202 if( !thread->Connect() ) {
203 if( thread->PasswordRequired() ) {
204 bool connected = false;
205 while( !connected && !thread->PasswordOutOfTries() ) {
206 PasswordDlg dlg(thread->PasswordRemainingTries());
207 if( dlg.run() == Gtk::RESPONSE_OK ) {
208 connected = thread->Connect(dlg.GetPassword());
209 if( !connected ) {
210 // low level error
211 Gtk::MessageDialog msg(thread->LastInterfaceError());
212 msg.run();
213 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
214 return false;
217 else { // user cancelled
218 thread->Reset();
219 StatusbarSet(_("Connection cancelled."));
220 return false;
223 if( thread->PasswordOutOfTries() )
225 Gtk::MessageDialog msg(thread->BadPasswordError());
226 msg.run();
227 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
228 return false;
231 else if( thread->BadSize() ) {
232 ++tries;
233 if( tries < 3 ) {
234 std::cerr << thread->BadSizeError() << std::endl;
235 thread->Reset();
236 sleep(2);
237 return Connect(thread);
239 else {
240 Gtk::MessageDialog msg(thread->BadSizeError());
241 msg.run();
242 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
243 return false;
246 else {
247 Gtk::MessageDialog msg(thread->LastInterfaceError());
248 msg.run();
249 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
250 return false;
253 tries = 0;
254 StatusbarSet(_("Connected to ") + thread->GetFullname() + ".");
255 return true;
258 void BackupWindow::Disconnect(Thread *thread)
260 if( thread->Working() ) {
261 Gtk::MessageDialog dialog(*this, thread->GetFullname() + _(" is working, 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, disconnecting now may cause data corruption. Proceed anyway?"),
331 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
332 if( dialog.run() != Gtk::RESPONSE_OK )
333 return false;
336 return true;
339 void BackupWindow::signal_exception_handler()
341 try {
342 throw;
344 catch( Glib::Exception &e ) {
345 // This usually just means a missing .glade file,
346 // so we try to carry on.
347 std::cerr << _("Glib::Exception caught in main: ") << std::endl;
348 std::cerr << e.what() << std::endl;
349 Gtk::MessageDialog msg(e.what());
350 msg.run();
352 catch( ... ) {
353 // anything else, terminate window and pass on to next handler
354 // (which should be in main.cc)
355 hide();
356 throw;
361 //////////////////////////////////////////////////////////////////////////////
362 // signal handlers
364 void BackupWindow::treeview_update()
366 for( Gtk::TreeModel::iterator i = m_pListStore->children().begin();
367 i != m_pListStore->children().end(); ++i ) {
368 unsigned int id = (*i)[m_columns.m_id];
369 Thread *thread = m_threads[id].get();
370 (*i)[m_columns.m_pin] = thread->GetPIN().Str();
371 (*i)[m_columns.m_name] = thread->GetDeviceName();
372 (*i)[m_columns.m_status] = thread->Status();
373 unsigned int finished(thread->GetRecordFinished()), total(thread->GetRecordTotal());
374 unsigned int percentage(0);
375 if( total == 0 || finished == total )
376 percentage = 100;
377 else {
378 percentage = 100 * finished / total;
379 if( percentage == 100 ) // never say 100% unless finished
380 percentage = 99;
382 (*i)[m_columns.m_percentage] = percentage;
383 if( thread->CheckFinishedMarker() ) {
384 std::string op;
386 if( thread->GetThreadState() & THREAD_STATE_BACKUP )
387 op = _("Backup on");
388 else if( thread->GetThreadState() & THREAD_STATE_RESTORE )
389 op = _("Restore on");
390 else
391 op = _("Operation on");
393 StatusbarSet(op + thread->GetFullname() + _(" finished!"));
395 // thread really finished, so force percentage to 100
396 (*i)[m_columns.m_percentage] = 100;
398 if( (thread->GetThreadState() & THREAD_STATE_BACKUP) &&
399 finished != total )
401 if( finished < total ) {
402 // in some cases, not all records are backed
403 // up, possibly due to international chars
404 // in the Blackberry data which somehow
405 // forces the device to use a different
406 // low level protocol... here we need to
407 // warn the user that not all data was
408 // included in the backup
409 std::ostringstream oss;
410 oss << _("Warning\n\nNot all records were processed on device: ") << thread->GetFullname()
411 << _("\n\nOnly ") << finished << _(" of ") << total
412 << _(" 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.");
414 oss << _("\n\nSummary:\n");
415 std::vector<std::string> msgs = thread->CompareTotals();
416 for( std::vector<std::string>::const_iterator i = msgs.begin() ; i != msgs.end(); ++i )
417 oss << *i << "\n";
418 Gtk::MessageDialog msg(oss.str());
419 msg.run();
421 else if( finished > total ) {
422 std::ostringstream oss;
423 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");
424 std::vector<std::string> msgs = thread->CompareTotals();
425 for( std::vector<std::string>::const_iterator i = msgs.begin() ; i != msgs.end(); ++i )
426 oss << *i << "\n";
427 Gtk::MessageDialog msg(oss.str());
428 msg.run();
435 void BackupWindow::on_backup()
437 Thread *thread = GetActive();
439 if( !thread || !Connect(thread) )
440 return;
442 // already working?
443 if( thread->Working() ) {
444 Gtk::MessageDialog msg(_("Thread already in progress."));
445 msg.run();
446 return;
449 // make sure our target directory exists
450 if( !Barry::ConfigFile::CheckPath(thread->GetPath()) ) {
451 Gtk::MessageDialog msg(_("Could not create directory: ") + thread->GetPath());
452 msg.run();
453 return;
456 // anything to do?
457 if( thread->GetBackupList().size() == 0 && !thread->AutoSelectAll() ) {
458 Gtk::MessageDialog msg(_("No databases selected in configuration."));
459 msg.run();
460 return;
463 // prompt for a backup label, if so configured
464 std::string backupLabel;
465 if( thread->PromptBackupLabel() ) {
466 PromptDlg dlg;
467 dlg.SetPrompt(_("Please enter a label for this backup (blank is ok):"));
468 if( dlg.run() == Gtk::RESPONSE_OK ) {
469 backupLabel = dlg.GetAnswer();
471 else {
472 // user cancelled
473 return;
477 // start the thread
478 if( !thread->Backup(backupLabel) ) {
479 Gtk::MessageDialog msg(_("Error starting backup thread: ") +
480 thread->LastInterfaceError());
481 msg.run();
483 else {
484 StatusbarSet(_("Backup of ") + thread->GetFullname() + _(" in progress..."));
488 bool BackupWindow::PromptForRestoreTarball(std::string &restoreFilename,
489 const std::string &start_path)
491 char buffer[PATH_MAX];
492 char *buf = getcwd(buffer, PATH_MAX);
494 // start at the base path given... if it fails, just open
495 // the dialog where we are
496 (void)chdir(start_path.c_str());
498 Gtk::FileChooserDialog dlg(*this, _("Select backup to restore from"));
499 dlg.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
500 dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
501 int result = dlg.run();
503 if( buf )
504 (void)chdir(buf);
506 if( result != Gtk::RESPONSE_OK )
507 return false;
509 restoreFilename = dlg.get_filename();
510 return true;
513 void BackupWindow::on_restore()
515 Thread *thread = GetActive();
517 if( !thread || !Connect(thread) )
518 return;
520 // already working?
521 if( thread->Working() ) {
522 Gtk::MessageDialog msg(_("Thread already in progress."));
523 msg.run();
524 return;
527 std::string restoreFilename;
528 if( !PromptForRestoreTarball(restoreFilename, thread->GetPath()) )
529 return; // nothing to do
531 // start the thread
532 if( !thread->Restore(restoreFilename) ) {
533 Gtk::MessageDialog msg(_("Error starting restore thread: ") +
534 thread->LastInterfaceError());
535 msg.run();
537 else {
538 StatusbarSet(_("Restore of ") + thread->GetFullname() + _(" in progress..."));
542 void BackupWindow::on_disconnect()
544 Thread *thread = GetActive();
545 if( thread )
546 Disconnect(thread);
549 void BackupWindow::on_disconnect_all()
551 for( unsigned int i = 0; i < m_device_count; ++i )
552 Disconnect(m_threads[i].get());
555 void BackupWindow::on_device_change()
557 Thread *thread = GetActive();
558 if( m_pActive )
559 m_pActive->UnsetActive();
560 m_pActive = thread;
561 if( thread && Connect(thread) )
562 thread->SetActive();
565 void BackupWindow::on_config()
567 Thread *thread = GetActive();
569 if( !thread || !Connect(thread) )
570 return;
572 thread->LoadConfig();
574 ConfigDlg dlg(thread->GetDBDB(), *thread);
575 if( dlg.run() == Gtk::RESPONSE_OK ) {
576 thread->SetBackupList(dlg.GetBackupList());
577 thread->SetRestoreList(dlg.GetRestoreList());
578 thread->SetDeviceName(dlg.GetDeviceName());
579 thread->SetBackupPath(dlg.GetBackupPath());
580 thread->SetPromptBackupLabel(dlg.GetPromptBackupLabel());
581 thread->SetAutoSelectAll(dlg.GetAutoSelectAll());
582 if( !thread->Save() )
583 StatusbarSet(_("Error saving config: ") +
584 thread->LastConfigError());
585 else
586 StatusbarSet(_("Config saved successfully."));
588 thread->LoadConfig();
591 void BackupWindow::on_reload()
593 if( CheckWorking() ) {
594 Scan();
598 void BackupWindow::on_file_quit()
600 if( CheckWorking() ) {
601 hide();
605 void BackupWindow::on_help_about()
607 Gtk::AboutDialog dlg;
608 dlg.set_copyright("Copyright (C) 2007-2013, Net Direct Inc.");
609 dlg.set_license(
610 " This program is free software; you can redistribute it and/or modify\n"
611 " it under the terms of the GNU General Public License as published by\n"
612 " the Free Software Foundation; either version 2 of the License, or\n"
613 " (at your option) any later version.\n"
614 "\n"
615 " This program is distributed in the hope that it will be useful,\n"
616 " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
617 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
618 "\n"
619 " See the GNU General Public License in the COPYING file at the\n"
620 " root directory of this project for more details.\n");
622 std::vector<std::string> authors;
623 authors.push_back("Chris Frey <cdfrey@foursquare.net>");
624 authors.push_back(_("and Barry contributors. See AUTHORS file"));
625 authors.push_back(_("for detailed contribution information."));
627 dlg.set_authors(authors);
629 int logical, major, minor;
630 const char *BarryVersion = Barry::Version(logical, major, minor);
631 dlg.set_name("Barry Backup");
632 dlg.set_version(PACKAGE_VERSION);
633 dlg.set_comments(std::string(_("Using library: ")) + BarryVersion);
634 dlg.set_website("http://netdirect.ca/barry");
635 dlg.run();
638 bool BackupWindow::on_startup()
640 Scan();
641 return false;
644 bool BackupWindow::on_delete_event(GdkEventAny *)
646 if( CheckWorking() )
647 return false; // allow closing of window via window manager
648 else
649 return true; // stop the close