- added GCC visibility support, limiting what symbols are visible
[barry.git] / gui / src / DeviceIface.cc
bloba448306fa21f91a455b7239d434a256c1ad82a82
1 ///
2 /// \file DeviceIface.cc
3 /// Interface class for device backup and restore
4 ///
6 /*
7 Copyright (C) 2007-2008, 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 <glibmm.h>
25 #include <iostream>
26 #include <iomanip>
27 #include <sstream>
28 #include <time.h>
29 #include <stdlib.h>
31 DeviceInterface::DeviceInterface()
32 : m_con(0)
33 , m_dbnameMutex(new Glib::Mutex) // this is just in an effort to
34 // avoid gtkmm headers in
35 // DeviceIface.h
36 , m_end_of_tar(false)
37 , m_tar_record_loaded(false)
38 , m_thread_quit(false)
42 DeviceInterface::~DeviceInterface()
44 delete m_con;
45 delete m_dbnameMutex;
48 bool DeviceInterface::False(const std::string &msg)
50 m_last_error = msg;
51 return false;
54 void DeviceInterface::BackupThread()
56 bool error = false;
57 m_thread_quit = false;
58 m_last_thread_error = "";
60 try {
62 // cycle through all database names in the dbList
63 // and store them all
64 ConfigFile::DBListType::const_iterator name = m_dbList.begin();
65 for( ; name != m_dbList.end(); ++name ) {
66 // save current db name
67 SetThreadDBName(*name);
69 // call the controller to do the work
70 unsigned int dbId = m_con->GetDBID(*name);
71 m_con->LoadDatabase(dbId, *this);
75 catch( Glib::Exception &e ) {
76 m_last_thread_error = e.what();
77 error = true;
79 catch( std::exception &e ) {
80 m_last_thread_error = e.what();
81 error = true;
83 catch( Quit &q ) {
84 m_last_thread_error = "Terminated by user.";
87 m_tarback->Close();
88 m_tarback.reset();
90 if( error )
91 m_AppComm.m_error->emit();
93 // signal host thread that we're done
94 m_AppComm.m_done->emit();
96 // done!
97 m_AppComm.Invalidate();
100 void DeviceInterface::RestoreThread()
102 m_thread_quit = false;
103 m_last_thread_error = "";
105 try {
107 // cycle until m_end_of_tar
108 while( !m_end_of_tar ) {
109 try {
110 // call the controller to do the work
111 unsigned int dbId = m_con->GetDBID(m_current_dbname);
112 m_AppComm.m_erase_db->emit();
113 m_con->SaveDatabase(dbId, *this);
115 catch( Barry::Error &be ) {
116 // save thread error
117 m_last_thread_error = "Error while restoring ";
118 m_last_thread_error += m_current_dbname + ". ";
119 m_last_thread_error += be.what();
120 m_last_thread_error += " Will continue processing.";
122 // notify host thread
123 m_AppComm.m_error->emit();
125 // skip over records from this db
126 std::cerr << "Error on database: "
127 << m_current_dbname << std::endl;
128 SkipCurrentDB();
133 catch( Glib::Exception &e ) {
134 m_last_thread_error = e.what();
135 m_AppComm.m_error->emit();
137 catch( std::exception &e ) {
138 m_last_thread_error = e.what();
139 m_AppComm.m_error->emit();
141 catch( Quit &q ) {
142 m_last_thread_error = "Terminated by user.";
145 m_tar->Close();
146 m_tar.reset();
148 // signal host thread that we're done
149 m_AppComm.m_done->emit();
151 // done!
152 m_AppComm.Invalidate();
155 void DeviceInterface::RestoreAndBackupThread()
157 m_thread_quit = false;
158 m_last_thread_error = "";
160 try {
162 // cycle until m_end_of_tar
163 while( !m_end_of_tar ) {
164 unsigned int dbId = m_con->GetDBID(m_current_dbname);
166 try {
168 // do restore first
169 m_AppComm.m_erase_db->emit();
170 m_con->SaveDatabase(dbId, *this);
173 catch( Barry::Error &be ) {
174 // save thread error
175 m_last_thread_error = "Error while restoring ";
176 m_last_thread_error += m_current_dbname + ". ";
177 m_last_thread_error += be.what();
178 m_last_thread_error += " Will continue processing.";
180 // notify host thread
181 m_AppComm.m_error->emit();
183 // skip over records from this db
184 std::cerr << "Error on database: "
185 << m_current_dbname << std::endl;
186 SkipCurrentDB();
189 // then the backup, even if restore fails
190 m_con->LoadDatabase(dbId, *this);
195 catch( Glib::Exception &e ) {
196 m_last_thread_error = e.what();
197 m_AppComm.m_error->emit();
199 catch( std::exception &e ) {
200 m_last_thread_error = e.what();
201 m_AppComm.m_error->emit();
203 catch( Quit &q ) {
204 m_last_thread_error = "Terminated by user.";
207 m_tar->Close();
208 m_tar.reset();
209 m_tarback->Close();
210 m_tarback.reset();
212 // signal host thread that we're done
213 m_AppComm.m_done->emit();
215 // done!
216 m_AppComm.Invalidate();
219 std::string DeviceInterface::MakeFilename(const std::string &pin)
221 time_t t = time(NULL);
222 struct tm *lt = localtime(&t);
224 std::ostringstream tarfilename;
225 tarfilename << pin << "-"
226 << std::setw(4) << std::setfill('0') << (lt->tm_year + 1900)
227 << std::setw(2) << std::setfill('0') << (lt->tm_mon + 1)
228 << std::setw(2) << std::setfill('0') << lt->tm_mday
229 << "-"
230 << std::setw(2) << std::setfill('0') << lt->tm_hour
231 << std::setw(2) << std::setfill('0') << lt->tm_min
232 << std::setw(2) << std::setfill('0') << lt->tm_sec
233 << ".tar.gz";
234 return tarfilename.str();
237 int DeviceInterface::CountFiles(reuse::TarFile &tar,
238 const ConfigFile::DBListType &restoreList)
240 int count = 0;
241 std::string name, last_name;
242 bool good = false;
244 while( tar.ReadNextFilenameOnly(name) ) {
245 std::string::size_type pos = name.rfind('/');
246 if( pos == std::string::npos )
247 continue; // bad name
248 std::string dbname = name.substr(0, pos);
250 if( dbname != last_name ) {
251 last_name = dbname;
252 good = restoreList.IsSelected(dbname);
254 if( good )
255 count++;
257 return count;
260 /// Splits a tarpath of the form "DBName/DBID" into separate string values.
261 /// Returns true if successful, false if tarpath is a bad name.
262 bool DeviceInterface::SplitTarPath(const std::string &tarpath,
263 std::string &dbname,
264 std::string &dbid_text,
265 uint8_t &dbrectype,
266 uint32_t &dbid)
268 std::string::size_type pos = tarpath.rfind('/');
269 if( pos == std::string::npos )
270 return false; // bad name
272 dbname = tarpath.substr(0, pos);
273 dbid_text = tarpath.substr(pos + 1);
274 if( dbname.size() == 0 || dbid_text.size() == 0 )
275 return false; // bad name
277 std::istringstream iss(dbid_text);
278 unsigned int temp;
279 iss >> std::hex >> dbid >> temp;
280 dbrectype = (uint8_t) temp;
282 return true;
285 void DeviceInterface::SetThreadDBName(const std::string &dbname)
287 Glib::Mutex::Lock lock(*m_dbnameMutex);
288 m_current_dbname_not_thread_safe = dbname;
289 m_current_dbname = dbname;
293 //////////////////////////////////////////////////////////////////////////////
294 // Public API
296 bool DeviceInterface::Connect(const Barry::ProbeResult &dev)
298 try {
299 Disconnect();
300 m_con = new Barry::Controller(dev);
301 m_con->OpenMode(Barry::Controller::Desktop);
302 return true;
304 catch( Barry::BadPassword & ) {
305 // pass on to the caller
306 throw;
308 catch( Barry::BadSize & ) {
309 // pass on to the caller
310 Disconnect();
311 throw;
313 catch( Barry::Error &e ) {
314 Disconnect();
315 return False(e.what());
319 bool DeviceInterface::Password(const char *password)
321 try {
322 m_con->RetryPassword(password);
323 return true;
325 catch( Barry::BadPassword & ) {
326 // pass on to the caller
327 throw;
329 catch( Barry::Error &e ) {
330 Disconnect();
331 return False(e.what());
335 void DeviceInterface::Disconnect()
337 delete m_con;
338 m_con = 0;
341 // cycle through controller's DBDB and count the records in all the
342 // databases selected in the backupList
343 int DeviceInterface::GetDeviceRecordTotal(const ConfigFile::DBListType &backupList) const
345 int count = 0;
347 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator
348 i = m_con->GetDBDB().Databases.begin();
349 for( ; i != m_con->GetDBDB().Databases.end(); ++i ) {
350 if( backupList.IsSelected(i->Name) ) {
351 count += i->RecordCount;
354 return count;
357 /// returns name of database the thread is currently working on
358 std::string DeviceInterface::GetThreadDBName() const
360 Glib::Mutex::Lock lock(*m_dbnameMutex);
361 return m_current_dbname_not_thread_safe;
364 bool DeviceInterface::StartBackup(AppComm comm,
365 const ConfigFile::DBListType &backupList,
366 const std::string &directory,
367 const std::string &pin)
369 if( m_AppComm.IsValid() )
370 return False("Thread already running.");
372 try {
374 std::string filename = directory + "/" + MakeFilename(pin);
375 m_tarback.reset( new reuse::TarFile(filename.c_str(), true, &reuse::gztar_ops_nonthread, true) );
378 catch( reuse::TarFile::TarError &te ) {
379 return False(te.what());
382 // setup
383 m_AppComm = comm;
384 m_dbList = backupList;
386 // start the thread
387 Glib::Thread::create(sigc::mem_fun(*this, &DeviceInterface::BackupThread), false);
388 return true;
391 bool DeviceInterface::StartRestore(AppComm comm,
392 const ConfigFile::DBListType &restoreList,
393 const std::string &filename,
394 int *pRecordCount)
396 if( m_AppComm.IsValid() )
397 return False("Thread already running.");
399 try {
400 if( pRecordCount ) {
401 // caller is asking for a total, so we do a quick
402 // scan through the tar file first
403 m_tar.reset( new reuse::TarFile(filename.c_str(), false, &reuse::gztar_ops_nonthread, true) );
404 *pRecordCount = CountFiles(*m_tar, restoreList);
406 // close for next open
407 m_tar.reset();
410 // open for the main restore
411 m_tar.reset( new reuse::TarFile(filename.c_str(), false, &reuse::gztar_ops_nonthread, true) );
414 catch( reuse::TarFile::TarError &te ) {
415 return False(te.what());
418 // setup
419 m_AppComm = comm;
420 m_dbList = restoreList;
421 m_current_dbname_not_thread_safe = "";
422 m_current_dbname = "";
423 m_unique_id = 0;
424 m_end_of_tar = false;
425 m_tar_record_loaded = false;
427 // get first tar record
428 Retrieve(0);
430 // start the thread
431 Glib::Thread::create(sigc::mem_fun(*this, &DeviceInterface::RestoreThread), false);
432 return true;
435 bool DeviceInterface::StartRestoreAndBackup(AppComm comm,
436 const ConfigFile::DBListType &restoreAndBackupList,
437 const std::string &filename,
438 const std::string &directory, const std::string &pin,
439 int *pRecordCount)
441 if( m_AppComm.IsValid() )
442 return False("Thread already running.");
444 try {
445 if( pRecordCount ) {
446 // caller is asking for a total, so we do a quick
447 // scan through the tar file first
448 m_tar.reset( new reuse::TarFile(filename.c_str(), false, &reuse::gztar_ops_nonthread, true) );
449 *pRecordCount = CountFiles(*m_tar, restoreAndBackupList);
451 // close for next open
452 m_tar.reset();
455 // open for the main restore
456 m_tar.reset( new reuse::TarFile(filename.c_str(), false, &reuse::gztar_ops_nonthread, true) );
458 // open for secondary backup
459 std::string back = directory + "/" + MakeFilename(pin);
460 m_tarback.reset( new reuse::TarFile(back.c_str(), true, &reuse::gztar_ops_nonthread, true) );
463 catch( reuse::TarFile::TarError &te ) {
464 return False(te.what());
467 // setup
468 m_AppComm = comm;
469 m_dbList = restoreAndBackupList;
470 m_current_dbname_not_thread_safe = "";
471 m_current_dbname = "";
472 m_unique_id = 0;
473 m_end_of_tar = false;
474 m_tar_record_loaded = false;
476 // get first tar record
477 Retrieve(0);
479 // start the thread
480 Glib::Thread::create(sigc::mem_fun(*this, &DeviceInterface::RestoreAndBackupThread), false);
481 return true;
486 //////////////////////////////////////////////////////////////////////////////
487 // Barry::Parser overrides
489 void DeviceInterface::SetIds(uint8_t RecType, uint32_t UniqueId)
491 m_rec_type = RecType;
492 m_unique_id = UniqueId;
493 std::ostringstream oss;
494 oss << std::hex << m_unique_id << " " << (unsigned int)m_rec_type;
495 m_tar_id_text = oss.str();
496 if( m_tar_id_text.size() == 0 )
497 throw std::runtime_error("No unique ID available!");
500 void DeviceInterface::ParseFields(const Barry::Data &data, size_t &offset)
502 m_record_data.assign((const char*)data.GetData() + offset, data.GetSize() - offset);
505 void DeviceInterface::Store()
507 std::string tarname = m_current_dbname + "/" + m_tar_id_text;
508 m_tarback->AppendFile(tarname.c_str(), m_record_data);
510 m_AppComm.m_progress->emit();
512 // check quit flag
513 if( m_thread_quit ) {
514 throw Quit();
519 //////////////////////////////////////////////////////////////////////////////
520 // Barry::Builder overrides
522 bool DeviceInterface::Retrieve(unsigned int dbId)
524 if( m_end_of_tar )
525 return false;
527 // if loaded, we are likely on a database
528 // boundary, and the last read crossed it, so don't load again
529 if( m_tar_record_loaded )
530 return true;
532 // search for a valid record
533 for(;;) {
534 // load record data from tar file
535 std::string filename;
536 if( !m_tar->ReadNextFile(filename, m_record_data) ) {
537 // assume end of file
538 m_end_of_tar = true;
539 return false;
541 m_tar_record_loaded = true;
543 // split record filename into dbname and ID
544 std::string dbname;
545 if( !SplitTarPath(filename, dbname, m_tar_id_text, m_rec_type, m_unique_id) ) {
546 // invalid filename, skip it
547 std::cerr << "Skipping invalid tar record: " << filename << std::endl;
548 continue;
551 // are we working on the same dbname as last time? if so, go ahead!
552 if( m_current_dbname == dbname ) {
553 return true;
556 // DIFFERENT DBNAME from here on down!
558 // does the filter allow this record? if not, skip it and continue
559 // looking
560 if( !m_dbList.IsSelected(dbname) ) {
561 continue;
564 // all checks pass, load the new dbname, and return false
565 // if we are on a dbname boundary
566 bool r_val = false;
567 if( m_current_dbname.size() == 0 ) {
568 // this is the first time through Retrieve, so ok
569 r_val = true;
572 SetThreadDBName(dbname);
573 return r_val;
577 uint8_t DeviceInterface::GetRecType() const
579 return m_rec_type;
582 uint32_t DeviceInterface::GetUniqueId() const
584 return m_unique_id;
587 void DeviceInterface::BuildHeader(Barry::Data &data, size_t &offset)
589 // nothing to do
592 void DeviceInterface::BuildFields(Barry::Data &data, size_t &offset)
594 int packet_size = offset + m_record_data.size();
595 unsigned char *buf = data.GetBuffer(packet_size);
596 memcpy(buf + offset, m_record_data.data(), m_record_data.size());
597 offset += m_record_data.size();
598 data.ReleaseBuffer(packet_size);
600 // clear loaded flag, as it has now been used
601 m_tar_record_loaded = false;
603 m_AppComm.m_progress->emit();
606 // helper function for halding restore errors
607 void DeviceInterface::SkipCurrentDB() throw()
609 // skip all records until next DB
610 try {
611 while( Retrieve(0) ) {
612 std::cerr << "Skipping: "
613 << m_current_dbname << "/"
614 << m_tar_id_text << std::endl;
615 m_tar_record_loaded = false;
618 catch( reuse::TarFile::TarError & ) {
619 m_end_of_tar = true;
621 catch( ... ) {
622 // swallow all other exceptions
623 std::cerr << "EXCEPTION IN SkipCurrentDB()! "
624 "Please report to Barry mailing list." << std::endl;