Imported Upstream version 1.2.99~20120606~SE~ff65aef~SYSYNC~2728cb4
[platform/upstream/syncevolution.git] / src / syncevo / FileConfigTree.cpp
1 /*
2  * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  *
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.
8  *
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.
13  *
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
17  * 02110-1301  USA
18  */
19
20 #include <string.h>
21 #include <ctype.h>
22
23 #include <syncevo/FileConfigTree.h>
24 #include <syncevo/IniConfigNode.h>
25 #include <syncevo/util.h>
26
27 #include <boost/foreach.hpp>
28 #include <boost/algorithm/string/predicate.hpp>
29
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <dirent.h>
35
36 #include <syncevo/declarations.h>
37 using namespace std;
38 SE_BEGIN_CXX
39
40 FileConfigTree::FileConfigTree(const string &root,
41                                const string &peer,
42                                SyncConfig::Layout layout) :
43     m_root(root),
44     m_peer(peer),
45     m_layout(layout),
46     m_readonly(false)
47 {
48 }
49
50 string FileConfigTree::getRootPath() const
51 {
52     return normalizePath(m_root + "/" + m_peer);
53 }
54
55 void FileConfigTree::flush()
56 {
57     BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
58         node.second->flush();
59     }
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");
65     }
66 }
67
68 void FileConfigTree::reload()
69 {
70     BOOST_FOREACH(const NodeCache_t::value_type &node, m_nodes) {
71         node.second->reload();
72     }
73 }
74
75 /**
76  * remove config files, backup files of config files (with ~ at
77  * the end) and empty directories
78  */
79 static bool rm_filter(const string &path, bool isDir)
80 {
81     if (isDir) {
82         // skip non-empty directories
83         ReadDir dir(path);
84         return dir.begin() == dir.end();
85     } else {
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;
98     }
99 }
100
101 void FileConfigTree::remove(const string &path)
102 {
103     string fullpath = m_root + "/" + path;
104     clearNodes(fullpath);
105     rm_r(fullpath, rm_filter);
106 }
107
108 void FileConfigTree::reset()
109 {
110     for (NodeCache_t::iterator it = m_nodes.begin();
111          it != m_nodes.end();
112          ++it) {
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");
119         }
120     }
121     m_nodes.clear();
122 }
123
124 void FileConfigTree::clearNodes(const string &fullpath) 
125 {
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. 
135              */
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");
141             }
142             m_nodes.erase(erased);
143         } else {
144             ++it;
145         }
146     }
147 }
148
149 boost::shared_ptr<ConfigNode> FileConfigTree::open(const string &path,
150                                                    ConfigTree::PropertyType type,
151                                                    const string &otherId)
152 {
153     string fullpath;
154     string filename;
155     
156     fullpath = normalizePath(m_root + "/" + path + "/");
157     if (type == other) {
158         if (m_layout == SyncConfig::SYNC4J_LAYOUT) {
159             fullpath += "/changes";
160             if (!otherId.empty()) {
161                 fullpath += "_";
162                 fullpath += otherId;
163             }
164             filename = "config.txt";
165         } else {
166             filename += ".other";
167             if (!otherId.empty()) {
168                 filename += "_";
169                 filename += otherId;
170             }
171             filename += ".ini";
172         }
173     } else {
174         filename = type == server ? ".server.ini" :
175             m_layout == SyncConfig::SYNC4J_LAYOUT ? "config.txt" :
176             type == hidden ? ".internal.ini" :
177             "config.ini";
178     }
179
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;
187     } else {
188         boost::shared_ptr<ConfigNode> node(new IniHashConfigNode(fullpath, filename, m_readonly));
189         return m_nodes[fullname] = node;
190     }
191 }
192
193 boost::shared_ptr<ConfigNode> FileConfigTree::add(const string &path,
194                                                   const boost::shared_ptr<ConfigNode> &node)
195 {
196     NodeCache_t::iterator found = m_nodes.find(path);
197     if (found != m_nodes.end()) {
198         return found->second;
199     } else {
200         m_nodes[path] = node;
201         return node;
202     }
203 }
204
205 static inline bool isNode(const string &dir, const string &name) {
206     struct stat buf;
207     string fullpath = dir + "/" + name;
208     return !stat(fullpath.c_str(), &buf) && S_ISDIR(buf.st_mode);
209 }
210  
211 list<string> FileConfigTree::getChildren(const string &path)
212 {
213     list<string> res;
214
215     string fullpath;
216     fullpath = normalizePath(m_root + "/" + path);
217
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);
224             }
225         }
226     }
227
228     // Now also add those which have been created,
229     // but not saved yet. The full path must be
230     // <path>/<childname>/<filename>.
231     fullpath += "/";
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
238             // the prefix
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
244                 // directory level.
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)) {
249                         res.push_back(name);
250                     }
251                 }
252             }
253         }
254     }
255
256     return res;
257 }
258
259 SE_END_CXX