2 /// \file BackupWindow.cc
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"
27 #include <gtkmm/aboutdialog.h>
34 BackupWindow::BackupWindow(BaseObjectType
*cobject
,
35 const Glib::RefPtr
<Gnome::Glade::Xml
> &xml
)
36 : Gtk::Window(cobject
)
41 , m_pDisconnectButton(0)
50 m_signal_update
.connect(
51 sigc::mem_fun(*this, &BackupWindow::treeview_update
));
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
);
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();
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:");
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
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();
174 m_pDeviceSelection
->select(iter
);
177 bool BackupWindow::Connect(Thread
*thread
)
179 if( thread
->Connected() )
182 StatusbarSet("Connecting to Device...");
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
196 StatusbarSet("Connection cancelled.");
200 if( thread
->PasswordOutOfTries() )
202 Gtk::MessageDialog
msg(thread
->BadPasswordError());
204 StatusbarSet("Cannot connect to " + thread
->GetFullname() + ".");
208 else if( thread
->BadSize() ) {
211 std::cerr
<< thread
->BadSizeError() << std::endl
;
214 return Connect(thread
);
217 Gtk::MessageDialog
msg(thread
->BadSizeError());
219 StatusbarSet("Cannot connect to " + thread
->GetFullname() + ".");
224 Gtk::MessageDialog
msg(thread
->LastInterfaceError());
226 StatusbarSet("Cannot connect to " + thread
->GetFullname() + ".");
231 StatusbarSet("Connected to " + thread
->GetFullname() + ".");
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
)
245 if( thread
->Connected() ) {
246 thread
->Disconnect();
247 StatusbarSet("Disconnected from " + thread
->GetFullname() + ".");
250 StatusbarSet("Not connected.");
254 void BackupWindow::CheckDeviceName(Thread
*thread
)
256 if( !thread
->HasDeviceName() ) {
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());
263 thread
->SetDeviceName(" ");
265 if( !thread
->Save() ) {
266 Gtk::MessageDialog
msg("Error saving config: " +
267 thread
->LastConfigError());
273 Thread
*BackupWindow::GetActive()
275 Gtk::TreeModel::iterator row
= m_pDeviceSelection
->get_selected();
277 unsigned int id
= (*row
)[m_columns
.m_id
];
278 return m_threads
[id
].get();
285 void BackupWindow::StatusbarSet(const Glib::ustring
& text
)
287 guint remove_id
= m_last_status_id
;
289 m_last_status_id
= m_pStatusbar
->push(text
);
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()
300 for( unsigned int i
= 0; i
< m_device_count
; ++i
) {
301 if( m_threads
[i
]->Working() ) {
308 Gtk::MessageDialog
dialog(*this, "One or more devices are working, "
309 "disconnecting now may cause data corruption. "
311 false, Gtk::MESSAGE_QUESTION
, Gtk::BUTTONS_OK_CANCEL
);
312 if( dialog
.run() != Gtk::RESPONSE_OK
)
319 void BackupWindow::signal_exception_handler()
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());
333 // anything else, terminate window and pass on to next handler
334 // (which should be in main.cc)
341 //////////////////////////////////////////////////////////////////////////////
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
)
358 percentage
= 100 * finished
/ total
;
359 if( percentage
== 100 ) // never say 100% unless finished
362 (*i
)[m_columns
.m_percentage
] = percentage
;
363 if( thread
->CheckFinishedMarker() ) {
366 if( thread
->GetThreadState() & THREAD_STATE_BACKUP
)
368 else if( thread
->GetThreadState() & THREAD_STATE_RESTORE
)
373 StatusbarSet(op
+ " on " + thread
->GetFullname() + " finished!");
374 if( (thread
->GetThreadState() & THREAD_STATE_BACKUP
) &&
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());
393 void BackupWindow::on_backup()
395 Thread
*thread
= GetActive();
397 if( !thread
|| !Connect(thread
) )
401 if( thread
->Working() ) {
402 Gtk::MessageDialog
msg("Thread already in progress.");
407 // make sure our target directory exists
408 if( !::CheckPath(thread
->GetPath()) ) {
409 Gtk::MessageDialog
msg("Could not create directory: " + thread
->GetPath());
415 if( thread
->GetBackupList().size() == 0 ) {
416 Gtk::MessageDialog
msg("No databases selected in configuration.");
421 // prompt for a backup label, if so configured
422 std::string backupLabel
;
423 if( thread
->PromptBackupLabel() ) {
425 dlg
.SetPrompt("Please enter a label for this backup (blank is ok):");
426 if( dlg
.run() == Gtk::RESPONSE_OK
) {
427 backupLabel
= dlg
.GetAnswer();
436 if( !thread
->Backup(backupLabel
) ) {
437 Gtk::MessageDialog
msg("Error starting backup thread: " +
438 thread
->LastInterfaceError());
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();
464 if( result
!= Gtk::RESPONSE_OK
)
467 restoreFilename
= dlg
.get_filename();
471 void BackupWindow::on_restore()
473 Thread
*thread
= GetActive();
475 if( !thread
|| !Connect(thread
) )
479 if( thread
->Working() ) {
480 Gtk::MessageDialog
msg("Thread already in progress.");
485 std::string restoreFilename
;
486 if( !PromptForRestoreTarball(restoreFilename
, thread
->GetPath()) )
487 return; // nothing to do
490 // if( !thread->RestoreAndBackup(restoreFilename) ) {
491 if( !thread
->Restore(restoreFilename
) ) {
492 Gtk::MessageDialog
msg("Error starting restore thread: " +
493 thread
->LastInterfaceError());
497 StatusbarSet("Restore of " + thread
->GetFullname() + " in progress...");
501 void BackupWindow::on_disconnect()
503 Thread
*thread
= GetActive();
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();
518 m_pActive
->UnsetActive();
520 if( thread
&& Connect(thread
) )
524 void BackupWindow::on_config()
526 Thread
*thread
= GetActive();
528 if( !thread
|| !Connect(thread
) )
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());
544 StatusbarSet("Config saved successfully.");
546 thread
->LoadConfig();
549 void BackupWindow::on_reload()
551 if( CheckWorking() ) {
556 void BackupWindow::on_file_quit()
558 if( CheckWorking() ) {
563 void BackupWindow::on_help_about()
565 Gtk::AboutDialog dlg
;
566 dlg
.set_copyright("Copyright (C) 2007-2009, Net Direct Inc.");
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"
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"
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
);
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");
596 bool BackupWindow::on_startup()
602 bool BackupWindow::on_delete_event(GdkEventAny
*)
605 return false; // allow closing of window via window manager
607 return true; // stop the close