os4x: fixed format string bugs
[barry.git] / gui / src / BackupWindow.cc
blob31d14a9c69768043ba1a0e7053a1491fed313307
1 ///
2 /// \file BackupWindow.cc
3 /// GUI window class
4 ///
6 /*
7 Copyright (C) 2007-2009, 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 <gtkmm/aboutdialog.h>
28 #include <iostream>
29 #include <iomanip>
30 #include <sstream>
31 #include <string>
32 #include <unistd.h>
34 BackupWindow::BackupWindow(BaseObjectType *cobject,
35 const Glib::RefPtr<Gnome::Glade::Xml> &xml)
36 : Gtk::Window(cobject)
37 , m_xml(xml)
38 , m_pStatusbar(0)
39 , m_pBackupButton(0)
40 , m_pRestoreButton(0)
41 , m_pDisconnectButton(0)
42 , m_pReloadButton(0)
43 , m_pDeviceLabel(0)
44 , m_pDeviceList(0)
45 , m_device_count(0)
46 , m_pActive(0)
47 , m_scanned(false)
48 , m_last_status_id(0)
50 m_signal_update.connect(
51 sigc::mem_fun(*this, &BackupWindow::treeview_update));
53 // setup menu signals
54 Gtk::MenuItem *pItem = 0;
55 m_xml->get_widget("menu_file_quit", pItem);
56 pItem->signal_activate().connect(
57 sigc::mem_fun(*this, &BackupWindow::on_file_quit));
59 m_xml->get_widget("menu_help_about", pItem);
60 pItem->signal_activate().connect(
61 sigc::mem_fun(*this, &BackupWindow::on_help_about));
63 // get various widget pointers we will use later
64 m_xml->get_widget("DeviceLabel", m_pDeviceLabel);
65 m_xml->get_widget("DeviceList", m_pDeviceList);
66 m_xml->get_widget("BackupButton", m_pBackupButton);
67 m_xml->get_widget("RestoreButton", m_pRestoreButton);
68 m_xml->get_widget("ConfigButton", m_pConfigButton);
69 m_xml->get_widget("DisconnectButton", m_pDisconnectButton);
70 m_xml->get_widget("DisconnectAllButton", m_pDisconnectAllButton);
71 m_xml->get_widget("ReloadButton", m_pReloadButton);
72 m_xml->get_widget("Statusbar", m_pStatusbar);
74 // set up device list
75 m_pListStore = Gtk::ListStore::create(m_columns);
76 m_pDeviceList->set_model(m_pListStore);
78 m_pDeviceList->append_column("PIN", m_columns.m_pin);
79 m_pDeviceList->append_column("Name", m_columns.m_name);
80 m_pDeviceList->append_column("Status", m_columns.m_status);
81 Gtk::CellRendererProgress* cell = new Gtk::CellRendererProgress;
82 m_pDeviceList->append_column("Progress", *cell);
83 Gtk::TreeViewColumn* pColumn = m_pDeviceList->get_column(3);
84 pColumn->add_attribute(cell->property_value(), m_columns.m_percentage);
86 m_pDeviceList->get_column(0)->set_min_width(60);
87 m_pDeviceList->get_column(1)->set_min_width(100);
88 m_pDeviceList->get_column(2)->set_min_width(75);
90 for( unsigned int i = 0; i < 4; ++i )
91 m_pDeviceList->get_column(i)->set_resizable();
93 // set up device list selection
94 m_pDeviceSelection = m_pDeviceList->get_selection();
96 // setup widget signals
97 m_pBackupButton->signal_clicked().connect(
98 sigc::mem_fun(*this, &BackupWindow::on_backup));
99 m_pRestoreButton->signal_clicked().connect(
100 sigc::mem_fun(*this, &BackupWindow::on_restore));
101 m_pConfigButton->signal_clicked().connect(
102 sigc::mem_fun(*this, &BackupWindow::on_config));
103 m_pDisconnectButton->signal_clicked().connect(
104 sigc::mem_fun(*this, &BackupWindow::on_disconnect));
105 m_pDisconnectAllButton->signal_clicked().connect(
106 sigc::mem_fun(*this, &BackupWindow::on_disconnect_all));
107 m_pReloadButton->signal_clicked().connect(
108 sigc::mem_fun(*this, &BackupWindow::on_reload));
109 m_pDeviceSelection->signal_changed().connect(
110 sigc::mem_fun(*this, &BackupWindow::on_device_change));
112 // setup startup device scan
113 Glib::signal_timeout().connect(
114 sigc::mem_fun(*this, &BackupWindow::on_startup), 500);
116 // workaround: normally this should say "Ready" but since
117 // the initial Scan() happens right away, and the statusbar
118 // doesn't seem to update the screen until the handler is
119 // finished, we update the status bar here instead
120 StatusbarSet("Scanning for devices...");
122 // do this last so that any exceptions in the constructor
123 // won't cause a connected signal handler to a non-object
124 // (i.e. ~BackupWindow() won't get called if constructor throws)
125 m_signal_handler_connection = Glib::add_exception_handler(
126 sigc::mem_fun(*this, &BackupWindow::signal_exception_handler) );
129 BackupWindow::~BackupWindow()
131 // disconnect the signal, as we're going out of business
132 m_signal_handler_connection.disconnect();
135 void BackupWindow::Scan()
137 StatusbarSet("Scanning for devices...");
139 m_pListStore->clear();
140 m_threads.clear();
142 m_bus.Probe();
143 m_device_count = m_bus.ProbeCount();
145 if( m_device_count == 0 )
146 m_pDeviceLabel->set_label("No devices.");
147 else if( m_device_count == 1 )
148 m_pDeviceLabel->set_label("1 device:");
149 else
151 std::ostringstream oss;
152 oss << m_device_count << " devices:";
153 m_pDeviceLabel->set_label(oss.str());
156 m_threads.resize(m_device_count);
157 for( unsigned int id = 0; id < m_device_count; ++id ) {
158 Device dev = m_bus.Get(id);
159 Gtk::TreeModel::iterator row = m_pListStore->append();
160 (*row)[m_columns.m_id] = id;
162 m_threads[id].reset(new Thread(dev, &m_signal_update));
165 // all devices loaded
166 m_scanned = true;
168 StatusbarSet("All devices loaded.");
170 // if one or more device plugged in,
171 // activate the first one
172 Gtk::TreeModel::iterator iter = m_pListStore->children().begin();
173 if( iter )
174 m_pDeviceSelection->select(iter);
177 bool BackupWindow::Connect(Thread *thread)
179 if( thread->Connected() )
180 return true;
182 StatusbarSet("Connecting to Device...");
183 static int tries(0);
185 CheckDeviceName(thread);
187 if( !thread->Connect() ) {
188 if( thread->PasswordRequired() ) {
189 bool connected = false;
190 while( !connected && !thread->PasswordOutOfTries() ) {
191 PasswordDlg dlg(thread->PasswordRemainingTries());
192 if( dlg.run() == Gtk::RESPONSE_OK )
193 connected = thread->Connect(dlg.GetPassword());
194 else { // user cancelled
195 thread->Reset();
196 StatusbarSet("Connection cancelled.");
197 return false;
200 if( thread->PasswordOutOfTries() )
202 Gtk::MessageDialog msg(thread->BadPasswordError());
203 msg.run();
204 StatusbarSet("Cannot connect to " + thread->GetFullname() + ".");
205 return false;
208 else if( thread->BadSize() ) {
209 ++tries;
210 if( tries < 3 ) {
211 std::cerr << thread->BadSizeError() << std::endl;
212 thread->Reset();
213 sleep(2);
214 return Connect(thread);
216 else {
217 Gtk::MessageDialog msg(thread->BadSizeError());
218 msg.run();
219 StatusbarSet("Cannot connect to " + thread->GetFullname() + ".");
220 return false;
223 else {
224 Gtk::MessageDialog msg(thread->LastInterfaceError());
225 msg.run();
226 StatusbarSet("Cannot connect to " + thread->GetFullname() + ".");
227 return false;
230 tries = 0;
231 StatusbarSet("Connected to " + thread->GetFullname() + ".");
232 return true;
235 void BackupWindow::Disconnect(Thread *thread)
237 if( thread->Working() ) {
238 Gtk::MessageDialog dialog(*this, thread->GetFullname() + " is working, "
239 "disconnecting from it may cause data corruption, are you sure to proceed?",
240 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
241 if( dialog.run() == Gtk::RESPONSE_CANCEL )
242 return;
245 if( thread->Connected() ) {
246 thread->Disconnect();
247 StatusbarSet("Disconnected from " + thread->GetFullname() + ".");
249 else {
250 StatusbarSet("Not connected.");
254 void BackupWindow::CheckDeviceName(Thread *thread)
256 if( !thread->HasDeviceName() ) {
257 PromptDlg dlg;
258 dlg.SetPrompt("Unnamed device found (PIN: " + thread->GetPIN().str() + ").\r\nPlease enter a name for it:");
259 if( dlg.run() == Gtk::RESPONSE_OK ) {
260 thread->SetDeviceName(dlg.GetAnswer());
262 else {
263 thread->SetDeviceName(" ");
265 if( !thread->Save() ) {
266 Gtk::MessageDialog msg("Error saving config: " +
267 thread->LastConfigError());
268 msg.run();
273 Thread *BackupWindow::GetActive()
275 Gtk::TreeModel::iterator row = m_pDeviceSelection->get_selected();
276 if( row ) {
277 unsigned int id = (*row)[m_columns.m_id];
278 return m_threads[id].get();
280 else {
281 return 0;
285 void BackupWindow::StatusbarSet(const Glib::ustring& text)
287 guint remove_id = m_last_status_id;
288 if( m_pStatusbar ) {
289 m_last_status_id = m_pStatusbar->push(text);
290 if( remove_id )
291 m_pStatusbar->remove_message(remove_id);
295 /// Returns true if ok to proceed (either nothing is currently 'working',
296 /// or the user confirmed to do it anyway)
297 bool BackupWindow::CheckWorking()
299 bool working(false);
300 for( unsigned int i = 0; i < m_device_count; ++i) {
301 if( m_threads[i]->Working() ) {
302 working = true;
303 break;
307 if( working ) {
308 Gtk::MessageDialog dialog(*this, "One or more devices are working, "
309 "disconnecting now may cause data corruption. "
310 "Proceed anyway?",
311 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
312 if( dialog.run() != Gtk::RESPONSE_OK )
313 return false;
316 return true;
319 void BackupWindow::signal_exception_handler()
321 try {
322 throw;
324 catch( Glib::Exception &e ) {
325 // This usually just means a missing .glade file,
326 // so we try to carry on.
327 std::cerr << "Glib::Exception caught in main: " << std::endl;
328 std::cerr << e.what() << std::endl;
329 Gtk::MessageDialog msg(e.what());
330 msg.run();
332 catch( ... ) {
333 // anything else, terminate window and pass on to next handler
334 // (which should be in main.cc)
335 hide();
336 throw;
341 //////////////////////////////////////////////////////////////////////////////
342 // signal handlers
344 void BackupWindow::treeview_update()
346 for( Gtk::TreeModel::iterator i = m_pListStore->children().begin();
347 i != m_pListStore->children().end(); ++i ) {
348 unsigned int id = (*i)[m_columns.m_id];
349 Thread *thread = m_threads[id].get();
350 (*i)[m_columns.m_pin] = thread->GetPIN().str();
351 (*i)[m_columns.m_name] = thread->GetDeviceName();
352 (*i)[m_columns.m_status] = thread->Status();
353 unsigned int finished(thread->GetRecordFinished()), total(thread->GetRecordTotal());
354 unsigned int percentage(0);
355 if( total == 0 || finished == total )
356 percentage = 100;
357 else {
358 percentage = 100 * finished / total;
359 if( percentage == 100 ) // never say 100% unless finished
360 percentage = 99;
362 (*i)[m_columns.m_percentage] = percentage;
363 if( thread->CheckFinishedMarker() ) {
364 std::string op;
366 if( thread->GetThreadState() & THREAD_STATE_BACKUP )
367 op = "Backup";
368 else if( thread->GetThreadState() & THREAD_STATE_RESTORE )
369 op = "Restore";
370 else
371 op = "Operation";
373 StatusbarSet(op + " on " + thread->GetFullname() + " finished!");
374 if( (thread->GetThreadState() & THREAD_STATE_BACKUP) &&
375 finished != total )
377 // in some cases, not all records are backed
378 // up, possibly due to international chars
379 // in the Blackberry data which somehow
380 // forces the device to use a different
381 // low level protocol... here we need to
382 // warn the user that not all data was
383 // included in the backup
384 std::ostringstream oss;
385 oss << "Warning\n\nNot all records were processed on device:" << thread->GetFullname() << "\n\nOnly " << finished << " of " << total << " 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.";
386 Gtk::MessageDialog msg(oss.str());
387 msg.run();
393 void BackupWindow::on_backup()
395 Thread *thread = GetActive();
397 if( !thread || !Connect(thread) )
398 return;
400 // already working?
401 if( thread->Working() ) {
402 Gtk::MessageDialog msg("Thread already in progress.");
403 msg.run();
404 return;
407 // make sure our target directory exists
408 if( !::CheckPath(thread->GetPath()) ) {
409 Gtk::MessageDialog msg("Could not create directory: " + thread->GetPath());
410 msg.run();
411 return;
414 // anything to do?
415 if( thread->GetBackupList().size() == 0 ) {
416 Gtk::MessageDialog msg("No databases selected in configuration.");
417 msg.run();
418 return;
421 // prompt for a backup label, if so configured
422 std::string backupLabel;
423 if( thread->PromptBackupLabel() ) {
424 PromptDlg dlg;
425 dlg.SetPrompt("Please enter a label for this backup (blank is ok):");
426 if( dlg.run() == Gtk::RESPONSE_OK ) {
427 backupLabel = dlg.GetAnswer();
429 else {
430 // user cancelled
431 return;
435 // start the thread
436 if( !thread->Backup(backupLabel) ) {
437 Gtk::MessageDialog msg("Error starting backup thread: " +
438 thread->LastInterfaceError());
439 msg.run();
441 else {
442 StatusbarSet("Backup of " + thread->GetFullname() + " in progress...");
446 bool BackupWindow::PromptForRestoreTarball(std::string &restoreFilename,
447 const std::string &start_path)
449 char buffer[PATH_MAX];
450 char *buf = getcwd(buffer, PATH_MAX);
452 // start at the base path given... if it fails, just open
453 // the dialog where we are
454 chdir(start_path.c_str());
456 Gtk::FileChooserDialog dlg(*this, "Select backup to restore from");
457 dlg.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
458 dlg.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
459 int result = dlg.run();
461 if( buf )
462 chdir(buf);
464 if( result != Gtk::RESPONSE_OK )
465 return false;
467 restoreFilename = dlg.get_filename();
468 return true;
471 void BackupWindow::on_restore()
473 Thread *thread = GetActive();
475 if( !thread || !Connect(thread) )
476 return;
478 // already working?
479 if( thread->Working() ) {
480 Gtk::MessageDialog msg("Thread already in progress.");
481 msg.run();
482 return;
485 std::string restoreFilename;
486 if( !PromptForRestoreTarball(restoreFilename, thread->GetPath()) )
487 return; // nothing to do
489 // start the thread
490 // if( !thread->RestoreAndBackup(restoreFilename) ) {
491 if( !thread->Restore(restoreFilename) ) {
492 Gtk::MessageDialog msg("Error starting restore thread: " +
493 thread->LastInterfaceError());
494 msg.run();
496 else {
497 StatusbarSet("Restore of " + thread->GetFullname() + " in progress...");
501 void BackupWindow::on_disconnect()
503 Thread *thread = GetActive();
504 if( thread )
505 Disconnect(thread);
508 void BackupWindow::on_disconnect_all()
510 for( unsigned int i = 0; i < m_device_count; ++i )
511 Disconnect(m_threads[i].get());
514 void BackupWindow::on_device_change()
516 Thread *thread = GetActive();
517 if( m_pActive )
518 m_pActive->UnsetActive();
519 m_pActive = thread;
520 if( thread && Connect(thread) )
521 thread->SetActive();
524 void BackupWindow::on_config()
526 Thread *thread = GetActive();
528 if( !thread || !Connect(thread) )
529 return;
531 thread->LoadConfig();
533 ConfigDlg dlg(thread->GetDBDB(), *thread);
534 if( dlg.run() == Gtk::RESPONSE_OK ) {
535 thread->SetBackupList(dlg.GetBackupList());
536 thread->SetRestoreList(dlg.GetRestoreList());
537 thread->SetDeviceName(dlg.GetDeviceName());
538 thread->SetBackupPath(dlg.GetBackupPath());
539 thread->SetPromptBackupLabel(dlg.GetPromptBackupLabel());
540 if( !thread->Save() )
541 StatusbarSet("Error saving config: " +
542 thread->LastConfigError());
543 else
544 StatusbarSet("Config saved successfully.");
546 thread->LoadConfig();
549 void BackupWindow::on_reload()
551 if( CheckWorking() ) {
552 Scan();
556 void BackupWindow::on_file_quit()
558 if( CheckWorking() ) {
559 hide();
563 void BackupWindow::on_help_about()
565 Gtk::AboutDialog dlg;
566 dlg.set_copyright("Copyright (C) 2007-2009, Net Direct Inc.");
567 dlg.set_license(
568 " This program is free software; you can redistribute it and/or modify\n"
569 " it under the terms of the GNU General Public License as published by\n"
570 " the Free Software Foundation; either version 2 of the License, or\n"
571 " (at your option) any later version.\n"
572 "\n"
573 " This program is distributed in the hope that it will be useful,\n"
574 " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
575 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
576 "\n"
577 " See the GNU General Public License in the COPYING file at the\n"
578 " root directory of this project for more details.\n");
580 std::vector<std::string> authors;
581 authors.push_back("Chris Frey <cdfrey@foursquare.net>");
582 authors.push_back("and Barry contributors. See AUTHORS file");
583 authors.push_back("for detailed contribution information.");
585 dlg.set_authors(authors);
587 int major, minor;
588 const char *BarryVersion = Barry::Version(major, minor);
589 dlg.set_name("Barry Backup");
590 dlg.set_version("0.17");
591 dlg.set_comments(std::string("Using library: ") + BarryVersion);
592 dlg.set_website("http://netdirect.ca/barry");
593 dlg.run();
596 bool BackupWindow::on_startup()
598 Scan();
599 return false;
602 bool BackupWindow::on_delete_event(GdkEventAny *)
604 if( CheckWorking() )
605 return false; // allow closing of window via window manager
606 else
607 return true; // stop the close