2 /// \file BackupWindow.cc
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"
29 #include <gtkmm/aboutdialog.h>
36 BackupWindow::BackupWindow(BaseObjectType
*cobject
,
37 const Glib::RefPtr
<Gnome::Glade::Xml
> &xml
)
38 : Gtk::Window(cobject
)
43 , m_pDisconnectButton(0)
52 m_signal_update
.connect(
53 sigc::mem_fun(*this, &BackupWindow::treeview_update
));
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
);
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();
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());
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:"));
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
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();
189 m_pDeviceSelection
->select(iter
);
192 bool BackupWindow::Connect(Thread
*thread
)
194 if( thread
->Connected() )
197 StatusbarSet(_("Connecting to Device..."));
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());
211 Gtk::MessageDialog
msg(thread
->LastInterfaceError());
213 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
217 else { // user cancelled
219 StatusbarSet(_("Connection cancelled."));
223 if( thread
->PasswordOutOfTries() )
225 Gtk::MessageDialog
msg(thread
->BadPasswordError());
227 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
231 else if( thread
->BadSize() ) {
234 std::cerr
<< thread
->BadSizeError() << std::endl
;
237 return Connect(thread
);
240 Gtk::MessageDialog
msg(thread
->BadSizeError());
242 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
247 Gtk::MessageDialog
msg(thread
->LastInterfaceError());
249 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
254 StatusbarSet(_("Connected to ") + thread
->GetFullname() + ".");
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
)
268 if( thread
->Connected() ) {
269 thread
->Disconnect();
270 StatusbarSet(_("Disconnected from ") + thread
->GetFullname() + ".");
273 StatusbarSet(_("Not connected."));
277 void BackupWindow::CheckDeviceName(Thread
*thread
)
279 if( !thread
->HasDeviceName() ) {
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());
286 thread
->SetDeviceName(" ");
288 if( !thread
->Save() ) {
289 Gtk::MessageDialog
msg(_("Error saving config: ") +
290 thread
->LastConfigError());
296 Thread
*BackupWindow::GetActive()
298 Gtk::TreeModel::iterator row
= m_pDeviceSelection
->get_selected();
300 unsigned int id
= (*row
)[m_columns
.m_id
];
301 return m_threads
[id
].get();
308 void BackupWindow::StatusbarSet(const Glib::ustring
& text
)
310 guint remove_id
= m_last_status_id
;
312 m_last_status_id
= m_pStatusbar
->push(text
);
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()
323 for( unsigned int i
= 0; i
< m_device_count
; ++i
) {
324 if( m_threads
[i
]->Working() ) {
331 Gtk::MessageDialog
dialog(*this, _("One or more devices are working, "
332 "disconnecting now may cause data corruption. "
334 false, Gtk::MESSAGE_QUESTION
, Gtk::BUTTONS_OK_CANCEL
);
335 if( dialog
.run() != Gtk::RESPONSE_OK
)
342 void BackupWindow::signal_exception_handler()
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());
356 // anything else, terminate window and pass on to next handler
357 // (which should be in main.cc)
364 //////////////////////////////////////////////////////////////////////////////
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
)
381 percentage
= 100 * finished
/ total
;
382 if( percentage
== 100 ) // never say 100% unless finished
385 (*i
)[m_columns
.m_percentage
] = percentage
;
386 if( thread
->CheckFinishedMarker() ) {
389 if( thread
->GetThreadState() & THREAD_STATE_BACKUP
)
391 else if( thread
->GetThreadState() & THREAD_STATE_RESTORE
)
392 op
= _("Restore on");
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
) &&
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
)
421 Gtk::MessageDialog
msg(oss
.str());
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
)
430 Gtk::MessageDialog
msg(oss
.str());
438 void BackupWindow::on_backup()
440 Thread
*thread
= GetActive();
442 if( !thread
|| !Connect(thread
) )
446 if( thread
->Working() ) {
447 Gtk::MessageDialog
msg(_("Thread already in progress."));
452 // make sure our target directory exists
453 if( !Barry::ConfigFile::CheckPath(thread
->GetPath()) ) {
454 Gtk::MessageDialog
msg(_("Could not create directory: ") + thread
->GetPath());
460 if( thread
->GetBackupList().size() == 0 && !thread
->AutoSelectAll() ) {
461 Gtk::MessageDialog
msg(_("No databases selected in configuration."));
466 // prompt for a backup label, if so configured
467 std::string backupLabel
;
468 if( thread
->PromptBackupLabel() ) {
470 dlg
.SetPrompt(_("Please enter a label for this backup (blank is ok):"));
471 if( dlg
.run() == Gtk::RESPONSE_OK
) {
472 backupLabel
= dlg
.GetAnswer();
481 if( !thread
->Backup(backupLabel
) ) {
482 Gtk::MessageDialog
msg(_("Error starting backup thread: ") +
483 thread
->LastInterfaceError());
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();
509 if( result
!= Gtk::RESPONSE_OK
)
512 restoreFilename
= dlg
.get_filename();
516 void BackupWindow::on_restore()
518 Thread
*thread
= GetActive();
520 if( !thread
|| !Connect(thread
) )
524 if( thread
->Working() ) {
525 Gtk::MessageDialog
msg(_("Thread already in progress."));
530 std::string restoreFilename
;
531 if( !PromptForRestoreTarball(restoreFilename
, thread
->GetPath()) )
532 return; // nothing to do
535 if( !thread
->Restore(restoreFilename
) ) {
536 Gtk::MessageDialog
msg(_("Error starting restore thread: ") +
537 thread
->LastInterfaceError());
541 StatusbarSet(_("Restore of ") + thread
->GetFullname() + _(" in progress..."));
545 void BackupWindow::on_disconnect()
547 Thread
*thread
= GetActive();
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();
562 m_pActive
->UnsetActive();
564 if( thread
&& Connect(thread
) )
568 void BackupWindow::on_config()
570 Thread
*thread
= GetActive();
572 if( !thread
|| !Connect(thread
) )
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());
589 StatusbarSet(_("Config saved successfully."));
591 thread
->LoadConfig();
594 void BackupWindow::on_reload()
596 if( CheckWorking() ) {
601 void BackupWindow::on_file_quit()
603 if( CheckWorking() ) {
608 void BackupWindow::on_help_about()
610 Gtk::AboutDialog dlg
;
611 dlg
.set_copyright("Copyright (C) 2007-2012, Net Direct Inc.");
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"
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"
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");
641 bool BackupWindow::on_startup()
647 bool BackupWindow::on_delete_event(GdkEventAny
*)
650 return false; // allow closing of window via window manager
652 return true; // stop the close