API + lib: removed StartParser() and EndParser() from Parser API
[barry.git] / gui / src / DeviceIface.cc
blob00e66db7ed749feec58bb534845303c9385fe7b7
1 ///
2 /// \file DeviceIface.cc
3 /// Interface class for device backup and restore
4 ///
6 /*
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 "DeviceIface.h"
23 #include "util.h"
24 #include "i18n.h"
25 #include <glibmm.h>
26 #include <iostream>
27 #include <iomanip>
28 #include <sstream>
29 #include <time.h>
30 #include <string.h>
31 #include <stdlib.h>
33 DeviceInterface::DeviceInterface(Device *dev)
34 : m_dev(dev)
35 , m_con(0)
36 , m_desktop(0)
37 , m_dbnameMutex(new Glib::Mutex) // this is just in an effort to
38 // avoid gtkmm headers in
39 // DeviceIface.h
40 , m_end_of_tar(false)
41 , m_tar_record_loaded(false)
42 , m_thread_quit(false)
46 DeviceInterface::~DeviceInterface()
48 Disconnect();
49 delete m_dbnameMutex;
52 bool DeviceInterface::False(const std::string &msg)
54 m_last_error = msg;
55 return false;
58 void DeviceInterface::BackupThread()
60 bool error = false;
61 m_thread_quit = false;
62 m_last_thread_error = "";
64 try {
65 // cycle through all database names in the dbList
66 // and store them all
67 Barry::ConfigFile::DBListType::const_iterator name = m_dbList.begin();
68 for( ; name != m_dbList.end(); ++name ) {
69 // save current db name
70 SetThreadDBName(*name);
71 // call the controller to do the work
72 unsigned int dbId = m_desktop->GetDBID(*name);
73 m_desktop->LoadDatabase(dbId, *this);
76 catch( Glib::Exception &e ) {
77 m_last_thread_error = e.what();
78 error = true;
80 catch( std::exception &e ) {
81 m_last_thread_error = e.what();
82 error = true;
84 catch( Quit &q ) {
85 m_last_thread_error = _("Terminated by user.");
88 m_tarback->Close();
89 m_tarback.reset();
91 if( error )
92 m_AppComm.m_error->emit();
94 // signal host thread that we're done
95 m_AppComm.m_done->emit();
97 // done!
98 m_AppComm.Invalidate();
101 void DeviceInterface::RestoreThread()
103 m_thread_quit = false;
104 m_last_thread_error = "";
106 try {
108 // cycle until m_end_of_tar
109 while( !m_end_of_tar ) {
110 try {
111 // call the controller to do the work
112 unsigned int dbId = m_desktop->GetDBID(m_current_dbname);
113 m_AppComm.m_erase_db->emit();
114 m_desktop->SaveDatabase(dbId, *this);
115 m_AppComm.m_restored_db->emit();
117 catch( Barry::Error &be ) {
118 // save thread error
119 m_last_thread_error = _("Error while restoring ");
120 m_last_thread_error += m_current_dbname + ". ";
121 m_last_thread_error += be.what();
122 m_last_thread_error += _(" Will continue processing.");
124 // notify host thread
125 m_AppComm.m_error->emit();
127 // skip over records from this db
128 std::cerr << _("Error on database: ")
129 << m_current_dbname << std::endl;
130 SkipCurrentDB();
135 catch( Glib::Exception &e ) {
136 m_last_thread_error = e.what();
137 m_AppComm.m_error->emit();
139 catch( std::exception &e ) {
140 m_last_thread_error = e.what();
141 m_AppComm.m_error->emit();
143 catch( Quit &q ) {
144 m_last_thread_error = _("Terminated by user.");
147 m_tar->Close();
148 m_tar.reset();
150 // signal host thread that we're done
151 m_AppComm.m_done->emit();
153 // done!
154 m_AppComm.Invalidate();
157 void DeviceInterface::RestoreAndBackupThread()
159 m_thread_quit = false;
160 m_last_thread_error = "";
162 try {
164 // cycle until m_end_of_tar
165 while( !m_end_of_tar ) {
166 unsigned int dbId = m_desktop->GetDBID(m_current_dbname);
168 try {
170 // do restore first
171 m_AppComm.m_erase_db->emit();
172 m_desktop->SaveDatabase(dbId, *this);
175 catch( Barry::Error &be ) {
176 // save thread error
177 m_last_thread_error = _("Error while restoring ");
178 m_last_thread_error += m_current_dbname + ". ";
179 m_last_thread_error += be.what();
180 m_last_thread_error += _(" Will continue processing.");
182 // notify host thread
183 m_AppComm.m_error->emit();
185 // skip over records from this db
186 std::cerr << _("Error on database: ")
187 << m_current_dbname << std::endl;
188 SkipCurrentDB();
191 // then the backup, even if restore fails
192 m_desktop->LoadDatabase(dbId, *this);
197 catch( Glib::Exception &e ) {
198 m_last_thread_error = e.what();
199 m_AppComm.m_error->emit();
201 catch( std::exception &e ) {
202 m_last_thread_error = e.what();
203 m_AppComm.m_error->emit();
205 catch( Quit &q ) {
206 m_last_thread_error = _("Terminated by user.");
209 m_tar->Close();
210 m_tar.reset();
211 m_tarback->Close();
212 m_tarback.reset();
214 // signal host thread that we're done
215 m_AppComm.m_done->emit();
217 // done!
218 m_AppComm.Invalidate();
221 std::string DeviceInterface::MakeFilename(const std::string &label) const
223 time_t t = time(NULL);
224 struct tm *lt = localtime(&t);
226 std::string fileLabel = label;
227 if( fileLabel.size() ) {
228 // prepend a hyphen
229 fileLabel.insert(fileLabel.begin(), '-');
231 // change all spaces in label to underscores
232 for( size_t i = 0; i < fileLabel.size(); i++ ) {
233 if( fileLabel[i] == ' ' )
234 fileLabel[i] = '_';
238 std::ostringstream tarfilename;
239 tarfilename << m_dev->GetPIN().str() << "-"
240 << std::setw(4) << std::setfill('0') << (lt->tm_year + 1900)
241 << std::setw(2) << std::setfill('0') << (lt->tm_mon + 1)
242 << std::setw(2) << std::setfill('0') << lt->tm_mday
243 << "-"
244 << std::setw(2) << std::setfill('0') << lt->tm_hour
245 << std::setw(2) << std::setfill('0') << lt->tm_min
246 << std::setw(2) << std::setfill('0') << lt->tm_sec
247 << fileLabel
248 << ".tar.gz";
249 return tarfilename.str();
252 int DeviceInterface::CountFiles(reuse::TarFile &tar,
253 const Barry::ConfigFile::DBListType &restoreList) const
255 int count = 0;
256 std::string name, last_name;
257 bool good = false;
259 while( tar.ReadNextFilenameOnly(name) ) {
260 std::string::size_type pos = name.rfind('/');
261 if( pos == std::string::npos )
262 continue; // bad name
263 std::string dbname = name.substr(0, pos);
265 if( dbname != last_name ) {
266 last_name = dbname;
267 good = restoreList.IsSelected(dbname);
269 if( good )
270 count++;
272 return count;
275 /// Splits a tarpath of the form "DBName/DBID" into separate string values.
276 /// Returns true if successful, false if tarpath is a bad name.
277 bool DeviceInterface::SplitTarPath(const std::string &tarpath,
278 std::string &dbname,
279 std::string &dbid_text,
280 uint8_t &dbrectype,
281 uint32_t &dbid) const
283 std::string::size_type pos = tarpath.rfind('/');
284 if( pos == std::string::npos )
285 return false; // bad name
287 dbname = tarpath.substr(0, pos);
288 dbid_text = tarpath.substr(pos + 1);
289 if( dbname.size() == 0 || dbid_text.size() == 0 )
290 return false; // bad name
292 std::istringstream iss(dbid_text);
293 unsigned int temp;
294 iss >> std::hex >> dbid >> temp;
295 dbrectype = (uint8_t) temp;
297 return true;
300 void DeviceInterface::SetThreadDBName(const std::string &dbname)
302 Glib::Mutex::Lock lock(*m_dbnameMutex);
303 m_current_dbname_not_thread_safe = dbname;
304 m_current_dbname = dbname;
307 //////////////////////////////////////////////////////////////////////////////
308 // Public API
310 void DeviceInterface::Reset()
312 Usb::Device dev(m_dev->result.m_dev);
313 dev.Reset();
316 bool DeviceInterface::Connect()
318 try {
319 Disconnect();
320 m_con = new Barry::Controller(m_dev->result);
321 m_desktop = new Barry::Mode::Desktop(*m_con);
322 m_desktop->Open();
323 return true;
325 catch( Barry::BadPassword & ) {
326 // pass on to the caller
327 throw;
329 catch( Barry::BadSize & ) {
330 // pass on to the caller
331 Disconnect();
332 throw;
334 catch( Barry::Error &e ) {
335 Disconnect();
336 return False(e.what());
340 bool DeviceInterface::Password(const char *password)
342 try {
343 m_desktop->RetryPassword(password);
344 return true;
346 catch( Barry::BadPassword & ) {
347 // pass on to the caller
348 throw;
350 catch( Barry::Error &e ) {
351 Disconnect();
352 return False(e.what());
356 void DeviceInterface::Disconnect()
358 delete m_desktop;
359 m_desktop = 0;
361 delete m_con;
362 m_con = 0;
365 // cycle through controller's DBDB and count the records in all the
366 // databases selected in the backupList
367 unsigned int DeviceInterface::GetRecordTotal(const Barry::ConfigFile::DBListType &backupList) const
369 unsigned int count = 0;
371 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator
372 i = m_desktop->GetDBDB().Databases.begin();
373 for( ; i != m_desktop->GetDBDB().Databases.end(); ++i ) {
374 if( backupList.IsSelected(i->Name) ) {
375 count += i->RecordCount;
378 return count;
381 unsigned int DeviceInterface::GetRecordTotal(const Barry::ConfigFile::DBListType &restoreList, const std::string &filename) const
383 unsigned int count = 0;
385 std::auto_ptr<reuse::TarFile> tar;
387 try {
388 // do a scan through the tar file
389 tar.reset( new reuse::TarFile(filename.c_str(), false, &reuse::gztar_ops_nonthread, true) );
390 count = CountFiles(*tar, restoreList);
392 catch( reuse::TarFile::TarError &te ) {
393 // just throw it away
395 return count;
398 /// returns name of database the thread is currently working on
399 std::string DeviceInterface::GetThreadDBName() const
401 Glib::Mutex::Lock lock(*m_dbnameMutex);
402 return m_current_dbname_not_thread_safe;
405 bool DeviceInterface::StartBackup(AppComm comm,
406 const Barry::ConfigFile::DBListType &backupList,
407 const std::string &directory,
408 const std::string &backupLabel)
410 if( m_AppComm.IsValid() )
411 return False(_("Thread already running."));
413 try {
414 std::string filename = directory + "/" + MakeFilename(backupLabel);
415 m_tarback.reset( new reuse::TarFile(filename.c_str(), true, &reuse::gztar_ops_nonthread, true) );
417 catch( reuse::TarFile::TarError &te ) {
418 return False(te.what());
421 // setup
422 m_AppComm = comm;
423 m_dbList = backupList;
425 // start the thread
426 Glib::Thread::create(sigc::mem_fun(*this, &DeviceInterface::BackupThread), false);
427 return true;
430 bool DeviceInterface::StartRestore(AppComm comm,
431 const Barry::ConfigFile::DBListType &restoreList,
432 const std::string &filename)
434 if( m_AppComm.IsValid() )
435 return False(_("Thread already running."));
437 try {
438 // open for the main restore
439 m_tar.reset( new reuse::TarFile(filename.c_str(), false, &reuse::gztar_ops_nonthread, true) );
442 catch( reuse::TarFile::TarError &te ) {
443 return False(te.what());
446 // setup
447 m_AppComm = comm;
448 m_dbList = restoreList;
449 m_current_dbname_not_thread_safe = "";
450 m_current_dbname = "";
451 m_unique_id = 0;
452 m_end_of_tar = false;
453 m_tar_record_loaded = false;
455 // get first tar record
456 Retrieve();
458 // start the thread
459 Glib::Thread::create(sigc::mem_fun(*this, &DeviceInterface::RestoreThread), false);
460 return true;
463 bool DeviceInterface::StartRestoreAndBackup(AppComm comm,
464 const Barry::ConfigFile::DBListType &restoreAndBackupList,
465 const std::string &filename,
466 const std::string &directory)
468 if( m_AppComm.IsValid() )
469 return False(_("Thread already running."));
471 try {
472 // open for the main restore
473 m_tar.reset( new reuse::TarFile(filename.c_str(), false, &reuse::gztar_ops_nonthread, true) );
475 // open for secondary backup
476 std::string back = directory + "/" + MakeFilename(m_dev->GetPIN().str());
477 m_tarback.reset( new reuse::TarFile(back.c_str(), true, &reuse::gztar_ops_nonthread, true) );
480 catch( reuse::TarFile::TarError &te ) {
481 return False(te.what());
484 // setup
485 m_AppComm = comm;
486 m_dbList = restoreAndBackupList;
487 m_current_dbname_not_thread_safe = "";
488 m_current_dbname = "";
489 m_unique_id = 0;
490 m_end_of_tar = false;
491 m_tar_record_loaded = false;
493 // get first tar record
494 Retrieve();
496 // start the thread
497 Glib::Thread::create(sigc::mem_fun(*this, &DeviceInterface::RestoreAndBackupThread), false);
498 return true;
503 //////////////////////////////////////////////////////////////////////////////
504 // Barry::Parser overrides
506 void DeviceInterface::ParseRecord(const Barry::DBData &data,
507 const Barry::IConverter *ic)
509 m_rec_type = data.GetRecType();
510 m_unique_id = data.GetUniqueId();
511 std::ostringstream oss;
512 oss << std::hex << m_unique_id << " " << (unsigned int)m_rec_type;
513 m_tar_id_text = oss.str();
514 if( m_tar_id_text.size() == 0 )
515 throw std::runtime_error(_("No unique ID available!"));
517 m_record_data.assign(
518 (const char*)data.GetData().GetData() + data.GetOffset(),
519 data.GetData().GetSize() - data.GetOffset());
521 // store in tarball
522 std::string tarname = m_current_dbname + "/" + m_tar_id_text;
523 m_tarback->AppendFile(tarname.c_str(), m_record_data);
525 m_AppComm.m_progress->emit();
527 // check quit flag
528 if( m_thread_quit ) {
529 throw Quit();
534 //////////////////////////////////////////////////////////////////////////////
535 // Barry::Builder overrides
537 bool DeviceInterface::Retrieve()
539 if( m_end_of_tar )
540 return false;
542 // if loaded, we are likely on a database
543 // boundary, and the last read crossed it, so don't load again
544 if( m_tar_record_loaded )
545 return true;
547 // search for a valid record
548 for(;;) {
549 // load record data from tar file
550 std::string filename;
551 if( !m_tar->ReadNextFile(filename, m_record_data) ) {
552 // assume end of file
553 m_end_of_tar = true;
554 return false;
556 m_tar_record_loaded = true;
558 // split record filename into dbname and ID
559 std::string dbname;
560 if( !SplitTarPath(filename, dbname, m_tar_id_text, m_rec_type, m_unique_id) ) {
561 // invalid filename, skip it
562 std::cerr << _("Skipping invalid tar record: ") << filename << std::endl;
563 continue;
566 // are we working on the same dbname as last time? if so, go ahead!
567 if( m_current_dbname == dbname ) {
568 return true;
571 // DIFFERENT DBNAME from here on down!
573 // does the filter allow this record? if not, skip it and continue
574 // looking
575 if( !m_dbList.IsSelected(dbname) ) {
576 continue;
579 // all checks pass, load the new dbname, and return false
580 // if we are on a dbname boundary
581 bool r_val = false;
582 if( m_current_dbname.size() == 0 ) {
583 // this is the first time through Retrieve, so ok
584 r_val = true;
587 SetThreadDBName(dbname);
588 return r_val;
592 bool DeviceInterface::BuildRecord(Barry::DBData &data, size_t &offset,
593 const Barry::IConverter *ic)
595 if( !Retrieve() )
596 return false;
598 // fill in the meta data
599 data.SetVersion(Barry::DBData::REC_VERSION_1);
600 data.SetDBName(m_current_dbname);
601 data.SetIds(m_rec_type, m_unique_id);
602 data.SetOffset(offset);
604 // fill in the record data
605 int packet_size = offset + m_record_data.size();
606 Barry::Data &block = data.UseData();
607 unsigned char *buf = block.GetBuffer(packet_size);
608 memcpy(buf + offset, m_record_data.data(), m_record_data.size());
609 offset += m_record_data.size();
610 block.ReleaseBuffer(packet_size);
612 // clear loaded flag, as it has now been used
613 m_tar_record_loaded = false;
614 m_AppComm.m_progress->emit();
615 return true;
618 bool DeviceInterface::FetchRecord(Barry::DBData &data,
619 const Barry::IConverter *ic)
621 size_t offset = 0;
622 return BuildRecord(data, offset, ic);
625 // helper function for halding restore errors
626 void DeviceInterface::SkipCurrentDB() throw()
628 // skip all records until next DB
629 try {
630 while( Retrieve() ) {
631 std::cerr << _("Skipping: ")
632 << m_current_dbname << "/"
633 << m_tar_id_text << std::endl;
634 m_tar_record_loaded = false;
637 catch( reuse::TarFile::TarError & ) {
638 m_end_of_tar = true;
640 catch( ... ) {
641 // swallow all other exceptions
642 std::cerr << "EXCEPTION IN SkipCurrentDB()! "
643 "Please report to Barry mailing list." << std::endl;
647 Device::Device(const Barry::ProbeResult &result)
648 : result(result)