lib: fixed parsing of recurring VEVENTS: DAILY and interval support
[barry/progweb.git] / gui / src / BackupWindow.cc
blob2fb926b716891dfab3b58ec18c875e6a7e11ff79
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 "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, "
262 "disconnecting from it may cause data corruption, are you sure to proceed?"),
263 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
264 if( dialog.run() == Gtk::RESPONSE_CANCEL )
265 return;
268 if( thread->Connected() ) {
269 thread->Disconnect();
270 StatusbarSet(_("Disconnected from ") + thread->GetFullname() + ".");
272 else {
273 StatusbarSet(_("Not connected."));
277 void BackupWindow::CheckDeviceName(Thread *thread)
279 if( !thread->HasDeviceName() ) {
280 PromptDlg dlg;
281 dlg.SetPrompt(_("Unnamed device found (PIN: ") + thread->GetPIN().Str() + ").\r\n " + _("Please enter a name for it:"));
282 if( dlg.run() == Gtk::RESPONSE_OK ) {
283 thread->SetDeviceName(dlg.GetAnswer());
285 else {
286 thread->SetDeviceName(" ");
288 if( !thread->Save() ) {
289 Gtk::MessageDialog msg(_("Error saving config: ") +
290 thread->LastConfigError());
291 msg.run();
296 Thread *BackupWindow::GetActive()
298 Gtk::TreeModel::iterator row = m_pDeviceSelection->get_selected();
299 if( row ) {
300 unsigned int id = (*row)[m_columns.m_id];
301 return m_threads[id].get();
303 else {
304 return 0;
308 void BackupWindow::StatusbarSet(const Glib::ustring& text)
310 guint remove_id = m_last_status_id;
311 if( m_pStatusbar ) {
312 m_last_status_id = m_pStatusbar->push(text);
313 if( remove_id )
314 m_pStatusbar->remove_message(remove_id);
318 /// Returns true if ok to proceed (either nothing is currently 'working',
319 /// or the user confirmed to do it anyway)
320 bool BackupWindow::CheckWorking()
322 bool working(false);
323 for( unsigned int i = 0; i < m_device_count; ++i) {
324 if( m_threads[i]->Working() ) {
325 working = true;
326 break;
330 if( working ) {
331 Gtk::MessageDialog dialog(*this, _("One or more devices are working, "
332 "disconnecting now may cause data corruption. "
333 "Proceed anyway?"),
334 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
335 if( dialog.run() != Gtk::RESPONSE_OK )
336 return false;
339 return true;
342 void BackupWindow::signal_exception_handler()
344 try {
345 throw;
347 catch( Glib::Exception &e ) {
348 // This usually just means a missing .glade file,
349 // so we try to carry on.
350 std::cerr << "Glib::Exception caught in main: " << std::endl;
351 std::cerr << e.what() << std::endl;
352 Gtk::MessageDialog msg(e.what());
353 msg.run();
355 catch( ... ) {
356 // anything else, terminate window and pass on to next handler
357 // (which should be in main.cc)
358 hide();
359 throw;
364 //////////////////////////////////////////////////////////////////////////////
365 // signal handlers
367 void BackupWindow::treeview_update()
369 for( Gtk::TreeModel::iterator i = m_pListStore->children().begin();
370 i != m_pListStore->children().end(); ++i ) {
371 unsigned int id = (*i)[m_columns.m_id];
372 Thread *thread = m_threads[id].get();
373 (*i)[m_columns.m_pin] = thread->GetPIN().Str();
374 (*i)[m_columns.m_name] = thread->GetDeviceName();
375 (*i)[m_columns.m_status] = thread->Status();
376 unsigned int finished(thread->GetRecordFinished()), total(thread->GetRecordTotal());
377 unsigned int percentage(0);
378 if( total == 0 || finished == total )
379 percentage = 100;
380 else {
381 percentage = 100 * finished / total;
382 if( percentage == 100 ) // never say 100% unless finished
383 percentage = 99;
385 (*i)[m_columns.m_percentage] = percentage;
386 if( thread->CheckFinishedMarker() ) {
387 std::string op;
389 if( thread->GetThreadState() & THREAD_STATE_BACKUP )
390 op = _("Backup on");
391 else if( thread->GetThreadState() & THREAD_STATE_RESTORE )
392 op = _("Restore on");
393 else
394 op = _("Operation on");
396 StatusbarSet(op + thread->GetFullname() + _(" finished!"));
398 // thread really finished, so force percentage to 100
399 (*i)[m_columns.m_percentage] = 100;
401 if( (thread->GetThreadState() & THREAD_STATE_BACKUP) &&
402 finished != total )
404 if( finished < total ) {
405 // in some cases, not all records are backed
406 // up, possibly due to international chars
407 // in the Blackberry data which somehow
408 // forces the device to use a different
409 // low level protocol... here we need to
410 // warn the user that not all data was
411 // included in the backup
412 std::ostringstream oss;
413 oss << _("Warning\n\nNot all records were processed on device: ") << thread->GetFullname()
414 << _("\n\nOnly ") << finished << _(" of ") << total
415 << _(" 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.");
417 oss << _("\n\nSummary:\n");
418 std::vector<std::string> msgs = thread->CompareTotals();
419 for( std::vector<std::string>::const_iterator i = msgs.begin() ; i != msgs.end(); ++i )
420 oss << *i << "\n";
421 Gtk::MessageDialog msg(oss.str());
422 msg.run();
424 else if( finished > total ) {
425 std::ostringstream oss;
426 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");
427 std::vector<std::string> msgs = thread->CompareTotals();
428 for( std::vector<std::string>::const_iterator i = msgs.begin() ; i != msgs.end(); ++i )
429 oss << *i << "\n";
430 Gtk::MessageDialog msg(oss.str());
431 msg.run();
438 void BackupWindow::on_backup()
440 Thread *thread = GetActive();
442 if( !thread || !Connect(thread) )
443 return;
445 // already working?
446 if( thread->Working() ) {
447 Gtk::MessageDialog msg(_("Thread already in progress."));
448 msg.run();
449 return;
452 // make sure our target directory exists
453 if( !Barry::ConfigFile::CheckPath(thread->GetPath()) ) {
454 Gtk::MessageDialog msg(_("Could not create directory: ") + thread->GetPath());
455 msg.run();
456 return;
459 // anything to do?
460 if( thread->GetBackupList().size() == 0 && !thread->AutoSelectAll() ) {
461 Gtk::MessageDialog msg(_("No databases selected in configuration."));
462 msg.run();
463 return;
466 // prompt for a backup label, if so configured
467 std::string backupLabel;
468 if( thread->PromptBackupLabel() ) {
469 PromptDlg dlg;
470 dlg.SetPrompt(_("Please enter a label for this backup (blank is ok):"));
471 if( dlg.run() == Gtk::RESPONSE_OK ) {
472 backupLabel = dlg.GetAnswer();
474 else {
475 // user cancelled
476 return;
480 // start the thread
481 if( !thread->Backup(backupLabel) ) {
482 Gtk::MessageDialog msg(_("Error starting backup thread: ") +
483 thread->LastInterfaceError());
484 msg.run();
486 else {
487 StatusbarSet(_("Backup of ") + thread->GetFullname() + _(" in progress..."));
491 bool BackupWindow::PromptForRestoreTarball(std::string &restoreFilename,
492 const std::string &start_path)
494 char buffer[PATH_MAX];
495 char *buf = getcwd(buffer, PATH_MAX);
497 // start at the base path given... if it fails, just open
498 // the dialog where we are
499 (void)chdir(start_path.c_str());
501 Gtk::FileChooserDialog dlg(*this, _("Select backup to restore from"));
502 dlg.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
503 dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
504 int result = dlg.run();
506 if( buf )
507 (void)chdir(buf);
509 if( result != Gtk::RESPONSE_OK )
510 return false;
512 restoreFilename = dlg.get_filename();
513 return true;
516 void BackupWindow::on_restore()
518 Thread *thread = GetActive();
520 if( !thread || !Connect(thread) )
521 return;
523 // already working?
524 if( thread->Working() ) {
525 Gtk::MessageDialog msg(_("Thread already in progress."));
526 msg.run();
527 return;
530 std::string restoreFilename;
531 if( !PromptForRestoreTarball(restoreFilename, thread->GetPath()) )
532 return; // nothing to do
534 // start the thread
535 if( !thread->Restore(restoreFilename) ) {
536 Gtk::MessageDialog msg(_("Error starting restore thread: ") +
537 thread->LastInterfaceError());
538 msg.run();
540 else {
541 StatusbarSet(_("Restore of ") + thread->GetFullname() + _(" in progress..."));
545 void BackupWindow::on_disconnect()
547 Thread *thread = GetActive();
548 if( thread )
549 Disconnect(thread);
552 void BackupWindow::on_disconnect_all()
554 for( unsigned int i = 0; i < m_device_count; ++i )
555 Disconnect(m_threads[i].get());
558 void BackupWindow::on_device_change()
560 Thread *thread = GetActive();
561 if( m_pActive )
562 m_pActive->UnsetActive();
563 m_pActive = thread;
564 if( thread && Connect(thread) )
565 thread->SetActive();
568 void BackupWindow::on_config()
570 Thread *thread = GetActive();
572 if( !thread || !Connect(thread) )
573 return;
575 thread->LoadConfig();
577 ConfigDlg dlg(thread->GetDBDB(), *thread);
578 if( dlg.run() == Gtk::RESPONSE_OK ) {
579 thread->SetBackupList(dlg.GetBackupList());
580 thread->SetRestoreList(dlg.GetRestoreList());
581 thread->SetDeviceName(dlg.GetDeviceName());
582 thread->SetBackupPath(dlg.GetBackupPath());
583 thread->SetPromptBackupLabel(dlg.GetPromptBackupLabel());
584 thread->SetAutoSelectAll(dlg.GetAutoSelectAll());
585 if( !thread->Save() )
586 StatusbarSet(_("Error saving config: ") +
587 thread->LastConfigError());
588 else
589 StatusbarSet(_("Config saved successfully."));
591 thread->LoadConfig();
594 void BackupWindow::on_reload()
596 if( CheckWorking() ) {
597 Scan();
601 void BackupWindow::on_file_quit()
603 if( CheckWorking() ) {
604 hide();
608 void BackupWindow::on_help_about()
610 Gtk::AboutDialog dlg;
611 dlg.set_copyright("Copyright (C) 2007-2012, Net Direct Inc.");
612 dlg.set_license(
613 " This program is free software; you can redistribute it and/or modify\n"
614 " it under the terms of the GNU General Public License as published by\n"
615 " the Free Software Foundation; either version 2 of the License, or\n"
616 " (at your option) any later version.\n"
617 "\n"
618 " This program is distributed in the hope that it will be useful,\n"
619 " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
620 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
621 "\n"
622 " See the GNU General Public License in the COPYING file at the\n"
623 " root directory of this project for more details.\n");
625 std::vector<std::string> authors;
626 authors.push_back("Chris Frey <cdfrey@foursquare.net>");
627 authors.push_back(_("and Barry contributors. See AUTHORS file"));
628 authors.push_back(_("for detailed contribution information."));
630 dlg.set_authors(authors);
632 int logical, major, minor;
633 const char *BarryVersion = Barry::Version(logical, major, minor);
634 dlg.set_name("Barry Backup");
635 dlg.set_version(PACKAGE_VERSION);
636 dlg.set_comments(std::string(_("Using library: ")) + BarryVersion);
637 dlg.set_website("http://netdirect.ca/barry");
638 dlg.run();
641 bool BackupWindow::on_startup()
643 Scan();
644 return false;
647 bool BackupWindow::on_delete_event(GdkEventAny *)
649 if( CheckWorking() )
650 return false; // allow closing of window via window manager
651 else
652 return true; // stop the close