Bumped copyright dates for 2013
[barry.git] / src / restore.cc
blobbe28f626901c8410cf4f16bf270f76d614107121
1 ///
2 /// \file restore.cc
3 /// Builder class for restoring from Barry Backup files
4 ///
6 /*
7 Copyright (C) 2010-2013, 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 "i18n.h"
23 #include "restore.h"
24 #include "tarfile.h"
25 #include "error.h"
26 #include <sstream>
27 #include <iomanip>
28 #include <iostream>
29 #include <string.h>
30 #include <algorithm>
32 using namespace std;
34 namespace Barry {
36 namespace {
38 int CountFiles(reuse::TarFile &tar,
39 const Barry::Restore::DBListType &restoreList,
40 Barry::Restore::DBListType *available,
41 bool default_all_db)
43 int count = 0;
44 std::string name, last_name;
45 bool good = false;
47 while( tar.ReadNextFilenameOnly(name) ) {
48 std::string::size_type pos = name.rfind('/');
49 if( pos == std::string::npos )
50 continue; // bad name
51 std::string dbname = name.substr(0, pos);
53 if( dbname != last_name ) {
54 last_name = dbname;
55 good = (default_all_db && restoreList.size() == 0) ||
56 restoreList.IsSelected(dbname);
58 if( good && available )
59 available->push_back(dbname);
61 if( good )
62 count++;
64 return count;
69 //////////////////////////////////////////////////////////////////////////////
70 // Static Restore members
72 /// Splits a tarpath of the form "DBName/DBID" into separate string values.
73 /// Returns true if successful, false if tarpath is a bad name.
74 bool Restore::SplitTarPath(const std::string &tarpath,
75 std::string &dbname,
76 std::string &dbid_text,
77 uint8_t &dbrectype,
78 uint32_t &dbid)
80 std::string::size_type pos = tarpath.rfind('/');
81 if( pos == std::string::npos )
82 return false; // bad name
84 dbname = tarpath.substr(0, pos);
85 dbid_text = tarpath.substr(pos + 1);
86 if( dbname.size() == 0 || dbid_text.size() == 0 )
87 return false; // bad name
89 std::istringstream iss(dbid_text);
90 unsigned int temp;
91 iss >> std::hex >> dbid >> temp;
92 dbrectype = (uint8_t) temp;
94 return true;
98 //////////////////////////////////////////////////////////////////////////////
99 // Restore - constructors
101 Restore::Restore(const std::string &tarpath, bool default_all_db)
102 : m_tarpath(tarpath)
103 , m_default_all_db(default_all_db)
104 , m_tar_record_state(RS_EMPTY)
105 , m_rec_type(0)
106 , m_unique_id(0)
108 try {
109 m_tar.reset( new reuse::TarFile(tarpath.c_str(), false,
110 &reuse::gztar_ops_nonthread, true) );
112 catch( reuse::TarFile::TarError &te ) {
113 throw Barry::RestoreError(te.what());
117 Restore::~Restore()
122 //////////////////////////////////////////////////////////////////////////////
123 // Restore - Protected helpers
125 bool Restore::IsSelected(const std::string &dbName) const
127 // if in skip list, always return false,
128 // if nothing is in the main list, use default
129 // otherwise, only return true if specifically selected
130 if( m_dbSkipList.IsSelected(dbName) )
131 return false;
132 else if( m_dbList.size() == 0 )
133 return m_default_all_db;
134 else
135 return m_dbList.IsSelected(dbName);
139 //////////////////////////////////////////////////////////////////////////////
140 // Restore - Public API
142 void Restore::AddDB(const std::string &dbName)
144 if( find(m_dbList.begin(), m_dbList.end(), dbName) == m_dbList.end() ) {
145 // only add it if it is not already in the list
146 m_dbList.push_back(dbName);
150 void Restore::Add(const DBListType &dbList)
152 for( DBListType::const_iterator i = dbList.begin();
153 i != dbList.end();
154 ++i )
156 AddDB(*i);
160 void Restore::Add(const DatabaseDatabase &dbdb)
162 for( DatabaseDatabase::DatabaseArrayType::const_iterator i = dbdb.Databases.begin();
163 i != dbdb.Databases.end();
164 ++i )
166 AddDB(i->Name);
170 void Restore::AddSkipDB(const std::string &dbName)
172 if( find(m_dbSkipList.begin(), m_dbSkipList.end(), dbName) == m_dbSkipList.end() ) {
173 // only add it if it is not already in the list
174 m_dbSkipList.push_back(dbName);
178 void Restore::SkipCurrentDB()
180 // skip all records until next DB
181 try {
182 Restore::RetrievalState state;
183 while( (state = Retrieve(m_record_data)) == RS_NEXT ) {
184 std::cerr << _("Skipping: ")
185 << m_current_dbname << "/"
186 << m_tar_id_text << std::endl;
187 m_tar_record_state = RS_EMPTY;
190 if( state == RS_DBEND ) {
191 // process the end of database, so that user is free
192 // to call GetNextMeta() or Retrieve() or BuildRecord()
193 m_tar_record_state = RS_NEXT;
196 catch( reuse::TarFile::TarError & ) {
197 m_tar_record_state = RS_EOF;
201 unsigned int Restore::GetRecordTotal() const
203 return GetRecordTotal(m_tarpath, m_dbList, m_default_all_db);
206 unsigned int Restore::GetRecordTotal(const std::string &tarpath,
207 const DBListType &dbList,
208 bool default_all_db)
210 unsigned int count = 0;
212 std::auto_ptr<reuse::TarFile> tar;
214 try {
215 // do a scan through the tar file
216 tar.reset( new reuse::TarFile(tarpath.c_str(), false,
217 &reuse::gztar_ops_nonthread, true) );
218 count = CountFiles(*tar, dbList, 0, default_all_db);
220 catch( reuse::TarFile::TarError &te ) {
221 throw Barry::RestoreError(te.what());
223 return count;
226 Barry::Restore::DBListType Restore::GetDBList() const
228 return GetDBList(m_tarpath);
231 Barry::Restore::DBListType Restore::GetDBList(const std::string &tarpath)
233 std::auto_ptr<reuse::TarFile> tar;
234 DBListType available, empty;
236 try {
237 // do a scan through the tar file
238 tar.reset( new reuse::TarFile(tarpath.c_str(), false,
239 &reuse::gztar_ops_nonthread, true) );
240 CountFiles(*tar, empty, &available, true);
241 return available;
243 catch( reuse::TarFile::TarError &te ) {
244 throw Barry::RestoreError(te.what());
248 bool Restore::GetNextMeta(DBData &data)
250 // always use m_record_data here, so that we don't lose access
251 // to the actual record data for future calls to BuildRecord()
252 // and FetchRecord()
253 if( m_tar_record_state == RS_EMPTY ) {
254 Retrieve(m_record_data);
257 // fill in the meta data that will be returned in the next call
258 // to BuildRecord() or FetchRecord()... this is only valid if
259 // the state is RS_NEXT
260 switch( m_tar_record_state )
262 case RS_NEXT:
263 data.SetVersion(Barry::DBData::REC_VERSION_1);
264 data.SetDBName(m_current_dbname);
265 data.SetIds(m_rec_type, m_unique_id);
266 data.SetOffset(0);
267 return true;
269 default:
270 return false;
275 //////////////////////////////////////////////////////////////////////////////
276 // Barry::Builder overrides
278 Restore::RetrievalState Restore::Retrieve(Data &record_data)
280 // don't do anything unless we're empty
281 if( m_tar_record_state != RS_EMPTY )
282 return m_tar_record_state;
284 // search for a valid record
285 for(;;) {
286 // load record data from tar file
287 std::string filename;
288 if( !m_tar->ReadNextFile(filename, record_data) ) {
289 // assume end of file
290 return m_tar_record_state = RS_EOF;
292 m_tar_record_state = RS_UNKNOWN;
294 // split record filename into dbname and ID
295 std::string dbname;
296 if( !SplitTarPath(filename, dbname, m_tar_id_text, m_rec_type, m_unique_id) ) {
297 // invalid filename, skip it
298 std::cerr << _("Skipping invalid tar record: ") << filename << std::endl;
299 continue;
302 // are we working on the same dbname as last time?
303 // if so, go ahead!
304 if( m_current_dbname == dbname ) {
305 return m_tar_record_state = RS_NEXT;
308 // DIFFERENT DBNAME from here on down!
309 m_tar_record_state = RS_DBEND;
311 // does the filter allow this record?
312 // if not, skip it and continue looking
313 if( !IsSelected(dbname) ) {
314 continue;
317 // all checks pass, load the new dbname, and return DBEND
318 // if we are on a dbname boundary
319 if( m_current_dbname.size() == 0 ) {
320 // this is the first time through Retrieve, so ok
321 m_tar_record_state = RS_NEXT;
324 m_current_dbname = dbname;
325 return m_tar_record_state;
329 bool Restore::BuildRecord(Barry::DBData &data,
330 size_t &offset,
331 const Barry::IConverter *ic)
333 // in this case, we are loading into m_record_data anyway,
334 // so no special handling is needed, like FetchRecord() needs.
335 switch( Retrieve(m_record_data) )
337 case RS_NEXT:
339 data.SetVersion(Barry::DBData::REC_VERSION_1);
340 data.SetDBName(m_current_dbname);
341 data.SetIds(m_rec_type, m_unique_id);
342 data.SetOffset(offset);
344 int packet_size = offset + m_record_data.GetSize();
345 unsigned char *buf = data.UseData().GetBuffer(packet_size);
346 memcpy(buf + offset, m_record_data.GetData(), m_record_data.GetSize());
347 offset += m_record_data.GetSize();
348 data.UseData().ReleaseBuffer(packet_size);
350 // clear loaded flag, as it has now been used
351 m_tar_record_state = RS_EMPTY;
352 return true;
355 case RS_EMPTY:
356 case RS_UNKNOWN:
357 default:
358 throw std::logic_error(_("Invalid state in Restore::BuildRecord()"));
360 case RS_DBEND:
361 // process the end of database by returning false
362 // the next round will be valid, so set to RS_NEXT
363 m_tar_record_state = RS_NEXT;
364 return false;
366 case RS_EOF:
367 // always return false at end of file
368 return false;
372 bool Restore::FetchRecord(Barry::DBData &data, const Barry::IConverter *ic)
374 // if the record has not yet been loaded, we can optimize
375 // the buffer, and pass in our own... otherwise, just copy
376 // the current buffer from m_record_data
378 // it is assumed here that Builder users will not alternate
379 // between calls to BuildRecord() and FetchRecord()
381 if( m_tar_record_state == RS_EMPTY ) {
382 // BUT, if RS_DBEND is the next value, then we need
383 // to save the data for the next round... this
384 // optimization is almost more bother than it's worth :-)
385 if( Retrieve(data.UseData()) == RS_DBEND ) {
386 m_record_data = data.GetData();
387 m_tar_record_state = RS_NEXT;
388 return false;
391 else {
392 data.UseData() = m_record_data;
395 switch( m_tar_record_state )
397 case RS_NEXT:
398 data.SetVersion(Barry::DBData::REC_VERSION_1);
399 data.SetDBName(m_current_dbname);
400 data.SetIds(m_rec_type, m_unique_id);
401 data.SetOffset(0);
403 // clear loaded flag, as it has now been used
404 m_tar_record_state = RS_EMPTY;
405 return true;
407 case RS_EMPTY:
408 case RS_UNKNOWN:
409 default:
410 throw std::logic_error(_("Invalid state in Restore::FetchRecord()"));
412 case RS_DBEND:
413 // process the end of database by returning false
414 // the next round will be valid, so set to RS_NEXT
415 m_tar_record_state = RS_NEXT;
416 return false;
418 case RS_EOF:
419 // always return false at end of file
420 return false;
424 bool Restore::EndOfFile() const
426 return m_tar_record_state == RS_EOF;
429 } // namespace Barry