syncevolution/src/syncevo/FileConfigTree.cpp

247 lines
7.7 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 <string.h>
#include <ctype.h>
#include <syncevo/FileConfigTree.h>
#include <syncevo/IniConfigNode.h>
#include <syncevo/util.h>
#include <boost/foreach.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <syncevo/declarations.h>
using namespace std;
SE_BEGIN_CXX
FileConfigTree::FileConfigTree(const string &root,
SyncConfig::Layout layout) :
m_root(root),
m_layout(layout),
m_readonly(false)
{
}
void FileConfigTree::flush()
{
BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
node.second->flush();
}
}
void FileConfigTree::reload()
{
BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
node.second->reload();
}
}
/**
* remove config files, backup files of config files (with ~ at
* the end) and empty directories
*/
static bool rm_filter(const string &path, bool isDir)
{
if (isDir) {
// skip non-empty directories
ReadDir dir(path);
return dir.begin() == dir.end();
} else {
// only delete well-known files
return boost::ends_with(path, "/config.ini") ||
boost::ends_with(path, "/config.ini~") ||
boost::ends_with(path, "/config.txt") ||
boost::ends_with(path, "/config.txt~") ||
boost::ends_with(path, "/.other.ini") ||
boost::ends_with(path, "/.other.ini~") ||
boost::ends_with(path, "/.server.ini") ||
boost::ends_with(path, "/.server.ini~") ||
boost::ends_with(path, "/.internal.ini") ||
boost::ends_with(path, "/.internal.ini~") ||
path.find("/.synthesis/") != path.npos;
}
}
void FileConfigTree::remove(const string &path)
{
string fullpath = m_root + "/" + path;
clearNodes(fullpath);
rm_r(fullpath, rm_filter);
}
void FileConfigTree::reset()
{
for (NodeCache_t::iterator it = m_nodes.begin();
it != m_nodes.end();
++it) {
if (it->second.use_count() > 1) {
// If the use count is larger than 1, then someone besides
// the cache is referencing the node. We cannot force that
// other entity to drop the reference, so bail out here.
SE_THROW(it->second->getName() +
": cannot be removed while in use");
}
}
m_nodes.clear();
}
void FileConfigTree::clearNodes(const string &fullpath)
{
NodeCache_t::iterator it;
it = m_nodes.begin();
while (it != m_nodes.end()) {
const string &key = it->first;
if (boost::starts_with(key, fullpath)){
/* 'it = m_nodes.erase(it);' doesn't make sense
* because 'map::erase' returns 'void' in gcc. But other
* containers like list, vector could work! :(
* Below is STL recommended usage.
*/
NodeCache_t::iterator erased = it++;
if (erased->second.use_count() > 1) {
// same check as in reset()
SE_THROW(erased->second->getName() +
": cannot be removed while in use");
}
m_nodes.erase(erased);
} else {
++it;
}
}
}
boost::shared_ptr<ConfigNode> FileConfigTree::open(const string &path,
ConfigTree::PropertyType type,
const string &otherId)
{
string fullpath;
string filename;
fullpath = normalizePath(m_root + "/" + path + "/");
if (type == other) {
if (m_layout == SyncConfig::SYNC4J_LAYOUT) {
fullpath += "/changes";
if (!otherId.empty()) {
fullpath += "_";
fullpath += otherId;
}
filename = "config.txt";
} else {
filename += ".other";
if (!otherId.empty()) {
filename += "_";
filename += otherId;
}
filename += ".ini";
}
} else {
filename = type == server ? ".server.ini" :
m_layout == SyncConfig::SYNC4J_LAYOUT ? "config.txt" :
type == hidden ? ".internal.ini" :
"config.ini";
}
string fullname = normalizePath(fullpath + "/" + filename);
NodeCache_t::iterator found = m_nodes.find(fullname);
if (found != m_nodes.end()) {
return found->second;
} else if(type != other && type != server) {
boost::shared_ptr<ConfigNode> node(new IniFileConfigNode(fullpath, filename, m_readonly));
return m_nodes[fullname] = node;
} else {
boost::shared_ptr<ConfigNode> node(new IniHashConfigNode(fullpath, filename, m_readonly));
return m_nodes[fullname] = node;
}
}
boost::shared_ptr<ConfigNode> FileConfigTree::add(const string &path,
const boost::shared_ptr<ConfigNode> &node)
{
NodeCache_t::iterator found = m_nodes.find(path);
if (found != m_nodes.end()) {
return found->second;
} else {
m_nodes[path] = node;
return node;
}
}
static inline bool isNode(const string &dir, const string &name) {
struct stat buf;
string fullpath = dir + "/" + name;
return !stat(fullpath.c_str(), &buf) && S_ISDIR(buf.st_mode);
}
list<string> FileConfigTree::getChildren(const string &path)
{
list<string> res;
string fullpath;
fullpath = normalizePath(m_root + "/" + path);
// first look at existing files
if (!access(fullpath.c_str(), F_OK)) {
ReadDir dir(fullpath);
BOOST_FOREACH(const string entry, dir) {
if (isNode(fullpath, entry)) {
res.push_back(entry);
}
}
}
// Now also add those which have been created,
// but not saved yet. The full path must be
// <path>/<childname>/<filename>.
fullpath += "/";
BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
string currpath = node.first;
if (currpath.size() > fullpath.size() &&
currpath.substr(0, fullpath.size()) == fullpath) {
// path prefix matches, now check whether we have
// a real sibling, i.e. another full path below
// the prefix
size_t start = fullpath.size();
size_t end = currpath.find('/', start);
if (currpath.npos != end) {
// Okay, another path separator found.
// Now make sure we don't have yet another
// directory level.
if (currpath.npos == currpath.find('/', end + 1)) {
// Insert it if not there yet.
string name = currpath.substr(start, end - start);
if (res.end() == find(res.begin(), res.end(), name)) {
res.push_back(name);
}
}
}
}
}
return res;
}
SE_END_CXX