lib: added reuse::TzWrapper class and utilities
[barry.git] / src / configfile.cc
blob0e8ee2fe650e54574a663e6dc70369dedf964aab
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 struct passwd *pw = getpwuid(getuid());
94 if( !pw )
95 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
97 m_filename = pw->pw_dir;
98 m_filename += "/.barry/backup/";
99 m_filename += m_pin.str();
100 m_filename += "/config";
103 void ConfigFile::BuildDefaultPath()
105 struct passwd *pw = getpwuid(getuid());
106 m_path = pw->pw_dir;
107 m_path += "/.barry/backup/";
108 m_path += m_pin.str();
111 void ConfigFile::Clear()
113 m_loaded = false;
114 m_backupList.clear();
115 m_restoreList.clear();
116 m_deviceName.clear();
117 m_promptBackupLabel = false;
120 /// Attempt to load the configuration file, but do not fail if not available
121 void ConfigFile::Load()
123 // start fresh
124 Clear();
126 // open input file
127 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
128 if( !in )
129 return;
131 std::string line;
132 DBListType *pList = 0;
134 while( std::getline(in, line) ) {
135 std::string keyword;
136 std::istringstream iss(line);
137 iss >> keyword;
139 if( keyword == "backup_list" ) {
140 pList = &m_backupList;
142 else if( keyword == "restore_list" ) {
143 pList = &m_restoreList;
145 else if( line[0] == ' ' && pList ) {
146 pList->push_back(line.c_str() + 1);
148 else {
149 pList = 0;
151 // add all remaining keyword checks here
152 if( keyword == "device_name" ) {
153 iss >> std::ws;
154 std::getline(iss, m_deviceName);
155 if( m_deviceName.size() == 0 ) {
156 // if there is a device_name setting,
157 // then this value must hold something,
158 // so that the user can ignore this
159 // field, and not get pestered all
160 // the time
161 m_deviceName = " ";
164 else if( keyword == "backup_path" ) {
165 iss >> std::ws;
166 std::getline(iss, m_path);
167 if( (m_path.size() == 0) || !(CheckPath(m_path)))
168 BuildDefaultPath();
170 else if( keyword == "prompt_backup_label" ) {
171 int flag;
172 iss >> flag;
173 m_promptBackupLabel = flag;
178 m_loaded = true;
181 /// Saves current device's config, overwriting or creating a config file
182 bool ConfigFile::Save()
184 if( !CheckPath(m_path, &m_last_error) )
185 return false;
187 std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
188 if( !out ) {
189 m_last_error = "Unable to open " + m_filename + " for writing.";
190 return false;
193 out << "backup_list" << std::endl;
194 for( DBListType::iterator i = m_backupList.begin(); i != m_backupList.end(); ++i ) {
195 out << " " << *i << std::endl;
198 out << "restore_list" << std::endl;
199 for( DBListType::iterator i = m_restoreList.begin(); i != m_restoreList.end(); ++i ) {
200 out << " " << *i << std::endl;
203 if( m_deviceName.size() ) {
204 out << "device_name " << m_deviceName << std::endl;
207 if( m_path.size() ) {
208 out << "backup_path " << m_path << std::endl;
211 out << "prompt_backup_label " << (m_promptBackupLabel ? 1 : 0) << std::endl;
213 if( !out ) {
214 m_last_error = "Error during write. Config may be incomplete.";
215 return false;
217 return true;
220 /// Compares a given databasedatabase from a real device with the
221 /// current config. If not yet configured, initialize with valid
222 /// defaults.
223 void ConfigFile::Enlighten(const Barry::DatabaseDatabase &db)
225 if( !m_loaded ) {
226 // if not fully loaded, we use db as our default list
227 // our defaults are: backup everything, restore everything
228 // except email
230 m_backupList.clear();
231 m_restoreList.clear();
233 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator i =
234 db.Databases.begin();
235 for( ; i != db.Databases.end(); ++i ) {
236 // backup everything
237 m_backupList.push_back(i->Name);
239 // restore everything except email (which could take ages)
240 // and Handheld Agent (which seems write protected)
241 if( i->Name != Barry::Message::GetDBName() &&
242 i->Name != "Handheld Agent" )
244 m_restoreList.push_back(i->Name);
250 /// Sets list with new config
251 void ConfigFile::SetBackupList(const DBListType &list)
253 m_backupList = list;
254 m_loaded = true;
257 void ConfigFile::SetRestoreList(const DBListType &list)
259 m_restoreList = list;
260 m_loaded = true;
263 void ConfigFile::SetDeviceName(const std::string &name)
265 if( name.size() )
266 m_deviceName = name;
267 else
268 m_deviceName = " ";
271 void ConfigFile::SetBackupPath(const std::string &path)
273 if( path.size() && CheckPath(path) )
274 m_path = path;
275 else
276 BuildDefaultPath();
279 void ConfigFile::SetPromptBackupLabel(bool prompt)
281 m_promptBackupLabel = prompt;
284 /// Checks that the path in path exists, and if not, creates it.
285 /// Returns false if unable to create path, true if ok.
286 bool ConfigFile::CheckPath(const std::string &path, std::string *perr)
288 if( path.size() == 0 ) {
289 if( perr )
290 *perr = "path is empty!";
291 return false;
294 if( access(path.c_str(), F_OK) == 0 )
295 return true;
297 std::string base;
298 std::string::size_type slash = 0;
299 while( (slash = path.find('/', slash + 1)) != std::string::npos ) {
300 base = path.substr(0, slash);
301 if( access(base.c_str(), F_OK) != 0 ) {
302 if( mkdir(base.c_str(), 0755) == -1 ) {
303 if( perr ) {
304 *perr = "mkdir(" + base + ") failed: ";
305 *perr += strerror(errno);
307 return false;
311 if( mkdir(path.c_str(), 0755) == -1 ) {
312 if( perr ) {
313 *perr = "last mkdir(" + path + ") failed: ";
314 *perr += strerror(errno);
316 return false;
318 return true;
323 //////////////////////////////////////////////////////////////////////////////
324 // GlobalConfigFile class members
326 GlobalConfigFile::GlobalConfigFile()
327 : m_loaded(false)
328 , m_verboseLogging(false)
330 BuildFilename();
331 Load();
334 GlobalConfigFile::GlobalConfigFile(const std::string &appname)
335 : m_loaded(false)
336 , m_appname(appname)
337 , m_verboseLogging(false)
339 // there can be no spaces in the appname
340 if( m_appname.find(' ') != std::string::npos )
341 throw std::logic_error("App name must have no spaces.");
343 BuildFilename();
344 Load();
347 GlobalConfigFile::~GlobalConfigFile()
351 void GlobalConfigFile::BuildFilename()
353 struct passwd *pw = getpwuid(getuid());
354 if( !pw )
355 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
357 m_filename = pw->pw_dir;
358 m_filename += "/.barry/config";
360 // build the global path too, since this never changes
361 m_path = pw->pw_dir;
362 m_path += "/.barry";
365 void GlobalConfigFile::Clear()
367 m_loaded = false;
368 m_lastDevice = 0;
371 void GlobalConfigFile::Load()
373 // start fresh
374 Clear();
376 // open input file
377 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
378 if( !in )
379 return;
381 std::string line;
383 while( std::getline(in, line) ) {
384 std::string keyword;
385 std::istringstream iss(line);
386 iss >> keyword;
388 if( keyword == "last_device" ) {
389 iss >> std::ws;
390 m_lastDevice.clear();
391 iss >> m_lastDevice;
393 else if( keyword == "verbose_logging" ) {
394 int flag = 0;
395 iss >> flag;
396 m_verboseLogging = flag;
398 else {
399 // store any other keys as app keys
400 if( keyword.substr(0, 2) == "X-" ) {
401 iss >> std::ws;
402 line.clear();
403 std::getline(iss, line);
404 m_keymap[keyword] = line;
409 m_loaded = true;
412 /// Save the current global config, overwriting or creating as needed
413 bool GlobalConfigFile::Save()
415 if( !ConfigFile::CheckPath(m_path, &m_last_error) )
416 return false;
418 std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
419 if( !out ) {
420 m_last_error = "Unable to open " + m_filename + " for writing.";
421 return false;
424 if( !(m_lastDevice == 0) ) {
425 out << "last_device " << m_lastDevice.str() << std::endl;
428 out << "verbose_logging " << (m_verboseLogging ? 1 : 0) << std::endl;
430 // store all app keys
431 keymap_type::const_iterator ci = m_keymap.begin();
432 for( ; ci != m_keymap.end(); ++ci ) {
433 out << ci->first << " " << ci->second << std::endl;
436 if( !out ) {
437 m_last_error = "Error during write. Config may be incomplete.";
438 return false;
440 return true;
443 void GlobalConfigFile::SetKey(const std::string &key, const std::string &value)
445 if( !m_appname.size() )
446 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
448 if( value.find_first_of("\n\r") != std::string::npos )
449 throw std::logic_error("SetKey values may not contain newline characters.");
451 std::string fullkey = "X-" + m_appname + "-" + key;
452 m_keymap[fullkey] = value;
455 std::string GlobalConfigFile::GetKey(const std::string &key) const
457 if( !m_appname.size() )
458 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
460 std::string fullkey = "X-" + m_appname + "-" + key;
461 keymap_type::const_iterator ci = m_keymap.find(fullkey);
462 if( ci == m_keymap.end() )
463 return "";
464 return ci->second;
467 void GlobalConfigFile::SetLastDevice(const Barry::Pin &pin)
469 m_lastDevice = pin;
472 void GlobalConfigFile::SetVerboseLogging(bool verbose)
474 m_verboseLogging = verbose;
478 } // namespace Barry