lib: fixed parsing of recurring VEVENTS: DAILY and interval support
[barry/progweb.git] / src / configfile.cc
blob35a88c31e922739f7ad0b409296ca0bcdb60eb9b
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;
89 std::ostream& operator<< (std::ostream &os, const ConfigFile::DBListType &list)
91 os << "DBListType dump:\n";
93 for( ConfigFile::DBListType::const_iterator i = list.begin();
94 i != list.end();
95 ++i )
97 os << " " << *i << "\n";
99 return os;
103 //////////////////////////////////////////////////////////////////////////////
104 // ConfigFile class members
106 /// Loads config file for the given pin, and ends up in an
107 /// unenlightened state. Throws ConfigFileError on error,
108 /// but it is not an error if the config does not exist.
109 /// Never use this if you have a DatabaseDatabase object!
110 /// This ctor is only for temporary loading of config data.
111 ConfigFile::ConfigFile(Barry::Pin pin)
112 : m_pin(pin)
113 , m_loaded(false)
114 , m_promptBackupLabel(false)
115 , m_autoSelectAll(false)
117 if( m_pin == 0 )
118 throw ConfigFileError("Configfile: empty pin");
120 BuildFilename();
121 BuildDefaultPath(); // this handles the situation that path is not set
122 Load();
125 /// Opens and loads config file for given pin, and calls Enlighten
126 /// Throws ConfigFileError on error. Should never fail unless
127 /// passed a bad pin.
128 ConfigFile::ConfigFile(Barry::Pin pin,
129 const Barry::DatabaseDatabase &db)
130 : m_pin(pin)
131 , m_loaded(false)
132 , m_promptBackupLabel(false)
133 , m_autoSelectAll(false)
135 if( m_pin == 0 )
136 throw ConfigFileError("Configfile: empty pin");
138 BuildFilename();
139 BuildDefaultPath();
140 Load();
141 Enlighten(db);
144 ConfigFile::~ConfigFile()
148 void ConfigFile::BuildFilename()
150 size_t strsize = 255 * 5;
151 char *strbuf = new char[strsize];
152 struct passwd pwbuf;
153 struct passwd *pw;
155 getpwuid_r(getuid(), &pwbuf, strbuf, strsize, &pw);
156 if( !pw ) {
157 delete [] strbuf;
158 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
161 m_filename = pw->pw_dir;
162 m_filename += "/.barry/backup/";
163 m_filename += m_pin.Str();
164 m_filename += "/config";
166 delete [] strbuf;
169 void ConfigFile::BuildDefaultPath()
171 struct passwd *pw = getpwuid(getuid());
172 m_path = pw->pw_dir;
173 m_path += "/.barry/backup/";
174 m_path += m_pin.Str();
177 void ConfigFile::Clear()
179 m_loaded = false;
180 m_backupList.clear();
181 m_restoreList.clear();
182 m_deviceName.clear();
183 m_promptBackupLabel = false;
184 m_autoSelectAll = false;
187 /// Attempt to load the configuration file, but do not fail if not available
188 void ConfigFile::Load()
190 // start fresh
191 Clear();
193 // open input file
194 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
195 if( !in )
196 return;
198 std::string line;
199 DBListType *pList = 0;
201 while( std::getline(in, line) ) {
202 std::string keyword;
203 std::istringstream iss(line);
204 iss >> keyword;
206 if( keyword == "backup_list" ) {
207 pList = &m_backupList;
209 else if( keyword == "restore_list" ) {
210 pList = &m_restoreList;
212 else if( line[0] == ' ' && pList ) {
213 pList->push_back(line.c_str() + 1);
215 else {
216 pList = 0;
218 // add all remaining keyword checks here
219 if( keyword == "device_name" ) {
220 iss >> std::ws;
221 std::getline(iss, m_deviceName);
222 if( m_deviceName.size() == 0 ) {
223 // if there is a device_name setting,
224 // then this value must hold something,
225 // so that the user can ignore this
226 // field, and not get pestered all
227 // the time
228 m_deviceName = " ";
231 else if( keyword == "backup_path" ) {
232 iss >> std::ws;
233 std::getline(iss, m_path);
234 if( (m_path.size() == 0) || !(CheckPath(m_path)))
235 BuildDefaultPath();
237 else if( keyword == "prompt_backup_label" ) {
238 int flag;
239 iss >> flag;
240 m_promptBackupLabel = flag;
242 else if( keyword == "auto_select_all" ) {
243 int flag;
244 iss >> flag;
245 m_autoSelectAll = flag;
250 m_loaded = true;
253 /// Saves current device's config, overwriting or creating a config file
254 bool ConfigFile::Save()
256 using namespace std;
258 if( !CheckPath(m_path, &m_last_error) )
259 return false;
261 ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
262 if( !out ) {
263 m_last_error = "Unable to open " + m_filename + " for writing.";
264 return false;
267 out << "backup_list" << endl;
268 for( DBListType::iterator i = m_backupList.begin(); i != m_backupList.end(); ++i ) {
269 out << " " << *i << endl;
272 out << "restore_list" << endl;
273 for( DBListType::iterator i = m_restoreList.begin(); i != m_restoreList.end(); ++i ) {
274 out << " " << *i << endl;
277 if( m_deviceName.size() ) {
278 out << "device_name " << m_deviceName << endl;
281 if( m_path.size() ) {
282 out << "backup_path " << m_path << endl;
285 out << "prompt_backup_label " << (m_promptBackupLabel ? 1 : 0) << endl;
286 out << "auto_select_all " << (m_autoSelectAll ? 1 : 0) << endl;
288 if( !out ) {
289 m_last_error = "Error during write. Config may be incomplete.";
290 return false;
292 return true;
295 /// Compares a given databasedatabase from a real device with the
296 /// current config. If not yet configured, initialize with valid
297 /// defaults.
298 void ConfigFile::Enlighten(const Barry::DatabaseDatabase &db)
300 if( !m_loaded ) {
301 // if not fully loaded, we use db as our default list
302 // our defaults are: backup everything, restore everything
303 // except email
305 m_backupList.clear();
306 m_restoreList.clear();
308 Barry::DatabaseDatabase::DatabaseArrayType::const_iterator i =
309 db.Databases.begin();
310 for( ; i != db.Databases.end(); ++i ) {
311 // backup everything
312 m_backupList.push_back(i->Name);
314 // restore everything except email (which could take ages)
315 // and Handheld Agent (which seems write protected)
316 if( i->Name != Barry::Message::GetDBName() &&
317 i->Name != "Handheld Agent" )
319 m_restoreList.push_back(i->Name);
325 // fill list with all databases from dbdb
326 ConfigFile:: DBListType& ConfigFile::DBListType::operator=(const DatabaseDatabase &dbdb)
328 // start empty
329 clear();
331 // copy over all DB names
332 DatabaseDatabase::DatabaseArrayType::const_iterator
333 i = dbdb.Databases.begin(), e = dbdb.Databases.end();
334 for( ; i != e; ++i ) {
335 push_back(i->Name);
338 return *this;
341 /// Sets list with new config
342 void ConfigFile::SetBackupList(const DBListType &list)
344 m_backupList = list;
345 m_loaded = true;
348 void ConfigFile::SetRestoreList(const DBListType &list)
350 m_restoreList = list;
351 m_loaded = true;
354 void ConfigFile::SetDeviceName(const std::string &name)
356 if( name.size() )
357 m_deviceName = name;
358 else
359 m_deviceName = " ";
362 void ConfigFile::SetBackupPath(const std::string &path)
364 if( path.size() && CheckPath(path) )
365 m_path = path;
366 else
367 BuildDefaultPath();
370 void ConfigFile::SetPromptBackupLabel(bool prompt)
372 m_promptBackupLabel = prompt;
375 void ConfigFile::SetAutoSelectAll(bool asa)
377 m_autoSelectAll = asa;
380 /// Checks that the path in path exists, and if not, creates it.
381 /// Returns false if unable to create path, true if ok.
382 bool ConfigFile::CheckPath(const std::string &path, std::string *perr)
384 if( path.size() == 0 ) {
385 if( perr )
386 *perr = "path is empty!";
387 return false;
390 if( access(path.c_str(), F_OK) == 0 )
391 return true;
393 std::string base;
394 std::string::size_type slash = 0;
395 while( (slash = path.find('/', slash + 1)) != std::string::npos ) {
396 base = path.substr(0, slash);
397 if( access(base.c_str(), F_OK) != 0 ) {
398 if( mkdir(base.c_str(), 0755) == -1 ) {
399 if( perr ) {
400 *perr = "mkdir(" + base + ") failed: ";
401 *perr += strerror(errno);
403 return false;
407 if( mkdir(path.c_str(), 0755) == -1 ) {
408 if( perr ) {
409 *perr = "last mkdir(" + path + ") failed: ";
410 *perr += strerror(errno);
412 return false;
414 return true;
419 //////////////////////////////////////////////////////////////////////////////
420 // GlobalConfigFile class members
422 GlobalConfigFile::GlobalConfigFile()
423 : m_loaded(false)
424 , m_verboseLogging(false)
426 BuildFilename();
427 Load();
430 GlobalConfigFile::GlobalConfigFile(const std::string &appname)
431 : m_loaded(false)
432 , m_appname(appname)
433 , m_verboseLogging(false)
435 // there can be no spaces in the appname
436 if( m_appname.find(' ') != std::string::npos )
437 throw std::logic_error("App name must have no spaces.");
439 BuildFilename();
440 Load();
443 GlobalConfigFile::~GlobalConfigFile()
447 void GlobalConfigFile::BuildFilename()
449 struct passwd *pw = getpwuid(getuid());
450 if( !pw )
451 throw ConfigFileError("BuildFilename: getpwuid failed", errno);
453 m_filename = pw->pw_dir;
454 m_filename += "/.barry/config";
456 // build the global path too, since this never changes
457 m_path = pw->pw_dir;
458 m_path += "/.barry";
461 void GlobalConfigFile::Clear()
463 m_loaded = false;
464 m_lastDevice = 0;
467 void GlobalConfigFile::Load()
469 // start fresh
470 Clear();
472 // open input file
473 std::ifstream in(m_filename.c_str(), std::ios::in | std::ios::binary);
474 if( !in )
475 return;
477 std::string line;
479 while( std::getline(in, line) ) {
480 std::string keyword;
481 std::istringstream iss(line);
482 iss >> keyword;
484 if( keyword == "last_device" ) {
485 iss >> std::ws;
486 m_lastDevice.Clear();
487 iss >> m_lastDevice;
489 else if( keyword == "verbose_logging" ) {
490 int flag = 0;
491 iss >> flag;
492 m_verboseLogging = flag;
494 else {
495 // store any other keys as app keys
496 if( keyword.substr(0, 2) == "X-" ) {
497 iss >> std::ws;
498 line.clear();
499 std::getline(iss, line);
500 m_keymap[keyword] = line;
505 m_loaded = true;
508 /// Save the current global config, overwriting or creating as needed
509 bool GlobalConfigFile::Save()
511 if( !ConfigFile::CheckPath(m_path, &m_last_error) )
512 return false;
514 std::ofstream out(m_filename.c_str(), std::ios::out | std::ios::binary);
515 if( !out ) {
516 m_last_error = "Unable to open " + m_filename + " for writing.";
517 return false;
520 if( !(m_lastDevice == 0) ) {
521 out << "last_device " << m_lastDevice.Str() << std::endl;
524 out << "verbose_logging " << (m_verboseLogging ? 1 : 0) << std::endl;
526 // store all app keys
527 keymap_type::const_iterator ci = m_keymap.begin();
528 for( ; ci != m_keymap.end(); ++ci ) {
529 out << ci->first << " " << ci->second << std::endl;
532 if( !out ) {
533 m_last_error = "Error during write. Config may be incomplete.";
534 return false;
536 return true;
539 void GlobalConfigFile::SetKey(const std::string &key, const std::string &value)
541 if( !m_appname.size() )
542 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
544 if( value.find_first_of("\n\r") != std::string::npos )
545 throw std::logic_error("SetKey values may not contain newline characters.");
547 std::string fullkey = "X-" + m_appname + "-" + key;
548 m_keymap[fullkey] = value;
551 std::string GlobalConfigFile::GetKey(const std::string &key,
552 const std::string &default_value) const
554 if( !m_appname.size() )
555 throw std::logic_error("Cannot use SetKey() without specifying an appname in the constructor.");
557 std::string fullkey = "X-" + m_appname + "-" + key;
558 keymap_type::const_iterator ci = m_keymap.find(fullkey);
559 if( ci == m_keymap.end() )
560 return default_value;
561 return ci->second;
564 void GlobalConfigFile::SetLastDevice(const Barry::Pin &pin)
566 m_lastDevice = pin;
569 void GlobalConfigFile::SetVerboseLogging(bool verbose)
571 m_verboseLogging = verbose;
575 } // namespace Barry