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"
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();
145 // display any errors from the probe
146 if( m_bus
.GetFailCount() ) {
147 std::ostringstream oss
;
148 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");
149 for( int i
= 0; i
< m_bus
.GetFailCount(); i
++ ) {
150 oss
<< m_bus
.GetFailMsg(i
) << "\n";
153 Gtk::MessageDialog
msg(oss
.str());
157 m_device_count
= m_bus
.ProbeCount();
159 if( m_device_count
== 0 )
160 m_pDeviceLabel
->set_label(_("No devices."));
161 else if( m_device_count
== 1 )
162 m_pDeviceLabel
->set_label(_("1 device:"));
165 std::ostringstream oss
;
166 oss
<< m_device_count
<< _(" devices:");
167 m_pDeviceLabel
->set_label(oss
.str());
170 m_threads
.resize(m_device_count
);
171 for( unsigned int id
= 0; id
< m_device_count
; ++id
) {
172 Device dev
= m_bus
.Get(id
);
173 Gtk::TreeModel::iterator row
= m_pListStore
->append();
174 (*row
)[m_columns
.m_id
] = id
;
176 m_threads
[id
].reset(new Thread(dev
, &m_signal_update
));
179 // all devices loaded
182 StatusbarSet(_("All devices loaded."));
184 // if one or more device plugged in,
185 // activate the first one
186 Gtk::TreeModel::iterator iter
= m_pListStore
->children().begin();
188 m_pDeviceSelection
->select(iter
);
191 bool BackupWindow::Connect(Thread
*thread
)
193 if( thread
->Connected() )
196 StatusbarSet(_("Connecting to Device..."));
199 CheckDeviceName(thread
);
201 if( !thread
->Connect() ) {
202 if( thread
->PasswordRequired() ) {
203 bool connected
= false;
204 while( !connected
&& !thread
->PasswordOutOfTries() ) {
205 PasswordDlg
dlg(thread
->PasswordRemainingTries());
206 if( dlg
.run() == Gtk::RESPONSE_OK
) {
207 connected
= thread
->Connect(dlg
.GetPassword());
210 Gtk::MessageDialog
msg(thread
->LastInterfaceError());
212 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
216 else { // user cancelled
218 StatusbarSet(_("Connection cancelled."));
222 if( thread
->PasswordOutOfTries() )
224 Gtk::MessageDialog
msg(thread
->BadPasswordError());
226 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
230 else if( thread
->BadSize() ) {
233 std::cerr
<< thread
->BadSizeError() << std::endl
;
236 return Connect(thread
);
239 Gtk::MessageDialog
msg(thread
->BadSizeError());
241 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
246 Gtk::MessageDialog
msg(thread
->LastInterfaceError());
248 StatusbarSet(_("Cannot connect to ") + thread
->GetFullname() + ".");
253 StatusbarSet(_("Connected to ") + thread
->GetFullname() + ".");
257 void BackupWindow::Disconnect(Thread
*thread
)
259 if( thread
->Working() ) {
260 Gtk::MessageDialog
dialog(*this, thread
->GetFullname() + _(" is working, "
261 "disconnecting from it may cause data corruption, are you sure to proceed?"),
262 false, Gtk::MESSAGE_QUESTION
, Gtk::BUTTONS_OK_CANCEL
);
263 if( dialog
.run() == Gtk::RESPONSE_CANCEL
)
267 if( thread
->Connected() ) {
268 thread
->Disconnect();
269 StatusbarSet(_("Disconnected from ") + thread
->GetFullname() + ".");
272 StatusbarSet(_("Not connected."));
276 void BackupWindow::CheckDeviceName(Thread
*thread
)
278 if( !thread
->HasDeviceName() ) {
280 dlg
.SetPrompt(_("Unnamed device found (PIN: ") + thread
->GetPIN().Str() + ").\r\n " + _("Please enter a name for it:"));
281 if( dlg
.run() == Gtk::RESPONSE_OK
) {
282 thread
->SetDeviceName(dlg
.GetAnswer());
285 thread
->SetDeviceName(" ");
287 if( !thread
->Save() ) {
288 Gtk::MessageDialog
msg(_("Error saving config: ") +
289 thread
->LastConfigError());
295 Thread
*BackupWindow::GetActive()
297 Gtk::TreeModel::iterator row
= m_pDeviceSelection
->get_selected();
299 unsigned int id
= (*row
)[m_columns
.m_id
];
300 return m_threads
[id
].get();
307 void BackupWindow::StatusbarSet(const Glib::ustring
& text
)
309 guint remove_id
= m_last_status_id
;
311 m_last_status_id
= m_pStatusbar
->push(text
);
313 m_pStatusbar
->remove_message(remove_id
);
317 /// Returns true if ok to proceed (either nothing is currently 'working',
318 /// or the user confirmed to do it anyway)
319 bool BackupWindow::CheckWorking()
322 for( unsigned int i
= 0; i
< m_device_count
; ++i
) {
323 if( m_threads
[i
]->Working() ) {
330 Gtk::MessageDialog
dialog(*this, _("One or more devices are working, "
331 "disconnecting now may cause data corruption. "
333 false, Gtk::MESSAGE_QUESTION
, Gtk::BUTTONS_OK_CANCEL
);
334 if( dialog
.run() != Gtk::RESPONSE_OK
)
341 void BackupWindow::signal_exception_handler()
346 catch( Glib::Exception
&e
) {
347 // This usually just means a missing .glade file,
348 // so we try to carry on.
349 std::cerr
<< "Glib::Exception caught in main: " << std::endl
;
350 std::cerr
<< e
.what() << std::endl
;
351 Gtk::MessageDialog
msg(e
.what());
355 // anything else, terminate window and pass on to next handler
356 // (which should be in main.cc)
363 //////////////////////////////////////////////////////////////////////////////
366 void BackupWindow::treeview_update()
368 for( Gtk::TreeModel::iterator i
= m_pListStore
->children().begin();
369 i
!= m_pListStore
->children().end(); ++i
) {
370 unsigned int id
= (*i
)[m_columns
.m_id
];
371 Thread
*thread
= m_threads
[id
].get();
372 (*i
)[m_columns
.m_pin
] = thread
->GetPIN().Str();
373 (*i
)[m_columns
.m_name
] = thread
->GetDeviceName();
374 (*i
)[m_columns
.m_status
] = thread
->Status();
375 unsigned int finished(thread
->GetRecordFinished()), total(thread
->GetRecordTotal());
376 unsigned int percentage(0);
377 if( total
== 0 || finished
== total
)
380 percentage
= 100 * finished
/ total
;
381 if( percentage
== 100 ) // never say 100% unless finished
384 (*i
)[m_columns
.m_percentage
] = percentage
;
385 if( thread
->CheckFinishedMarker() ) {
388 if( thread
->GetThreadState() & THREAD_STATE_BACKUP
)
390 else if( thread
->GetThreadState() & THREAD_STATE_RESTORE
)
391 op
= _("Restore on");
393 op
= _("Operation on");
395 StatusbarSet(op
+ thread
->GetFullname() + _(" finished!"));
397 // thread really finished, so force percentage to 100
398 (*i
)[m_columns
.m_percentage
] = 100;
400 if( (thread
->GetThreadState() & THREAD_STATE_BACKUP
) &&
403 if( finished
< total
) {
404 // in some cases, not all records are backed
405 // up, possibly due to international chars
406 // in the Blackberry data which somehow
407 // forces the device to use a different
408 // low level protocol... here we need to
409 // warn the user that not all data was
410 // included in the backup
411 std::ostringstream oss
;
412 oss
<< _("Warning\n\nNot all records were processed on device: ") << thread
->GetFullname()
413 << _("\n\nOnly ") << finished
<< _(" of ") << total
414 << _(" 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.");
416 oss
<< _("\n\nSummary:\n");
417 std::vector
<std::string
> msgs
= thread
->CompareTotals();
418 for( std::vector
<std::string
>::const_iterator i
= msgs
.begin() ; i
!= msgs
.end(); ++i
)
420 Gtk::MessageDialog
msg(oss
.str());
423 else if( finished
> total
) {
424 std::ostringstream oss
;
425 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");
426 std::vector
<std::string
> msgs
= thread
->CompareTotals();
427 for( std::vector
<std::string
>::const_iterator i
= msgs
.begin() ; i
!= msgs
.end(); ++i
)
429 Gtk::MessageDialog
msg(oss
.str());
437 void BackupWindow::on_backup()
439 Thread
*thread
= GetActive();
441 if( !thread
|| !Connect(thread
) )
445 if( thread
->Working() ) {
446 Gtk::MessageDialog
msg(_("Thread already in progress."));
451 // make sure our target directory exists
452 if( !Barry::ConfigFile::CheckPath(thread
->GetPath()) ) {
453 Gtk::MessageDialog
msg(_("Could not create directory: ") + thread
->GetPath());
459 if( thread
->GetBackupList().size() == 0 && !thread
->AutoSelectAll() ) {
460 Gtk::MessageDialog
msg(_("No databases selected in configuration."));
465 // prompt for a backup label, if so configured
466 std::string backupLabel
;
467 if( thread
->PromptBackupLabel() ) {
469 dlg
.SetPrompt(_("Please enter a label for this backup (blank is ok):"));
470 if( dlg
.run() == Gtk::RESPONSE_OK
) {
471 backupLabel
= dlg
.GetAnswer();
480 if( !thread
->Backup(backupLabel
) ) {
481 Gtk::MessageDialog
msg(_("Error starting backup thread: ") +
482 thread
->LastInterfaceError());
486 StatusbarSet(_("Backup of ") + thread
->GetFullname() + _(" in progress..."));
490 bool BackupWindow::PromptForRestoreTarball(std::string
&restoreFilename
,
491 const std::string
&start_path
)
493 char buffer
[PATH_MAX
];
494 char *buf
= getcwd(buffer
, PATH_MAX
);
496 // start at the base path given... if it fails, just open
497 // the dialog where we are
498 (void)chdir(start_path
.c_str());
500 Gtk::FileChooserDialog
dlg(*this, _("Select backup to restore from"));
501 dlg
.add_button(Gtk::Stock::OK
, Gtk::RESPONSE_OK
);
502 dlg
.add_button(Gtk::Stock::CANCEL
, Gtk::RESPONSE_CANCEL
);
503 int result
= dlg
.run();
508 if( result
!= Gtk::RESPONSE_OK
)
511 restoreFilename
= dlg
.get_filename();
515 void BackupWindow::on_restore()
517 Thread
*thread
= GetActive();
519 if( !thread
|| !Connect(thread
) )
523 if( thread
->Working() ) {
524 Gtk::MessageDialog
msg(_("Thread already in progress."));
529 std::string restoreFilename
;
530 if( !PromptForRestoreTarball(restoreFilename
, thread
->GetPath()) )
531 return; // nothing to do
534 if( !thread
->Restore(restoreFilename
) ) {
535 Gtk::MessageDialog
msg(_("Error starting restore thread: ") +
536 thread
->LastInterfaceError());
540 StatusbarSet(_("Restore of ") + thread
->GetFullname() + _(" in progress..."));
544 void BackupWindow::on_disconnect()
546 Thread
*thread
= GetActive();
551 void BackupWindow::on_disconnect_all()
553 for( unsigned int i
= 0; i
< m_device_count
; ++i
)
554 Disconnect(m_threads
[i
].get());
557 void BackupWindow::on_device_change()
559 Thread
*thread
= GetActive();
561 m_pActive
->UnsetActive();
563 if( thread
&& Connect(thread
) )
567 void BackupWindow::on_config()
569 Thread
*thread
= GetActive();
571 if( !thread
|| !Connect(thread
) )
574 thread
->LoadConfig();
576 ConfigDlg
dlg(thread
->GetDBDB(), *thread
);
577 if( dlg
.run() == Gtk::RESPONSE_OK
) {
578 thread
->SetBackupList(dlg
.GetBackupList());
579 thread
->SetRestoreList(dlg
.GetRestoreList());
580 thread
->SetDeviceName(dlg
.GetDeviceName());
581 thread
->SetBackupPath(dlg
.GetBackupPath());
582 thread
->SetPromptBackupLabel(dlg
.GetPromptBackupLabel());
583 thread
->SetAutoSelectAll(dlg
.GetAutoSelectAll());
584 if( !thread
->Save() )
585 StatusbarSet(_("Error saving config: ") +
586 thread
->LastConfigError());
588 StatusbarSet(_("Config saved successfully."));
590 thread
->LoadConfig();
593 void BackupWindow::on_reload()
595 if( CheckWorking() ) {
600 void BackupWindow::on_file_quit()
602 if( CheckWorking() ) {
607 void BackupWindow::on_help_about()
609 Gtk::AboutDialog dlg
;
610 dlg
.set_copyright("Copyright (C) 2007-2012, Net Direct Inc.");
612 " This program is free software; you can redistribute it and/or modify\n"
613 " it under the terms of the GNU General Public License as published by\n"
614 " the Free Software Foundation; either version 2 of the License, or\n"
615 " (at your option) any later version.\n"
617 " This program is distributed in the hope that it will be useful,\n"
618 " but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
619 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
621 " See the GNU General Public License in the COPYING file at the\n"
622 " root directory of this project for more details.\n");
624 std::vector
<std::string
> authors
;
625 authors
.push_back("Chris Frey <cdfrey@foursquare.net>");
626 authors
.push_back(_("and Barry contributors. See AUTHORS file"));
627 authors
.push_back(_("for detailed contribution information."));
629 dlg
.set_authors(authors
);
631 int logical
, major
, minor
;
632 const char *BarryVersion
= Barry::Version(logical
, major
, minor
);
633 dlg
.set_name("Barry Backup");
634 dlg
.set_version("0.18.0");
635 dlg
.set_comments(std::string(_("Using library: ")) + BarryVersion
);
636 dlg
.set_website("http://netdirect.ca/barry");
640 bool BackupWindow::on_startup()
646 bool BackupWindow::on_delete_event(GdkEventAny
*)
649 return false; // allow closing of window via window manager
651 return true; // stop the close