2 /// \file BackupWindow.cc
7 Copyright (C) 2007-2010, 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"
28 #include <gtkmm/aboutdialog.h>
35 BackupWindow::BackupWindow(BaseObjectType
*cobject
,
36 const Glib::RefPtr
<Gnome::Glade::Xml
> &xml
)
37 : Gtk::Window(cobject
)
42 , m_pDisconnectButton(0)
51 m_signal_update
.connect(
52 sigc::mem_fun(*this, &BackupWindow::treeview_update
));
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
);
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();
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:"));
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
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();
175 m_pDeviceSelection
->select(iter
);
178 bool BackupWindow::Connect(Thread
*thread
)
180 if( thread
->Connected() )
183 StatusbarSet(_("Connecting to Device..."));
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
197 StatusbarSet(_("Connection cancelled."));
201 if( thread
->PasswordOutOfTries() )
203 Gtk::MessageDialog
msg(thread
->BadPasswordError());
205 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
209 else if( thread
->BadSize() ) {
212 std::cerr
<< thread
->BadSizeError() << std::endl
;
215 return Connect(thread
);
218 Gtk::MessageDialog
msg(thread
->BadSizeError());
220 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
225 Gtk::MessageDialog
msg(thread
->LastInterfaceError());
227 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
232 StatusbarSet(_("Connected to ") + thread
->GetFullname() + ".");
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
)
246 if( thread
->Connected() ) {
247 thread
->Disconnect();
248 StatusbarSet(_("Disconnected from ") + thread
->GetFullname() + ".");
251 StatusbarSet(_("Not connected."));
255 void BackupWindow::CheckDeviceName(Thread
*thread
)
257 if( !thread
->HasDeviceName() ) {
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());
264 thread
->SetDeviceName(" ");
266 if( !thread
->Save() ) {
267 Gtk::MessageDialog
msg(_("Error saving config: ") +
268 thread
->LastConfigError());
274 Thread
*BackupWindow::GetActive()
276 Gtk::TreeModel::iterator row
= m_pDeviceSelection
->get_selected();
278 unsigned int id
= (*row
)[m_columns
.m_id
];
279 return m_threads
[id
].get();
286 void BackupWindow::StatusbarSet(const Glib::ustring
& text
)
288 guint remove_id
= m_last_status_id
;
290 m_last_status_id
= m_pStatusbar
->push(text
);
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()
301 for( unsigned int i
= 0; i
< m_device_count
; ++i
) {
302 if( m_threads
[i
]->Working() ) {
309 Gtk::MessageDialog
dialog(*this, _("One or more devices are working, "
310 "disconnecting now may cause data corruption. "
312 false, Gtk::MESSAGE_QUESTION
, Gtk::BUTTONS_OK_CANCEL
);
313 if( dialog
.run() != Gtk::RESPONSE_OK
)
320 void BackupWindow::signal_exception_handler()
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());
334 // anything else, terminate window and pass on to next handler
335 // (which should be in main.cc)
342 //////////////////////////////////////////////////////////////////////////////
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
)
359 percentage
= 100 * finished
/ total
;
360 if( percentage
== 100 ) // never say 100% unless finished
363 (*i
)[m_columns
.m_percentage
] = percentage
;
364 if( thread
->CheckFinishedMarker() ) {
367 if( thread
->GetThreadState() & THREAD_STATE_BACKUP
)
369 else if( thread
->GetThreadState() & THREAD_STATE_RESTORE
)
370 op
= _("Restore on");
372 op
= _("Operation on");
374 StatusbarSet(op
+ thread
->GetFullname() + _(" finished!"));
375 if( (thread
->GetThreadState() & THREAD_STATE_BACKUP
) &&
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());
396 void BackupWindow::on_backup()
398 Thread
*thread
= GetActive();
400 if( !thread
|| !Connect(thread
) )
404 if( thread
->Working() ) {
405 Gtk::MessageDialog
msg(_("Thread already in progress."));
410 // make sure our target directory exists
411 if( !Barry::ConfigFile::CheckPath(thread
->GetPath()) ) {
412 Gtk::MessageDialog
msg(_("Could not create directory: ") + thread
->GetPath());
418 if( thread
->GetBackupList().size() == 0 ) {
419 Gtk::MessageDialog
msg(_("No databases selected in configuration."));
424 // prompt for a backup label, if so configured
425 std::string backupLabel
;
426 if( thread
->PromptBackupLabel() ) {
428 dlg
.SetPrompt(_("Please enter a label for this backup (blank is ok):"));
429 if( dlg
.run() == Gtk::RESPONSE_OK
) {
430 backupLabel
= dlg
.GetAnswer();
439 if( !thread
->Backup(backupLabel
) ) {
440 Gtk::MessageDialog
msg(_("Error starting backup thread: ") +
441 thread
->LastInterfaceError());
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();
467 if( result
!= Gtk::RESPONSE_OK
)
470 restoreFilename
= dlg
.get_filename();
474 void BackupWindow::on_restore()
476 Thread
*thread
= GetActive();
478 if( !thread
|| !Connect(thread
) )
482 if( thread
->Working() ) {
483 Gtk::MessageDialog
msg(_("Thread already in progress."));
488 std::string restoreFilename
;
489 if( !PromptForRestoreTarball(restoreFilename
, thread
->GetPath()) )
490 return; // nothing to do
493 if( !thread
->Restore(restoreFilename
) ) {
494 Gtk::MessageDialog
msg(_("Error starting restore thread: ") +
495 thread
->LastInterfaceError());
499 StatusbarSet(_("Restore of ") + thread
->GetFullname() + _(" in progress..."));
503 void BackupWindow::on_disconnect()
505 Thread
*thread
= GetActive();
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();
520 m_pActive
->UnsetActive();
522 if( thread
&& Connect(thread
) )
526 void BackupWindow::on_config()
528 Thread
*thread
= GetActive();
530 if( !thread
|| !Connect(thread
) )
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());
546 StatusbarSet(_("Config saved successfully."));
548 thread
->LoadConfig();
551 void BackupWindow::on_reload()
553 if( CheckWorking() ) {
558 void BackupWindow::on_file_quit()
560 if( CheckWorking() ) {
565 void BackupWindow::on_help_about()
567 Gtk::AboutDialog dlg
;
568 dlg
.set_copyright("Copyright (C) 2007-2010, Net Direct Inc.");
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"
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"
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
);
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");
598 bool BackupWindow::on_startup()
604 bool BackupWindow::on_delete_event(GdkEventAny
*)
607 return false; // allow closing of window via window manager
609 return true; // stop the close