2 * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 #include <syncevo/FileConfigTree.h>
24 #include <syncevo/IniConfigNode.h>
25 #include <syncevo/util.h>
27 #include <boost/foreach.hpp>
28 #include <boost/algorithm/string/predicate.hpp>
36 #include <syncevo/declarations.h>
40 FileConfigTree::FileConfigTree(const string &root,
42 SyncConfig::Layout layout) :
50 string FileConfigTree::getRootPath() const
52 return normalizePath(m_root + "/" + m_peer);
55 void FileConfigTree::flush()
57 BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
60 if (m_layout == SyncConfig::SHARED_LAYOUT) {
61 // ensure that "peers" directory exists for new-style configs,
62 // not created by flushing nodes for pure context configs but
63 // needed to detect new-syle configs
64 mkdir_p(getRootPath() + "/peers");
68 void FileConfigTree::reload()
70 BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
71 node.second->reload();
76 * remove config files, backup files of config files (with ~ at
77 * the end) and empty directories
79 static bool rm_filter(const string &path, bool isDir)
82 // skip non-empty directories
84 return dir.begin() == dir.end();
86 // only delete well-known files
87 return boost::ends_with(path, "/config.ini") ||
88 boost::ends_with(path, "/config.ini~") ||
89 boost::ends_with(path, "/config.txt") ||
90 boost::ends_with(path, "/config.txt~") ||
91 boost::ends_with(path, "/.other.ini") ||
92 boost::ends_with(path, "/.other.ini~") ||
93 boost::ends_with(path, "/.server.ini") ||
94 boost::ends_with(path, "/.server.ini~") ||
95 boost::ends_with(path, "/.internal.ini") ||
96 boost::ends_with(path, "/.internal.ini~") ||
97 path.find("/.synthesis/") != path.npos;
101 void FileConfigTree::remove(const string &path)
103 string fullpath = m_root + "/" + path;
104 clearNodes(fullpath);
105 rm_r(fullpath, rm_filter);
108 void FileConfigTree::reset()
110 for (NodeCache_t::iterator it = m_nodes.begin();
113 if (it->second.use_count() > 1) {
114 // If the use count is larger than 1, then someone besides
115 // the cache is referencing the node. We cannot force that
116 // other entity to drop the reference, so bail out here.
117 SE_THROW(it->second->getName() +
118 ": cannot be removed while in use");
124 void FileConfigTree::clearNodes(const string &fullpath)
126 NodeCache_t::iterator it;
127 it = m_nodes.begin();
128 while (it != m_nodes.end()) {
129 const string &key = it->first;
130 if (boost::starts_with(key, fullpath)){
131 /* 'it = m_nodes.erase(it);' doesn't make sense
132 * because 'map::erase' returns 'void' in gcc. But other
133 * containers like list, vector could work! :(
134 * Below is STL recommended usage.
136 NodeCache_t::iterator erased = it++;
137 if (erased->second.use_count() > 1) {
138 // same check as in reset()
139 SE_THROW(erased->second->getName() +
140 ": cannot be removed while in use");
142 m_nodes.erase(erased);
149 boost::shared_ptr<ConfigNode> FileConfigTree::open(const string &path,
150 ConfigTree::PropertyType type,
151 const string &otherId)
156 fullpath = normalizePath(m_root + "/" + path + "/");
158 if (m_layout == SyncConfig::SYNC4J_LAYOUT) {
159 fullpath += "/changes";
160 if (!otherId.empty()) {
164 filename = "config.txt";
166 filename += ".other";
167 if (!otherId.empty()) {
174 filename = type == server ? ".server.ini" :
175 m_layout == SyncConfig::SYNC4J_LAYOUT ? "config.txt" :
176 type == hidden ? ".internal.ini" :
180 string fullname = normalizePath(fullpath + "/" + filename);
181 NodeCache_t::iterator found = m_nodes.find(fullname);
182 if (found != m_nodes.end()) {
183 return found->second;
184 } else if(type != other && type != server) {
185 boost::shared_ptr<ConfigNode> node(new IniFileConfigNode(fullpath, filename, m_readonly));
186 return m_nodes[fullname] = node;
188 boost::shared_ptr<ConfigNode> node(new IniHashConfigNode(fullpath, filename, m_readonly));
189 return m_nodes[fullname] = node;
193 boost::shared_ptr<ConfigNode> FileConfigTree::add(const string &path,
194 const boost::shared_ptr<ConfigNode> &node)
196 NodeCache_t::iterator found = m_nodes.find(path);
197 if (found != m_nodes.end()) {
198 return found->second;
200 m_nodes[path] = node;
205 static inline bool isNode(const string &dir, const string &name) {
207 string fullpath = dir + "/" + name;
208 return !stat(fullpath.c_str(), &buf) && S_ISDIR(buf.st_mode);
211 list<string> FileConfigTree::getChildren(const string &path)
216 fullpath = normalizePath(m_root + "/" + path);
218 // first look at existing files
219 if (!access(fullpath.c_str(), F_OK)) {
220 ReadDir dir(fullpath);
221 BOOST_FOREACH(const string entry, dir) {
222 if (isNode(fullpath, entry)) {
223 res.push_back(entry);
228 // Now also add those which have been created,
229 // but not saved yet. The full path must be
230 // <path>/<childname>/<filename>.
232 BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
233 string currpath = node.first;
234 if (currpath.size() > fullpath.size() &&
235 currpath.substr(0, fullpath.size()) == fullpath) {
236 // path prefix matches, now check whether we have
237 // a real sibling, i.e. another full path below
239 size_t start = fullpath.size();
240 size_t end = currpath.find('/', start);
241 if (currpath.npos != end) {
242 // Okay, another path separator found.
243 // Now make sure we don't have yet another
245 if (currpath.npos == currpath.find('/', end + 1)) {
246 // Insert it if not there yet.
247 string name = currpath.substr(start, end - start);
248 if (res.end() == find(res.begin(), res.end(), name)) {