syncevolution/src/syncevo/SingleFileConfigTree.cpp
Patrick Ohly 006bcf2603 single file format for multiple .ini files (MBC #1208)
This patch adds the infrastructure for reading a single file
which contains multiple separate .ini files. Writing is not
implemented.

The Ini*ConfigNode classes are derived from the File*ConfigNode
classes and could replace those. The only reason for not doing
this right away is that the master branch is in code freeze in
preparation for 1.0. Removing File*ConfigNode and replacing
their usages should be done after the release.

The SingleFileConfigTree implements the splitting of the single
file into independent pieces. It instantiates IniFileConfigNodes
with in-memory access to the actual data, using the new classes
introduced for this case in the previous patches.

SingleFileConfigTree could also use other ConfigNodes, but that kind
of flexibility is not needed yet and also would require rethinking the
way how the single file is split. Right now the separators ("===
.... ===") are known to not occur in the individual pieces.

Like the FileConfigTree, this class must be able to preserve
nodes which were created without flushing the tree afterwards.
This is done when reading from a read-only template, which may
add new nodes or modify existing ones in memory.
2010-05-04 09:39:20 +02:00

242 lines
7.6 KiB
C++

/*
* Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
*
* 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 <config.h>
#include "test.h"
#include <syncevo/SingleFileConfigTree.h>
#include <syncevo/StringDataBlob.h>
#include <syncevo/FileDataBlob.h>
#include <syncevo/IniConfigNode.h>
#include <syncevo/util.h>
#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
using namespace std;
SingleFileConfigTree::SingleFileConfigTree(const boost::shared_ptr<DataBlob> &data) :
m_data(data)
{
readFile();
}
SingleFileConfigTree::SingleFileConfigTree(const string &fullpath) :
m_data(new FileDataBlob(fullpath, true))
{
readFile();
}
boost::shared_ptr<ConfigNode> SingleFileConfigTree::open(const string &filename)
{
string normalized = normalizePath(string("/") + filename);
boost::shared_ptr<ConfigNode> &entry = m_nodes[normalized];
if (entry) {
return entry;
}
string name = getRootPath() + " - " + normalized;
boost::shared_ptr<DataBlob> data;
BOOST_FOREACH(const FileContent_t::value_type &file, m_content) {
if (file.first == normalized) {
data.reset(new StringDataBlob(name, file.second, true));
break;
}
}
if (!data) {
/*
* creating new files not supported, would need support for detecting
* StringDataBlob::write()
*/
data.reset(new StringDataBlob(name, boost::shared_ptr<std::string>(), true));
}
entry.reset(new IniFileConfigNode(data));
return entry;
}
void SingleFileConfigTree::flush()
{
// not implemented, cannot write anyway
}
void SingleFileConfigTree::remove(const string &path)
{
SE_THROW("internal error: SingleFileConfigTree::remove() called");
}
void SingleFileConfigTree::reset()
{
m_nodes.clear();
readFile();
}
boost::shared_ptr<ConfigNode> SingleFileConfigTree::open(const string &path,
PropertyType type,
const string &otherId)
{
string fullpath = path;
if (!fullpath.empty()) {
fullpath += "/";
}
switch (type) {
case visible:
fullpath += "config.ini";
break;
case hidden:
fullpath += ".internal.ini";
break;
case other:
fullpath += ".other.ini";
break;
case server:
fullpath += ".server.ini";
break;
}
return open(fullpath);
}
static void checkChild(const string &normalized,
const string &node,
set<string> &subdirs)
{
if (boost::starts_with(node, normalized)) {
string remainder = node.substr(normalized.size());
size_t offset = remainder.find('/');
if (offset != remainder.npos) {
// only directories underneath path matter
subdirs.insert(remainder.substr(0, offset));
}
}
}
list<string> SingleFileConfigTree::getChildren(const string &path)
{
set<string> subdirs;
string normalized = normalizePath(string("/") + path);
if (normalized != "/") {
normalized += "/";
}
// must check both actual files as well as unsaved nodes
BOOST_FOREACH(const FileContent_t::value_type &file, m_content) {
checkChild(normalized, file.first, subdirs);
}
BOOST_FOREACH(const NodeCache_t::value_type &file, m_nodes) {
checkChild(normalized, file.first, subdirs);
}
list<string> result;
BOOST_FOREACH(const string &dir, subdirs) {
result.push_back(dir);
}
return result;
}
void SingleFileConfigTree::readFile()
{
boost::shared_ptr<istream> in(m_data->read());
boost::shared_ptr<string> content;
string line;
m_content.clear();
while (getline(*in, line)) {
if (boost::starts_with(line, "=== ") &&
boost::ends_with(line, " ===")) {
string name = line.substr(4, line.size() - 8);
name = normalizePath(string("/") + name);
content.reset(new string);
m_content[name] = content;
} else if (content) {
(*content) += line;
(*content) += "\n";
}
}
}
#ifdef ENABLE_UNIT_TESTS
class SingleIniTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(SingleIniTest);
CPPUNIT_TEST(simple);
CPPUNIT_TEST_SUITE_END();
void simple() {
boost::shared_ptr<string> data(new string);
data->assign("# comment\n"
"# foo\n"
"=== foo/config.ini ===\n"
"foo = bar\n"
"foo2 = bar2\n"
"=== foo/.config.ini ===\n"
"foo_internal = bar_internal\n"
"foo2_internal = bar2_internal\n"
"=== /bar/.internal.ini ===\n"
"bar = foo\n"
"=== sources/addressbook/config.ini ===\n"
"=== sources/calendar/config.ini ===\n"
"evolutionsource = Personal\n");
boost::shared_ptr<DataBlob> blob(new StringDataBlob("test", data, true));
SingleFileConfigTree tree(blob);
boost::shared_ptr<ConfigNode> node;
node = tree.open("foo/config.ini");
CPPUNIT_ASSERT(node);
CPPUNIT_ASSERT(node->exists());
CPPUNIT_ASSERT_EQUAL(string("test - /foo/config.ini"), node->getName());
CPPUNIT_ASSERT_EQUAL(string("bar"), node->readProperty("foo"));
CPPUNIT_ASSERT_EQUAL(string("bar2"), node->readProperty("foo2"));
node = tree.open("/foo/config.ini");
CPPUNIT_ASSERT(node);
CPPUNIT_ASSERT(node->exists());
node = tree.open("foo//.config.ini");
CPPUNIT_ASSERT(node);
CPPUNIT_ASSERT(node->exists());
CPPUNIT_ASSERT_EQUAL(string("bar_internal"), node->readProperty("foo_internal"));
CPPUNIT_ASSERT_EQUAL(string("bar2_internal"), node->readProperty("foo2_internal"));
node = tree.open("bar///./.internal.ini");
CPPUNIT_ASSERT(node);
CPPUNIT_ASSERT(node->exists());
CPPUNIT_ASSERT_EQUAL(string("foo"), node->readProperty("bar"));
node = tree.open("sources/addressbook/config.ini");
CPPUNIT_ASSERT(node);
CPPUNIT_ASSERT(node->exists());
node = tree.open("sources/calendar/config.ini");
CPPUNIT_ASSERT(node);
CPPUNIT_ASSERT(node->exists());
CPPUNIT_ASSERT_EQUAL(string("Personal"), node->readProperty("evolutionsource"));
node = tree.open("no-such-source/config.ini");
CPPUNIT_ASSERT(node);
CPPUNIT_ASSERT(!node->exists());
list<string> dirs = tree.getChildren("");
CPPUNIT_ASSERT_EQUAL(string("bar|foo|no-such-source|sources"), boost::join(dirs, "|"));
dirs = tree.getChildren("sources/");
CPPUNIT_ASSERT_EQUAL(string("addressbook|calendar"), boost::join(dirs, "|"));
}
};
SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SingleIniTest);
#endif // ENABLE_UNIT_TESTS
SE_END_CXX