lib: added Backup parser and Restore builder classes
[barry.git] / src / configfile.cc
blobe98240bb47ca1a07f09b7895f397cf6a7cbf6767
1 ///
2 /// \file configfile.cc
3 /// Barry configuraion class, for one device PIN
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 "configfile.h"
23 #include "error.h"
24 #include "r_message.h"
25 #include <pwd.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <fstream>
30 #include <sstream>
31 #include <sys/types.h>
32 #include <sys/stat.h>
34 namespace Barry {
36 bool ConfigFile::DBListType::IsSelected(const std::string &dbname) const
38 const_iterator i = begin();
39 for( ; i != end(); ++i ) {
40 if( *i == dbname ) {
41 return true;
44 return false;
48 //////////////////////////////////////////////////////////////////////////////
49 // ConfigFile class members
51 /// Loads config file for the given pin, and ends up in an
52 /// unenlightened state. Throws ConfigFileError on error,
53 /// but it is not an error if the config does not exist.
54 /// Never use this if you have a DatabaseDatabase object!
55 /// This ctor is only for temporary loading of config data.
56 ConfigFile::ConfigFile(Barry::Pin pin)
57 : m_pin(pin)
58 , m_loaded(false)
59 , m_promptBackupLabel(false)
61 if( m_pin == 0 )
62 throw ConfigFileError("Configfile: empty pin");
64 BuildFilename();
65 BuildDefaultPath(); // this handles the situation that path is not set
66 Load();
69 /// Opens and loads config file for given pin, and calls Enlighten
70 /// Throws ConfigFileError on error. Should never fail unless
71 /// passed a bad pin.
72 ConfigFile::ConfigFile(Barry::Pin pin,
73 const Barry::DatabaseDatabase &db)
74 : m_pin(pin)
75 , m_loaded(false)
76 , m_promptBackupLabel(false)
78 if( m_pin == 0 )
79 throw ConfigFileError("Configfile: empty pin");
81 BuildFilename();
82 BuildDefaultPath();
83 Load();
84 Enlighten(db);
87 ConfigFile::~ConfigFile()
91 void ConfigFile::BuildFilename()
93 size_t strsize = 255 * 5;
94 char *strbuf = new char[strsize];
95 struct passwd pwbuf;
96 struct passwd *pw;
98 getpwuid_r(getuid(), &pwbuf, strbuf, strsize, &pw);
99 if( !pw ) {
100 delete [] strbuf;
101 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
104 m_filename = pw->pw_dir;
105 m_filename += "/.barry/backup/";
106 m_filename += m_pin.str();
107 m_filename += "/config";
109 delete [] strbuf;
112 void ConfigFile::BuildDefaultPath()
114 struct passwd *pw = getpwuid(getuid());
115 m_path = pw->pw_dir;
116 m_path += "/.barry/backup/";
117 m_path += m_pin.str();
120 void ConfigFile::Clear()
122 m_loaded = false;
123 m_backupList.clear();
124 m_restoreList.clear();
125 m_deviceName.clear();
126 m_promptBackupLabel = false;
129 /// Attempt to load the configuration file, but do not fail if not available
130 void ConfigFile::Load()
132 // start fresh
133 Clear();
135 // open input file
136 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
137 if( !in )
138 return;
140 std::string line;
141 DBListType *pList = 0;
143 while( std::getline(in, line) ) {
144 std::string keyword;
145 std::istringstream iss(line);
146 iss >> keyword;
148 if( keyword == "backup_list" ) {
149 pList = &m_backupList;
151 else if( keyword == "restore_list" ) {
152 pList = &m_restoreList;
154 else if( line[0] == ' ' && pList ) {
155 pList->push_back(line.c_str() + 1);
157 else {
158 pList = 0;
160 // add all remaining keyword checks here
161 if( keyword == "device_name" ) {
162 iss >> std::ws;
163 std::getline(iss, m_deviceName);
164 if( m_deviceName.size() == 0 ) {
165 // if there is a device_name setting,
166 // then this value must hold something,
167 // so that the user can ignore this
168 // field, and not get pestered all
169 // the time
170 m_deviceName = " ";
173 else if( keyword == "backup_path" ) {
174 iss >> std::ws;
175 std::getline(iss, m_path);
176 if( (m_path.size() == 0) || !(CheckPath(m_path)))
177 BuildDefaultPath();
179 else if( keyword == "prompt_backup_label" ) {
180 int flag;
181 iss >> flag;
182 m_promptBackupLabel = flag;
187 m_loaded = true;
190 /// Saves current device's config, overwriting or creating a config file
191 bool ConfigFile::Save()
193 if( !CheckPath(m_path, &m_last_error) )
194 return false;
196 std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
197 if( !out ) {
198 m_last_error = "Unable to open " + m_filename + " for writing.";
199 return false;
202 out << "backup_list" << std::endl;
203 for( DBListType::iterator i = m_backupList.begin(); i != m_backupList.end(); ++i ) {
204 out << " " << *i << std::endl;
207 out << "restore_list" << std::endl;
208 for( DBListType::iterator i = m_restoreList.begin(); i != m_restoreList.end(); ++i ) {
209 out << " " << *i << std::endl;
212 if( m_deviceName.size() ) {
213 out << "device_name " << m_deviceName << std::endl;
216 if( m_path.size() ) {
217 out << "backup_path " << m_path << std::endl;
220 out << "prompt_backup_label " << (m_promptBackupLabel ? 1 : 0) << std::endl;
222 if( !out ) {
223 m_last_error = "Error during write. Config may be incomplete.";
224 return false;
226 return true;
229 /// Compares a given databasedatabase from a real device with the
230 /// current config. If not yet configured, initialize with valid
231 /// defaults.
232 void ConfigFile::Enlighten(const Barry::DatabaseDatabase &db)
234 if( !m_loaded ) {
235 // if not fully loaded, we use db as our default list
236 // our defaults are: backup everything, restore everything
237 // except email
239 m_backupList.clear();
240 m_restoreList.clear();
242 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator i =
243 db.Databases.begin();
244 for( ; i != db.Databases.end(); ++i ) {
245 // backup everything
246 m_backupList.push_back(i->Name);
248 // restore everything except email (which could take ages)
249 // and Handheld Agent (which seems write protected)
250 if( i->Name != Barry::Message::GetDBName() &&
251 i->Name != "Handheld Agent" )
253 m_restoreList.push_back(i->Name);
259 /// Sets list with new config
260 void ConfigFile::SetBackupList(const DBListType &list)
262 m_backupList = list;
263 m_loaded = true;
266 void ConfigFile::SetRestoreList(const DBListType &list)
268 m_restoreList = list;
269 m_loaded = true;
272 void ConfigFile::SetDeviceName(const std::string &name)
274 if( name.size() )
275 m_deviceName = name;
276 else
277 m_deviceName = " ";
280 void ConfigFile::SetBackupPath(const std::string &path)
282 if( path.size() && CheckPath(path) )
283 m_path = path;
284 else
285 BuildDefaultPath();
288 void ConfigFile::SetPromptBackupLabel(bool prompt)
290 m_promptBackupLabel = prompt;
293 /// Checks that the path in path exists, and if not, creates it.
294 /// Returns false if unable to create path, true if ok.
295 bool ConfigFile::CheckPath(const std::string &path, std::string *perr)
297 if( path.size() == 0 ) {
298 if( perr )
299 *perr = "path is empty!";
300 return false;
303 if( access(path.c_str(), F_OK) == 0 )
304 return true;
306 std::string base;
307 std::string::size_type slash = 0;
308 while( (slash = path.find('/', slash + 1)) != std::string::npos ) {
309 base = path.substr(0, slash);
310 if( access(base.c_str(), F_OK) != 0 ) {
311 if( mkdir(base.c_str(), 0755) == -1 ) {
312 if( perr ) {
313 *perr = "mkdir(" + base + ") failed: ";
314 *perr += strerror(errno);
316 return false;
320 if( mkdir(path.c_str(), 0755) == -1 ) {
321 if( perr ) {
322 *perr = "last mkdir(" + path + ") failed: ";
323 *perr += strerror(errno);
325 return false;
327 return true;
332 //////////////////////////////////////////////////////////////////////////////
333 // GlobalConfigFile class members
335 GlobalConfigFile::GlobalConfigFile()
336 : m_loaded(false)
337 , m_verboseLogging(false)
339 BuildFilename();
340 Load();
343 GlobalConfigFile::GlobalConfigFile(const std::string &appname)
344 : m_loaded(false)
345 , m_appname(appname)
346 , m_verboseLogging(false)
348 // there can be no spaces in the appname
349 if( m_appname.find(' ') != std::string::npos )
350 throw std::logic_error("App name must have no spaces.");
352 BuildFilename();
353 Load();
356 GlobalConfigFile::~GlobalConfigFile()
360 void GlobalConfigFile::BuildFilename()
362 struct passwd *pw = getpwuid(getuid());
363 if( !pw )
364 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
366 m_filename = pw->pw_dir;
367 m_filename += "/.barry/config";
369 // build the global path too, since this never changes
370 m_path = pw->pw_dir;
371 m_path += "/.barry";
374 void GlobalConfigFile::Clear()
376 m_loaded = false;
377 m_lastDevice = 0;
380 void GlobalConfigFile::Load()
382 // start fresh
383 Clear();
385 // open input file
386 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
387 if( !in )
388 return;
390 std::string line;
392 while( std::getline(in, line) ) {
393 std::string keyword;
394 std::istringstream iss(line);
395 iss >> keyword;
397 if( keyword == "last_device" ) {
398 iss >> std::ws;
399 m_lastDevice.clear();
400 iss >> m_lastDevice;
402 else if( keyword == "verbose_logging" ) {
403 int flag = 0;
404 iss >> flag;
405 m_verboseLogging = flag;
407 else {
408 // store any other keys as app keys
409 if( keyword.substr(0, 2) == "X-" ) {
410 iss >> std::ws;
411 line.clear();
412 std::getline(iss, line);
413 m_keymap[keyword] = line;
418 m_loaded = true;
421 /// Save the current global config, overwriting or creating as needed
422 bool GlobalConfigFile::Save()
424 if( !ConfigFile::CheckPath(m_path, &m_last_error) )
425 return false;
427 std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
428 if( !out ) {
429 m_last_error = "Unable to open " + m_filename + " for writing.";
430 return false;
433 if( !(m_lastDevice == 0) ) {
434 out << "last_device " << m_lastDevice.str() << std::endl;
437 out << "verbose_logging " << (m_verboseLogging ? 1 : 0) << std::endl;
439 // store all app keys
440 keymap_type::const_iterator ci = m_keymap.begin();
441 for( ; ci != m_keymap.end(); ++ci ) {
442 out << ci->first << " " << ci->second << std::endl;
445 if( !out ) {
446 m_last_error = "Error during write. Config may be incomplete.";
447 return false;
449 return true;
452 void GlobalConfigFile::SetKey(const std::string &key, const std::string &value)
454 if( !m_appname.size() )
455 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
457 if( value.find_first_of("\n\r") != std::string::npos )
458 throw std::logic_error("SetKey values may not contain newline characters.");
460 std::string fullkey = "X-" + m_appname + "-" + key;
461 m_keymap[fullkey] = value;
464 std::string GlobalConfigFile::GetKey(const std::string &key) const
466 if( !m_appname.size() )
467 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
469 std::string fullkey = "X-" + m_appname + "-" + key;
470 keymap_type::const_iterator ci = m_keymap.find(fullkey);
471 if( ci == m_keymap.end() )
472 return "";
473 return ci->second;
476 void GlobalConfigFile::SetLastDevice(const Barry::Pin &pin)
478 m_lastDevice = pin;
481 void GlobalConfigFile::SetVerboseLogging(bool verbose)
483 m_verboseLogging = verbose;
487 } // namespace Barry