Imported Upstream version 1.2.99~20120606~SE~ff65aef~SYSYNC~2728cb4
[platform/upstream/syncevolution.git] / src / syncevo / IniConfigNode.cpp
1 /*
2  * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) version 3.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301  USA
19  */
20
21 #include <syncevo/IniConfigNode.h>
22 #include <syncevo/FileDataBlob.h>
23 #include <syncevo/SyncConfig.h>
24 #include <syncevo/util.h>
25
26 #include <boost/scoped_array.hpp>
27 #include <boost/foreach.hpp>
28
29 #include <syncevo/declarations.h>
30 using namespace std;
31 SE_BEGIN_CXX
32
33 IniBaseConfigNode::IniBaseConfigNode(const boost::shared_ptr<DataBlob> &data) :
34     m_data(data)
35 {
36 }
37
38 void IniBaseConfigNode::flush()
39 {
40     if (!m_modified) {
41         return;
42     }
43
44     if (m_data->isReadonly()) {
45         throw std::runtime_error(m_data->getName() + ": internal error: flushing read-only config node not allowed");
46     }
47
48     boost::shared_ptr<std::ostream> file = m_data->write();
49     toFile(*file);
50
51     m_modified = false;
52 }
53
54 IniFileConfigNode::IniFileConfigNode(const boost::shared_ptr<DataBlob> &data) :
55     IniBaseConfigNode(data)
56 {
57     read();
58 }
59
60 IniFileConfigNode::IniFileConfigNode(const std::string &path, const std::string &fileName, bool readonly) :
61     IniBaseConfigNode(boost::shared_ptr<DataBlob>(new FileDataBlob(path, fileName, readonly)))
62 {
63     read();
64 }
65
66
67
68 void IniFileConfigNode::toFile(std::ostream &file) {
69     BOOST_FOREACH(const string &line, m_lines) {
70         file << line << std::endl;
71     }
72 }
73
74 void IniFileConfigNode::read()
75 {
76     boost::shared_ptr<std::istream> file(m_data->read());
77     std::string line;
78     while (getline(*file, line)) {
79         m_lines.push_back(line);
80     }
81     m_modified = false;
82 }
83
84
85 /**
86  * get property and value from line, if any present
87  */
88 static bool getContent(const string &line,
89                        string &property,
90                        string &value,
91                        bool &isComment,
92                        bool fuzzyComments)
93 {
94     size_t start = 0;
95     while (start < line.size() &&
96            isspace(line[start])) {
97         start++;
98     }
99
100     // empty line?
101     if (start == line.size()) {
102         return false;
103     }
104
105     // Comment? Potentially keep reading, might be commented out assignment.
106     isComment = false;
107     if (line[start] == '#') {
108         if (!fuzzyComments) {
109             return false;
110         }
111         isComment = true;
112     }
113
114     // recognize # <word> = <value> as commented out (= default) value
115     if (isComment) {
116         start++;
117         while (start < line.size() &&
118                isspace(line[start])) {
119             start++;
120         }
121     }
122
123     // extract property
124     size_t end = start;
125     while (end < line.size() &&
126            !isspace(line[end])) {
127         end++;
128     }
129     property = line.substr(start, end - start);
130
131     // skip assignment 
132     start = end;
133     while (start < line.size() &&
134            isspace(line[start])) {
135         start++;
136     }
137     if (start == line.size() ||
138         line[start] != '=') {
139         // invalid syntax or we tried to read a comment as assignment
140         return false;
141     }
142
143     // extract value
144     start++;
145     while (start < line.size() &&
146            isspace(line[start])) {
147         start++;
148     }
149
150     value = line.substr(start);
151     // remove trailing white space: usually it is
152     // added accidentally by users
153     size_t numspaces = 0;
154     while (numspaces < value.size() &&
155            isspace(value[value.size() - 1 - numspaces])) {
156         numspaces++;
157     }
158     value.erase(value.size() - numspaces);
159
160     // @TODO: strip quotation marks around value?!
161     
162     return true;    
163 }
164
165 /**
166  * check whether the line contains the property and if so, extract its value
167  */
168 static bool getValue(const string &line,
169                      const string &property,
170                      string &value,
171                      bool &isComment,
172                      bool fuzzyComments)
173
174 {
175     string curProp;
176     return getContent(line, curProp, value, isComment, fuzzyComments) &&
177         !strcasecmp(curProp.c_str(), property.c_str());
178 }
179
180 InitStateString IniFileConfigNode::readProperty(const string &property) const
181 {
182     string value;
183
184     BOOST_FOREACH(const string &line, m_lines) {
185         bool isComment;
186
187         if (getValue(line, property, value, isComment, false)) {
188             return InitStateString(value, true);
189         }
190     }
191     return InitStateString();
192 }
193
194 void IniFileConfigNode::readProperties(ConfigProps &props) const {
195     map<string, string> res;
196     string value, property;
197
198     BOOST_FOREACH(const string &line, m_lines) {
199         bool isComment;
200         if (getContent(line, property, value, isComment, false)) {
201             // don't care about the result: only the first instance
202             // of the property counts, so it doesn't matter when
203             // inserting it again later fails
204             props.insert(ConfigProps::value_type(property, InitStateString(value, true)));
205         }
206     }
207 }
208
209 void IniFileConfigNode::removeProperty(const string &property)
210 {
211     string value;
212
213     list<string>::iterator it = m_lines.begin();
214     while (it != m_lines.end()) {
215         const string &line = *it;
216         bool isComment;
217         if (getValue(line, property, value, isComment, false)) {
218             it = m_lines.erase(it);
219             m_modified = true;
220         } else {
221             it++;
222         }
223     }
224 }
225
226 void IniFileConfigNode::writeProperty(const string &property,
227                                       const InitStateString &newvalue,
228                                       const string &comment) {
229     string newstr;
230     string oldvalue;
231     bool isDefault = false;
232
233     if (!newvalue.wasSet()) {
234         newstr += "# ";
235         isDefault = true;
236     }
237     newstr += property + " = " + newvalue;
238
239     BOOST_FOREACH(string &line, m_lines) {
240         bool isComment;
241
242         if (getValue(line, property, oldvalue, isComment, true)) {
243             if (newvalue != oldvalue ||
244                 (isComment && !isDefault)) {
245                 line = newstr;
246                 m_modified = true;
247             }
248             return;
249         }
250     }
251
252     // add each line of the comment as separate line in .ini file
253     if (comment.size()) {
254         list<string> commentLines;
255         ConfigProperty::splitComment(comment, commentLines);
256         if (m_lines.size()) {
257             m_lines.push_back("");
258         }
259         BOOST_FOREACH(const string &comment, commentLines) {
260             m_lines.push_back(string("# ") + comment);
261         }
262     }
263
264     m_lines.push_back(newstr);
265     m_modified = true;
266 }
267
268 void IniFileConfigNode::clear()
269 {
270     m_lines.clear();
271     m_modified = true;
272 }
273
274 IniHashConfigNode::IniHashConfigNode(const boost::shared_ptr<DataBlob> &data) :
275     IniBaseConfigNode(data)
276 {
277     read();
278 }
279
280 IniHashConfigNode::IniHashConfigNode(const string &path, const string &fileName, bool readonly) :
281     IniBaseConfigNode(boost::shared_ptr<DataBlob>(new FileDataBlob(path, fileName, readonly)))
282 {
283     read();
284 }
285
286 void IniHashConfigNode::read()
287 {
288     boost::shared_ptr<std::istream> file(m_data->read());
289     std::string line;
290     while (std::getline(*file, line)) {
291         string property, value;
292         bool isComment;
293         if (getContent(line, property, value, isComment, false)) {
294             m_props.insert(StringPair(property, value));
295         }
296     }
297     m_modified = false;
298 }
299
300 void IniHashConfigNode::toFile(std::ostream &file)
301 {
302     BOOST_FOREACH(const StringPair &prop, m_props) {
303         file << prop.first << " = " <<  prop.second << std::endl;
304     }
305 }
306
307 void IniHashConfigNode::readProperties(ConfigProps &props) const
308 {
309     BOOST_FOREACH(const StringPair &prop, m_props) {
310         props.insert(ConfigProps::value_type(prop.first, InitStateString(prop.second, true)));
311     }
312 }
313
314 void IniHashConfigNode::writeProperties(const ConfigProps &props)
315 {
316     if (!props.empty()) {
317         m_props.insert(props.begin(), props.end());
318         m_modified = true;
319     }
320 }
321
322
323 InitStateString IniHashConfigNode::readProperty(const string &property) const
324 {
325     std::map<std::string, std::string>::const_iterator it = m_props.find(property);
326     if (it != m_props.end()) {
327         return InitStateString(it->second, true);
328     } else {
329         return InitStateString();
330     }
331 }
332
333 void IniHashConfigNode::removeProperty(const string &property) {
334     map<string, string>::iterator it = m_props.find(property);
335     if(it != m_props.end()) {
336         m_props.erase(it);
337         m_modified = true;
338     }
339 }
340
341 void IniHashConfigNode::clear()
342 {
343     if (!m_props.empty()) {
344         m_props.clear();
345         m_modified = true;
346     }
347 }
348
349 void IniHashConfigNode::writeProperty(const string &property,
350                                       const InitStateString &newvalue,
351                                       const string &comment)
352 {
353     // we only store explicitly set properties
354     if (!newvalue.wasSet()) {
355         removeProperty(property);
356         return;
357     }
358     map<string, string>::iterator it = m_props.find(property);
359     if(it != m_props.end()) {
360         if (it->second != newvalue) {
361             it->second = newvalue;
362             m_modified = true;
363         }
364     } else {
365         m_props.insert(StringPair(property, newvalue));
366         m_modified = true;
367     }
368 }
369
370
371 SE_END_CXX