maint: remove -j2 flags on Fedora/OpenSUSE to workaround Make 3.82 borkage
[barry/progweb.git] / src / configfile.cc
blobb917c9fee0d18cb807c50577fb7a0ef0face60e1
1 ///
2 /// \file configfile.cc
3 /// Barry configuraion class, for one device PIN
4 ///
6 /*
7 Copyright (C) 2007-2012, 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 "getpwuid.h"
26 #include <string.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <fstream>
30 #include <sstream>
31 #include <iostream>
32 #include <iomanip>
33 #include <sys/types.h>
34 #include <sys/stat.h>
36 namespace Barry {
38 /// Creates a tar.gz filename using PIN + date + time + label.
39 /// Does not include any path, just returns a new filename.
40 std::string MakeBackupFilename(const Barry::Pin &pin,
41 const std::string &label)
43 using namespace std;
45 time_t t = time(NULL);
46 struct tm *lt = localtime(&t);
48 std::string fileLabel = label;
49 if( fileLabel.size() ) {
50 // prepend a hyphen
51 fileLabel.insert(fileLabel.begin(), '-');
53 // translate all spaces and slashes
54 for( size_t i = 0; i < fileLabel.size(); i++ ) {
55 if( fileLabel[i] == ' ' )
56 fileLabel[i] = '_';
57 else if( fileLabel[i] == '/' )
58 fileLabel[i] = '-';
59 else if( fileLabel[i] == '\\' )
60 fileLabel[i] = '-';
64 ostringstream tarfilename;
65 tarfilename << pin.Str() << "-"
66 << setw(4) << setfill('0') << (lt->tm_year + 1900)
67 << setw(2) << setfill('0') << (lt->tm_mon + 1)
68 << setw(2) << setfill('0') << lt->tm_mday
69 << "-"
70 << setw(2) << setfill('0') << lt->tm_hour
71 << setw(2) << setfill('0') << lt->tm_min
72 << setw(2) << setfill('0') << lt->tm_sec
73 << fileLabel
74 << ".tar.gz";
75 return tarfilename.str();
78 bool ConfigFile::DBListType::IsSelected(const std::string &dbname) const
80 const_iterator i = begin();
81 for( ; i != end(); ++i ) {
82 if( *i == dbname ) {
83 return true;
86 return false;
90 //////////////////////////////////////////////////////////////////////////////
91 // ConfigFile class members
93 /// Loads config file for the given pin, and ends up in an
94 /// unenlightened state. Throws ConfigFileError on error,
95 /// but it is not an error if the config does not exist.
96 /// Never use this if you have a DatabaseDatabase object!
97 /// This ctor is only for temporary loading of config data.
98 ConfigFile::ConfigFile(Barry::Pin pin)
99 : m_pin(pin)
100 , m_loaded(false)
101 , m_promptBackupLabel(false)
103 if( m_pin == 0 )
104 throw ConfigFileError("Configfile: empty pin");
106 BuildFilename();
107 BuildDefaultPath(); // this handles the situation that path is not set
108 Load();
111 /// Opens and loads config file for given pin, and calls Enlighten
112 /// Throws ConfigFileError on error. Should never fail unless
113 /// passed a bad pin.
114 ConfigFile::ConfigFile(Barry::Pin pin,
115 const Barry::DatabaseDatabase &db)
116 : m_pin(pin)
117 , m_loaded(false)
118 , m_promptBackupLabel(false)
120 if( m_pin == 0 )
121 throw ConfigFileError("Configfile: empty pin");
123 BuildFilename();
124 BuildDefaultPath();
125 Load();
126 Enlighten(db);
129 ConfigFile::~ConfigFile()
133 void ConfigFile::BuildFilename()
135 size_t strsize = 255 * 5;
136 char *strbuf = new char[strsize];
137 struct passwd pwbuf;
138 struct passwd *pw;
140 getpwuid_r(getuid(), &pwbuf, strbuf, strsize, &pw);
141 if( !pw ) {
142 delete [] strbuf;
143 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
146 m_filename = pw->pw_dir;
147 m_filename += "/.barry/backup/";
148 m_filename += m_pin.Str();
149 m_filename += "/config";
151 delete [] strbuf;
154 void ConfigFile::BuildDefaultPath()
156 struct passwd *pw = getpwuid(getuid());
157 m_path = pw->pw_dir;
158 m_path += "/.barry/backup/";
159 m_path += m_pin.Str();
162 void ConfigFile::Clear()
164 m_loaded = false;
165 m_backupList.clear();
166 m_restoreList.clear();
167 m_deviceName.clear();
168 m_promptBackupLabel = false;
171 /// Attempt to load the configuration file, but do not fail if not available
172 void ConfigFile::Load()
174 // start fresh
175 Clear();
177 // open input file
178 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
179 if( !in )
180 return;
182 std::string line;
183 DBListType *pList = 0;
185 while( std::getline(in, line) ) {
186 std::string keyword;
187 std::istringstream iss(line);
188 iss >> keyword;
190 if( keyword == "backup_list" ) {
191 pList = &m_backupList;
193 else if( keyword == "restore_list" ) {
194 pList = &m_restoreList;
196 else if( line[0] == ' ' && pList ) {
197 pList->push_back(line.c_str() + 1);
199 else {
200 pList = 0;
202 // add all remaining keyword checks here
203 if( keyword == "device_name" ) {
204 iss >> std::ws;
205 std::getline(iss, m_deviceName);
206 if( m_deviceName.size() == 0 ) {
207 // if there is a device_name setting,
208 // then this value must hold something,
209 // so that the user can ignore this
210 // field, and not get pestered all
211 // the time
212 m_deviceName = " ";
215 else if( keyword == "backup_path" ) {
216 iss >> std::ws;
217 std::getline(iss, m_path);
218 if( (m_path.size() == 0) || !(CheckPath(m_path)))
219 BuildDefaultPath();
221 else if( keyword == "prompt_backup_label" ) {
222 int flag;
223 iss >> flag;
224 m_promptBackupLabel = flag;
229 m_loaded = true;
232 /// Saves current device's config, overwriting or creating a config file
233 bool ConfigFile::Save()
235 if( !CheckPath(m_path, &m_last_error) )
236 return false;
238 std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
239 if( !out ) {
240 m_last_error = "Unable to open " + m_filename + " for writing.";
241 return false;
244 out << "backup_list" << std::endl;
245 for( DBListType::iterator i = m_backupList.begin(); i != m_backupList.end(); ++i ) {
246 out << " " << *i << std::endl;
249 out << "restore_list" << std::endl;
250 for( DBListType::iterator i = m_restoreList.begin(); i != m_restoreList.end(); ++i ) {
251 out << " " << *i << std::endl;
254 if( m_deviceName.size() ) {
255 out << "device_name " << m_deviceName << std::endl;
258 if( m_path.size() ) {
259 out << "backup_path " << m_path << std::endl;
262 out << "prompt_backup_label " << (m_promptBackupLabel ? 1 : 0) << std::endl;
264 if( !out ) {
265 m_last_error = "Error during write. Config may be incomplete.";
266 return false;
268 return true;
271 /// Compares a given databasedatabase from a real device with the
272 /// current config. If not yet configured, initialize with valid
273 /// defaults.
274 void ConfigFile::Enlighten(const Barry::DatabaseDatabase &db)
276 if( !m_loaded ) {
277 // if not fully loaded, we use db as our default list
278 // our defaults are: backup everything, restore everything
279 // except email
281 m_backupList.clear();
282 m_restoreList.clear();
284 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator i =
285 db.Databases.begin();
286 for( ; i != db.Databases.end(); ++i ) {
287 // backup everything
288 m_backupList.push_back(i->Name);
290 // restore everything except email (which could take ages)
291 // and Handheld Agent (which seems write protected)
292 if( i->Name != Barry::Message::GetDBName() &&
293 i->Name != "Handheld Agent" )
295 m_restoreList.push_back(i->Name);
301 /// Sets list with new config
302 void ConfigFile::SetBackupList(const DBListType &list)
304 m_backupList = list;
305 m_loaded = true;
308 void ConfigFile::SetRestoreList(const DBListType &list)
310 m_restoreList = list;
311 m_loaded = true;
314 void ConfigFile::SetDeviceName(const std::string &name)
316 if( name.size() )
317 m_deviceName = name;
318 else
319 m_deviceName = " ";
322 void ConfigFile::SetBackupPath(const std::string &path)
324 if( path.size() && CheckPath(path) )
325 m_path = path;
326 else
327 BuildDefaultPath();
330 void ConfigFile::SetPromptBackupLabel(bool prompt)
332 m_promptBackupLabel = prompt;
335 /// Checks that the path in path exists, and if not, creates it.
336 /// Returns false if unable to create path, true if ok.
337 bool ConfigFile::CheckPath(const std::string &path, std::string *perr)
339 if( path.size() == 0 ) {
340 if( perr )
341 *perr = "path is empty!";
342 return false;
345 if( access(path.c_str(), F_OK) == 0 )
346 return true;
348 std::string base;
349 std::string::size_type slash = 0;
350 while( (slash = path.find('/', slash + 1)) != std::string::npos ) {
351 base = path.substr(0, slash);
352 if( access(base.c_str(), F_OK) != 0 ) {
353 if( mkdir(base.c_str(), 0755) == -1 ) {
354 if( perr ) {
355 *perr = "mkdir(" + base + ") failed: ";
356 *perr += strerror(errno);
358 return false;
362 if( mkdir(path.c_str(), 0755) == -1 ) {
363 if( perr ) {
364 *perr = "last mkdir(" + path + ") failed: ";
365 *perr += strerror(errno);
367 return false;
369 return true;
374 //////////////////////////////////////////////////////////////////////////////
375 // GlobalConfigFile class members
377 GlobalConfigFile::GlobalConfigFile()
378 : m_loaded(false)
379 , m_verboseLogging(false)
381 BuildFilename();
382 Load();
385 GlobalConfigFile::GlobalConfigFile(const std::string &appname)
386 : m_loaded(false)
387 , m_appname(appname)
388 , m_verboseLogging(false)
390 // there can be no spaces in the appname
391 if( m_appname.find(' ') != std::string::npos )
392 throw std::logic_error("App name must have no spaces.");
394 BuildFilename();
395 Load();
398 GlobalConfigFile::~GlobalConfigFile()
402 void GlobalConfigFile::BuildFilename()
404 struct passwd *pw = getpwuid(getuid());
405 if( !pw )
406 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
408 m_filename = pw->pw_dir;
409 m_filename += "/.barry/config";
411 // build the global path too, since this never changes
412 m_path = pw->pw_dir;
413 m_path += "/.barry";
416 void GlobalConfigFile::Clear()
418 m_loaded = false;
419 m_lastDevice = 0;
422 void GlobalConfigFile::Load()
424 // start fresh
425 Clear();
427 // open input file
428 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
429 if( !in )
430 return;
432 std::string line;
434 while( std::getline(in, line) ) {
435 std::string keyword;
436 std::istringstream iss(line);
437 iss >> keyword;
439 if( keyword == "last_device" ) {
440 iss >> std::ws;
441 m_lastDevice.Clear();
442 iss >> m_lastDevice;
444 else if( keyword == "verbose_logging" ) {
445 int flag = 0;
446 iss >> flag;
447 m_verboseLogging = flag;
449 else {
450 // store any other keys as app keys
451 if( keyword.substr(0, 2) == "X-" ) {
452 iss >> std::ws;
453 line.clear();
454 std::getline(iss, line);
455 m_keymap[keyword] = line;
460 m_loaded = true;
463 /// Save the current global config, overwriting or creating as needed
464 bool GlobalConfigFile::Save()
466 if( !ConfigFile::CheckPath(m_path, &m_last_error) )
467 return false;
469 std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
470 if( !out ) {
471 m_last_error = "Unable to open " + m_filename + " for writing.";
472 return false;
475 if( !(m_lastDevice == 0) ) {
476 out << "last_device " << m_lastDevice.Str() << std::endl;
479 out << "verbose_logging " << (m_verboseLogging ? 1 : 0) << std::endl;
481 // store all app keys
482 keymap_type::const_iterator ci = m_keymap.begin();
483 for( ; ci != m_keymap.end(); ++ci ) {
484 out << ci->first << " " << ci->second << std::endl;
487 if( !out ) {
488 m_last_error = "Error during write. Config may be incomplete.";
489 return false;
491 return true;
494 void GlobalConfigFile::SetKey(const std::string &key, const std::string &value)
496 if( !m_appname.size() )
497 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
499 if( value.find_first_of("\n\r") != std::string::npos )
500 throw std::logic_error("SetKey values may not contain newline characters.");
502 std::string fullkey = "X-" + m_appname + "-" + key;
503 m_keymap[fullkey] = value;
506 std::string GlobalConfigFile::GetKey(const std::string &key,
507 const std::string &default_value) const
509 if( !m_appname.size() )
510 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
512 std::string fullkey = "X-" + m_appname + "-" + key;
513 keymap_type::const_iterator ci = m_keymap.find(fullkey);
514 if( ci == m_keymap.end() )
515 return default_value;
516 return ci->second;
519 void GlobalConfigFile::SetLastDevice(const Barry::Pin &pin)
521 m_lastDevice = pin;
524 void GlobalConfigFile::SetVerboseLogging(bool verbose)
526 m_verboseLogging = verbose;
530 } // namespace Barry