8b2503ddc6
Moved ConfigNode::createFileNode() into ConfigNode.cpp where it belongs. Moving it now allows us to replace FileConfigNode.cpp later on.
421 lines
11 KiB
C++
421 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
|
|
* Copyright (C) 2009 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include <syncevo/FileConfigNode.h>
|
|
#include <syncevo/SafeConfigNode.h>
|
|
#include <syncevo/SyncContext.h>
|
|
#include <syncevo/util.h>
|
|
|
|
#include <boost/scoped_array.hpp>
|
|
#include <boost/foreach.hpp>
|
|
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <syncevo/declarations.h>
|
|
SE_BEGIN_CXX
|
|
|
|
FileBaseConfigNode::FileBaseConfigNode(const string &path, const string &fileName, bool readonly) :
|
|
m_path(path),
|
|
m_fileName(fileName),
|
|
m_modified(false),
|
|
m_readonly(readonly),
|
|
m_exists(false)
|
|
{
|
|
}
|
|
|
|
void FileBaseConfigNode::flush()
|
|
{
|
|
if (!m_modified) {
|
|
return;
|
|
}
|
|
|
|
if (m_readonly) {
|
|
throw std::runtime_error(m_path + "/" + m_fileName + ": internal error: flushing read-only file config node not allowed");
|
|
}
|
|
|
|
mkdir_p(m_path);
|
|
|
|
string filename = m_path + "/" + m_fileName;
|
|
string tmpFilename = m_path + "/.#" + m_fileName;
|
|
|
|
FILE *file = fopen(tmpFilename.c_str(), "w");
|
|
if (file) {
|
|
toFile(file);
|
|
fflush(file);
|
|
bool failed = ferror(file);
|
|
if (fclose(file)) {
|
|
failed = true;
|
|
}
|
|
if (failed ||
|
|
rename(tmpFilename.c_str(), filename.c_str())) {
|
|
SyncContext::throwError(tmpFilename, errno);
|
|
}
|
|
} else {
|
|
SyncContext::throwError(tmpFilename, errno);
|
|
}
|
|
|
|
m_modified = false;
|
|
m_exists = true;
|
|
}
|
|
|
|
FileConfigNode::FileConfigNode(const string &path, const string &fileName, bool readonly) :
|
|
FileBaseConfigNode(path,fileName,readonly)
|
|
{
|
|
read();
|
|
}
|
|
|
|
void FileConfigNode::toFile(FILE* file) {
|
|
BOOST_FOREACH(const string &line, m_lines) {
|
|
fprintf(file, "%s\n", line.c_str());
|
|
}
|
|
}
|
|
|
|
void FileConfigNode::read()
|
|
{
|
|
string filename = m_path + "/" + m_fileName;
|
|
|
|
FILE *file = fopen(filename.c_str(), "r");
|
|
char buffer[512];
|
|
|
|
m_lines.clear();
|
|
if (file) {
|
|
/** add check to avoid errors when a line is larger than 512 bytes */
|
|
string line;
|
|
while (fgets(buffer, sizeof(buffer), file)) {
|
|
char *eol = strchr(buffer, '\n');
|
|
if (eol) {
|
|
*eol = 0;
|
|
line += buffer;
|
|
} else {
|
|
line += buffer;
|
|
continue;
|
|
}
|
|
m_lines.push_back(line);
|
|
line = "";
|
|
}
|
|
m_exists = true;
|
|
fclose(file);
|
|
}
|
|
m_modified = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* get property and value from line, if any present
|
|
*/
|
|
static bool getContent(const string &line,
|
|
string &property,
|
|
string &value,
|
|
bool &isComment,
|
|
bool fuzzyComments)
|
|
{
|
|
size_t start = 0;
|
|
while (start < line.size() &&
|
|
isspace(line[start])) {
|
|
start++;
|
|
}
|
|
|
|
// empty line?
|
|
if (start == line.size()) {
|
|
return false;
|
|
}
|
|
|
|
// Comment? Potentially keep reading, might be commented out assignment.
|
|
isComment = false;
|
|
if (line[start] == '#') {
|
|
if (!fuzzyComments) {
|
|
return false;
|
|
}
|
|
isComment = true;
|
|
}
|
|
|
|
// recognize # <word> = <value> as commented out (= default) value
|
|
if (isComment) {
|
|
start++;
|
|
while (start < line.size() &&
|
|
isspace(line[start])) {
|
|
start++;
|
|
}
|
|
}
|
|
|
|
// extract property
|
|
size_t end = start;
|
|
while (end < line.size() &&
|
|
!isspace(line[end])) {
|
|
end++;
|
|
}
|
|
property = line.substr(start, end - start);
|
|
|
|
// skip assignment
|
|
start = end;
|
|
while (start < line.size() &&
|
|
isspace(line[start])) {
|
|
start++;
|
|
}
|
|
if (start == line.size() ||
|
|
line[start] != '=') {
|
|
// invalid syntax or we tried to read a comment as assignment
|
|
return false;
|
|
}
|
|
|
|
// extract value
|
|
start++;
|
|
while (start < line.size() &&
|
|
isspace(line[start])) {
|
|
start++;
|
|
}
|
|
|
|
value = line.substr(start);
|
|
// remove trailing white space: usually it is
|
|
// added accidentally by users
|
|
size_t numspaces = 0;
|
|
while (numspaces < value.size() &&
|
|
isspace(value[value.size() - 1 - numspaces])) {
|
|
numspaces++;
|
|
}
|
|
value.erase(value.size() - numspaces);
|
|
|
|
// @TODO: strip quotation marks around value?!
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* check whether the line contains the property and if so, extract its value
|
|
*/
|
|
static bool getValue(const string &line,
|
|
const string &property,
|
|
string &value,
|
|
bool &isComment,
|
|
bool fuzzyComments)
|
|
|
|
{
|
|
string curProp;
|
|
return getContent(line, curProp, value, isComment, fuzzyComments) &&
|
|
!strcasecmp(curProp.c_str(), property.c_str());
|
|
}
|
|
|
|
string FileConfigNode::readProperty(const string &property) const {
|
|
string value;
|
|
|
|
BOOST_FOREACH(const string &line, m_lines) {
|
|
bool isComment;
|
|
|
|
if (getValue(line, property, value, isComment, false)) {
|
|
return value;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
|
|
void FileConfigNode::readProperties(ConfigProps &props) const {
|
|
map<string, string> res;
|
|
string value, property;
|
|
|
|
BOOST_FOREACH(const string &line, m_lines) {
|
|
bool isComment;
|
|
if (getContent(line, property, value, isComment, false)) {
|
|
// don't care about the result: only the first instance
|
|
// of the property counts, so it doesn't matter when
|
|
// inserting it again later fails
|
|
props.insert(pair<string, string>(property, value));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileConfigNode::removeProperty(const string &property)
|
|
{
|
|
string value;
|
|
|
|
list<string>::iterator it = m_lines.begin();
|
|
while (it != m_lines.end()) {
|
|
const string &line = *it;
|
|
bool isComment;
|
|
if (getValue(line, property, value, isComment, false)) {
|
|
it = m_lines.erase(it);
|
|
m_modified = true;
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileConfigNode::setProperty(const string &property,
|
|
const string &newvalue,
|
|
const string &comment,
|
|
const string *defValue) {
|
|
string newstr;
|
|
string oldvalue;
|
|
bool isDefault = false;
|
|
|
|
if (defValue &&
|
|
*defValue == newvalue) {
|
|
newstr += "# ";
|
|
isDefault = true;
|
|
}
|
|
newstr += property + " = " + newvalue;
|
|
|
|
BOOST_FOREACH(string &line, m_lines) {
|
|
bool isComment;
|
|
|
|
if (getValue(line, property, oldvalue, isComment, true)) {
|
|
if (newvalue != oldvalue ||
|
|
(isComment && !isDefault)) {
|
|
line = newstr;
|
|
m_modified = true;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// add each line of the comment as separate line in .ini file
|
|
if (comment.size()) {
|
|
list<string> commentLines;
|
|
ConfigProperty::splitComment(comment, commentLines);
|
|
if (m_lines.size()) {
|
|
m_lines.push_back("");
|
|
}
|
|
BOOST_FOREACH(const string &comment, commentLines) {
|
|
m_lines.push_back(string("# ") + comment);
|
|
}
|
|
}
|
|
|
|
m_lines.push_back(newstr);
|
|
m_modified = true;
|
|
}
|
|
|
|
void FileConfigNode::clear()
|
|
{
|
|
m_lines.clear();
|
|
m_modified = true;
|
|
}
|
|
|
|
HashFileConfigNode::HashFileConfigNode(const string &path, const string &fileName, bool readonly) :
|
|
FileBaseConfigNode(path,fileName,readonly)
|
|
{
|
|
read();
|
|
}
|
|
|
|
void HashFileConfigNode::read()
|
|
{
|
|
string filename = m_path + "/" + m_fileName;
|
|
|
|
FILE *file = fopen(filename.c_str(), "r");
|
|
char buffer[512];
|
|
|
|
if (file) {
|
|
string line;
|
|
while (fgets(buffer, sizeof(buffer), file)) {
|
|
char *eol = strchr(buffer, '\n');
|
|
if (eol) {
|
|
*eol = 0;
|
|
line += buffer;
|
|
}else{
|
|
line += buffer;
|
|
continue;
|
|
}
|
|
string property, value;
|
|
bool isComment;
|
|
if (getContent(line, property, value, isComment, false)) {
|
|
m_props.insert(StringPair(property, value));
|
|
}
|
|
line = "";
|
|
}
|
|
m_exists = true;
|
|
fclose(file);
|
|
}
|
|
m_modified = false;
|
|
}
|
|
|
|
void HashFileConfigNode::toFile(FILE* file) {
|
|
BOOST_FOREACH(const StringPair &prop, m_props) {
|
|
fprintf(file, "%s = %s\n", prop.first.c_str(),prop.second.c_str());
|
|
}
|
|
}
|
|
|
|
void HashFileConfigNode::readProperties(ConfigProps &props) const {
|
|
BOOST_FOREACH(const StringPair &prop, m_props) {
|
|
props.insert(prop);
|
|
}
|
|
}
|
|
|
|
void HashFileConfigNode::writeProperties(const ConfigProps &props) {
|
|
if (!props.empty()) {
|
|
m_props.insert(props.begin(), props.end());
|
|
m_modified = true;
|
|
}
|
|
}
|
|
|
|
|
|
string HashFileConfigNode::readProperty(const string &property) const {
|
|
std::map<std::string, std::string>::const_iterator it = m_props.find(property);
|
|
if (it != m_props.end()) {
|
|
return it->second;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void HashFileConfigNode::removeProperty(const string &property){
|
|
map<string, string>::iterator it = m_props.find(property);
|
|
if(it != m_props.end()) {
|
|
m_props.erase(it);
|
|
m_modified = true;
|
|
}
|
|
}
|
|
|
|
void HashFileConfigNode::clear()
|
|
{
|
|
if (!m_props.empty()) {
|
|
m_props.clear();
|
|
m_modified = true;
|
|
}
|
|
}
|
|
|
|
void HashFileConfigNode::setProperty(const string &property,
|
|
const string &newvalue,
|
|
const string &comment,
|
|
const string *defValue) {
|
|
/** we don't support property comments here. Also, we ignore comment*/
|
|
if (defValue &&
|
|
*defValue == newvalue) {
|
|
removeProperty(property);
|
|
return;
|
|
}
|
|
map<string, string>::iterator it = m_props.find(property);
|
|
if(it != m_props.end()) {
|
|
string oldvalue = it->second;
|
|
if(oldvalue != newvalue) {
|
|
m_props.erase(it);
|
|
m_props.insert(StringPair(property, newvalue));
|
|
m_modified = true;
|
|
}
|
|
} else {
|
|
m_props.insert(StringPair(property, newvalue));
|
|
m_modified = true;
|
|
}
|
|
}
|
|
|
|
|
|
SE_END_CXX
|