Updated copyright dates for 2011
[barry.git] / gui / src / BackupWindow.cc
blobf9a4cf28fa2ef03fb88c129ebae65fbfa25b0cf5
1 ///
2 /// \file BackupWindow.cc
3 /// GUI window class
4 ///
6 /*
7 Copyright (C) 2007-2011, 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();
144 m_device_count = m_bus.ProbeCount();
146 if( m_device_count == 0 )
147 m_pDeviceLabel->set_label(_("No devices."));
148 else if( m_device_count == 1 )
149 m_pDeviceLabel->set_label(_("1 device:"));
150 else
152 std::ostringstream oss;
153 oss << m_device_count << _(" devices:");
154 m_pDeviceLabel->set_label(oss.str());
157 m_threads.resize(m_device_count);
158 for( unsigned int id = 0; id < m_device_count; ++id ) {
159 Device dev = m_bus.Get(id);
160 Gtk::TreeModel::iterator row = m_pListStore->append();
161 (*row)[m_columns.m_id] = id;
163 m_threads[id].reset(new Thread(dev, &m_signal_update));
166 // all devices loaded
167 m_scanned = true;
169 StatusbarSet(_("All devices loaded."));
171 // if one or more device plugged in,
172 // activate the first one
173 Gtk::TreeModel::iterator iter = m_pListStore->children().begin();
174 if( iter )
175 m_pDeviceSelection->select(iter);
178 bool BackupWindow::Connect(Thread *thread)
180 if( thread->Connected() )
181 return true;
183 StatusbarSet(_("Connecting to Device..."));
184 static int tries(0);
186 CheckDeviceName(thread);
188 if( !thread->Connect() ) {
189 if( thread->PasswordRequired() ) {
190 bool connected = false;
191 while( !connected && !thread->PasswordOutOfTries() ) {
192 PasswordDlg dlg(thread->PasswordRemainingTries());
193 if( dlg.run() == Gtk::RESPONSE_OK )
194 connected = thread->Connect(dlg.GetPassword());
195 else { // user cancelled
196 thread->Reset();
197 StatusbarSet(_("Connection cancelled."));
198 return false;
201 if( thread->PasswordOutOfTries() )
203 Gtk::MessageDialog msg(thread->BadPasswordError());
204 msg.run();
205 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
206 return false;
209 else if( thread->BadSize() ) {
210 ++tries;
211 if( tries < 3 ) {
212 std::cerr << thread->BadSizeError() << std::endl;
213 thread->Reset();
214 sleep(2);
215 return Connect(thread);
217 else {
218 Gtk::MessageDialog msg(thread->BadSizeError());
219 msg.run();
220 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
221 return false;
224 else {
225 Gtk::MessageDialog msg(thread->LastInterfaceError());
226 msg.run();
227 StatusbarSet(_("Cannot connect to ") + thread->GetFullname() + ".");
228 return false;
231 tries = 0;
232 StatusbarSet(_("Connected to ") + thread->GetFullname() + ".");
233 return true;
236 void BackupWindow::Disconnect(Thread *thread)
238 if( thread->Working() ) {
239 Gtk::MessageDialog dialog(*this, thread->GetFullname() + _(" is working, "
240 "disconnecting from it may cause data corruption, are you sure to proceed?"),
241 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
242 if( dialog.run() == Gtk::RESPONSE_CANCEL )
243 return;
246 if( thread->Connected() ) {
247 thread->Disconnect();
248 StatusbarSet(_("Disconnected from ") + thread->GetFullname() + ".");
250 else {
251 StatusbarSet(_("Not connected."));
255 void BackupWindow::CheckDeviceName(Thread *thread)
257 if( !thread->HasDeviceName() ) {
258 PromptDlg dlg;
259 dlg.SetPrompt(_("Unnamed device found (PIN: ") + thread->GetPIN().Str() + ").\r\n " + _("Please enter a name for it:"));
260 if( dlg.run() == Gtk::RESPONSE_OK ) {
261 thread->SetDeviceName(dlg.GetAnswer());
263 else {
264 thread->SetDeviceName(" ");
266 if( !thread->Save() ) {
267 Gtk::MessageDialog msg(_("Error saving config: ") +
268 thread->LastConfigError());
269 msg.run();
274 Thread *BackupWindow::GetActive()
276 Gtk::TreeModel::iterator row = m_pDeviceSelection->get_selected();
277 if( row ) {
278 unsigned int id = (*row)[m_columns.m_id];
279 return m_threads[id].get();
281 else {
282 return 0;
286 void BackupWindow::StatusbarSet(const Glib::ustring& text)
288 guint remove_id = m_last_status_id;
289 if( m_pStatusbar ) {
290 m_last_status_id = m_pStatusbar->push(text);
291 if( remove_id )
292 m_pStatusbar->remove_message(remove_id);
296 /// Returns true if ok to proceed (either nothing is currently 'working',
297 /// or the user confirmed to do it anyway)
298 bool BackupWindow::CheckWorking()
300 bool working(false);
301 for( unsigned int i = 0; i < m_device_count; ++i) {
302 if( m_threads[i]->Working() ) {
303 working = true;
304 break;
308 if( working ) {
309 Gtk::MessageDialog dialog(*this, _("One or more devices are working, "
310 "disconnecting now may cause data corruption. "
311 "Proceed anyway?"),
312 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
313 if( dialog.run() != Gtk::RESPONSE_OK )
314 return false;
317 return true;
320 void BackupWindow::signal_exception_handler()
322 try {
323 throw;
325 catch( Glib::Exception &e ) {
326 // This usually just means a missing .glade file,
327 // so we try to carry on.
328 std::cerr << "Glib::Exception caught in main: " << std::endl;
329 std::cerr << e.what() << std::endl;
330 Gtk::MessageDialog msg(e.what());
331 msg.run();
333 catch( ... ) {
334 // anything else, terminate window and pass on to next handler
335 // (which should be in main.cc)
336 hide();
337 throw;
342 //////////////////////////////////////////////////////////////////////////////
343 // signal handlers
345 void BackupWindow::treeview_update()
347 for( Gtk::TreeModel::iterator i = m_pListStore->children().begin();
348 i != m_pListStore->children().end(); ++i ) {
349 unsigned int id = (*i)[m_columns.m_id];
350 Thread *thread = m_threads[id].get();
351 (*i)[m_columns.m_pin] = thread->GetPIN().Str();
352 (*i)[m_columns.m_name] = thread->GetDeviceName();
353 (*i)[m_columns.m_status] = thread->Status();
354 unsigned int finished(thread->GetRecordFinished()), total(thread->GetRecordTotal());
355 unsigned int percentage(0);
356 if( total == 0 || finished == total )
357 percentage = 100;
358 else {
359 percentage = 100 * finished / total;
360 if( percentage == 100 ) // never say 100% unless finished
361 percentage = 99;
363 (*i)[m_columns.m_percentage] = percentage;
364 if( thread->CheckFinishedMarker() ) {
365 std::string op;
367 if( thread->GetThreadState() & THREAD_STATE_BACKUP )
368 op = _("Backup on");
369 else if( thread->GetThreadState() & THREAD_STATE_RESTORE )
370 op = _("Restore on");
371 else
372 op = _("Operation on");
374 StatusbarSet(op + thread->GetFullname() + _(" finished!"));
375 if( (thread->GetThreadState() & THREAD_STATE_BACKUP) &&
376 finished != total )
378 // in some cases, not all records are backed
379 // up, possibly due to international chars
380 // in the Blackberry data which somehow
381 // forces the device to use a different
382 // low level protocol... here we need to
383 // warn the user that not all data was
384 // included in the backup
385 std::ostringstream oss;
386 oss << _("Warning\n\nNot all records were processed on device: ") << thread->GetFullname()
387 << _("\n\nOnly ") << finished << _(" of ") << total
388 << _(" 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. Please contact the developers at http://netdirect.ca/barry if you want to assist in debugging this issue.");
389 Gtk::MessageDialog msg(oss.str());
390 msg.run();
396 void BackupWindow::on_backup()
398 Thread *thread = GetActive();
400 if( !thread || !Connect(thread) )
401 return;
403 // already working?
404 if( thread->Working() ) {
405 Gtk::MessageDialog msg(_("Thread already in progress."));
406 msg.run();
407 return;
410 // make sure our target directory exists
411 if( !Barry::ConfigFile::CheckPath(thread->GetPath()) ) {
412 Gtk::MessageDialog msg(_("Could not create directory: ") + thread->GetPath());
413 msg.run();
414 return;
417 // anything to do?
418 if( thread->GetBackupList().size() == 0 ) {
419 Gtk::MessageDialog msg(_("No databases selected in configuration."));
420 msg.run();
421 return;
424 // prompt for a backup label, if so configured
425 std::string backupLabel;
426 if( thread->PromptBackupLabel() ) {
427 PromptDlg dlg;
428 dlg.SetPrompt(_("Please enter a label for this backup (blank is ok):"));
429 if( dlg.run() == Gtk::RESPONSE_OK ) {
430 backupLabel = dlg.GetAnswer();
432 else {
433 // user cancelled
434 return;
438 // start the thread
439 if( !thread->Backup(backupLabel) ) {
440 Gtk::MessageDialog msg(_("Error starting backup thread: ") +
441 thread->LastInterfaceError());
442 msg.run();
444 else {
445 StatusbarSet(_("Backup of ") + thread->GetFullname() + _(" in progress..."));
449 bool BackupWindow::PromptForRestoreTarball(std::string &restoreFilename,
450 const std::string &start_path)
452 char buffer[PATH_MAX];
453 char *buf = getcwd(buffer, PATH_MAX);
455 // start at the base path given... if it fails, just open
456 // the dialog where we are
457 (void)chdir(start_path.c_str());
459 Gtk::FileChooserDialog dlg(*this, _("Select backup to restore from"));
460 dlg.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
461 dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
462 int result = dlg.run();
464 if( buf )
465 (void)chdir(buf);
467 if( result != Gtk::RESPONSE_OK )
468 return false;
470 restoreFilename = dlg.get_filename();
471 return true;
474 void BackupWindow::on_restore()
476 Thread *thread = GetActive();
478 if( !thread || !Connect(thread) )
479 return;
481 // already working?
482 if( thread->Working() ) {
483 Gtk::MessageDialog msg(_("Thread already in progress."));
484 msg.run();
485 return;
488 std::string restoreFilename;
489 if( !PromptForRestoreTarball(restoreFilename, thread->GetPath()) )
490 return; // nothing to do
492 // start the thread
493 if( !thread->Restore(restoreFilename) ) {
494 Gtk::MessageDialog msg(_("Error starting restore thread: ") +
495 thread->LastInterfaceError());
496 msg.run();
498 else {
499 StatusbarSet(_("Restore of ") + thread->GetFullname() + _(" in progress..."));
503 void BackupWindow::on_disconnect()
505 Thread *thread = GetActive();
506 if( thread )
507 Disconnect(thread);
510 void BackupWindow::on_disconnect_all()
512 for( unsigned int i = 0; i < m_device_count; ++i )
513 Disconnect(m_threads[i].get());
516 void BackupWindow::on_device_change()
518 Thread *thread = GetActive();
519 if( m_pActive )
520 m_pActive->UnsetActive();
521 m_pActive = thread;
522 if( thread && Connect(thread) )
523 thread->SetActive();
526 void BackupWindow::on_config()
528 Thread *thread = GetActive();
530 if( !thread || !Connect(thread) )
531 return;
533 thread->LoadConfig();
535 ConfigDlg dlg(thread->GetDBDB(), *thread);
536 if( dlg.run() == Gtk::RESPONSE_OK ) {
537 thread->SetBackupList(dlg.GetBackupList());
538 thread->SetRestoreList(dlg.GetRestoreList());
539 thread->SetDeviceName(dlg.GetDeviceName());
540 thread->SetBackupPath(dlg.GetBackupPath());
541 thread->SetPromptBackupLabel(dlg.GetPromptBackupLabel());
542 if( !thread->Save() )
543 StatusbarSet(_("Error saving config: ") +
544 thread->LastConfigError());
545 else
546 StatusbarSet(_("Config saved successfully."));
548 thread->LoadConfig();
551 void BackupWindow::on_reload()
553 if( CheckWorking() ) {
554 Scan();
558 void BackupWindow::on_file_quit()
560 if( CheckWorking() ) {
561 hide();
565 void BackupWindow::on_help_about()
567 Gtk::AboutDialog dlg;
568 dlg.set_copyright("Copyright (C) 2007-2011, Net Direct Inc.");
569 dlg.set_license(
570 " This program is free software; you can redistribute it and/or modify\n"
571 " it under the terms of the GNU General Public License as published by\n"
572 " the Free Software Foundation; either version 2 of the License, or\n"
573 " (at your option) any later version.\n"
574 "\n"
575 " This program is distributed in the hope that it will be useful,\n"
576 " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
577 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
578 "\n"
579 " See the GNU General Public License in the COPYING file at the\n"
580 " root directory of this project for more details.\n");
582 std::vector<std::string> authors;
583 authors.push_back("Chris Frey <cdfrey@foursquare.net>");
584 authors.push_back(_("and Barry contributors. See AUTHORS file"));
585 authors.push_back(_("for detailed contribution information."));
587 dlg.set_authors(authors);
589 int major, minor;
590 const char *BarryVersion = Barry::Version(major, minor);
591 dlg.set_name("Barry Backup");
592 dlg.set_version("0.17");
593 dlg.set_comments(std::string(_("Using library: ")) + BarryVersion);
594 dlg.set_website("http://netdirect.ca/barry");
595 dlg.run();
598 bool BackupWindow::on_startup()
600 Scan();
601 return false;
604 bool BackupWindow::on_delete_event(GdkEventAny *)
606 if( CheckWorking() )
607 return false; // allow closing of window via window manager
608 else
609 return true; // stop the close