Imported Upstream version 0.8~alpha1
[platform/upstream/syncevolution.git] / src / FileConfigNode.cpp
1 /*
2  * Copyright (C) 2008 Patrick Ohly
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY, TITLE, NONINFRINGEMENT or FITNESS FOR A PARTICULAR
11  * PURPOSE.  See the GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
16  * 02111-1307  USA
17  */
18
19 #include "FileConfigNode.h"
20 #include "EvolutionSyncClient.h"
21 #include "SyncEvolutionUtil.h"
22
23 #include <boost/scoped_array.hpp>
24
25 #include <unistd.h>
26 #include <errno.h>
27 #include <fcntl.h>
28
29 /** @TODO: replace stdio.h with streams */
30
31 FileConfigNode::FileConfigNode(const string &path, const string &fileName) :
32     m_path(path),
33     m_fileName(fileName),
34     m_modified(false),
35     m_exists(false)
36 {
37     read();
38 }
39
40 void FileConfigNode::read()
41 {
42     string filename = m_path + "/" + m_fileName;
43
44     FILE *file = fopen(filename.c_str(), "r");
45     char buffer[512];
46
47     m_lines.clear();
48     if (file) {
49         while (fgets(buffer, sizeof(buffer), file)) {
50             char *eol = strchr(buffer, '\n');
51             if (eol) {
52                 *eol = 0;
53             }
54             m_lines.push_back(buffer);
55         }
56         m_exists = true;
57         fclose(file);
58     }
59     m_modified = false;
60 }
61
62 void FileConfigNode::flush()
63 {
64     if (!m_modified) {
65         return;
66     }
67
68     mkdir_p(m_path);
69
70     string filename = m_path + "/" + m_fileName;
71     string tmpFilename = m_path + "/.#" + m_fileName;
72
73     FILE *file = fopen(tmpFilename.c_str(), "w");
74     if (file) {
75         for (list<string>::const_iterator it = m_lines.begin();
76              it != m_lines.end();
77              it++) {
78             fprintf(file, "%s\n", it->c_str());
79         }
80         fflush(file);
81         bool failed = ferror(file);
82         if (fclose(file)) {
83             failed = true;
84         }
85         if (failed ||
86             rename(tmpFilename.c_str(), filename.c_str())) {
87             EvolutionSyncClient::throwError(tmpFilename + ": " + strerror(errno));
88         }
89     } else {
90         EvolutionSyncClient::throwError(tmpFilename + ": " + strerror(errno));
91     }
92
93     m_modified = false;
94     m_exists = true;
95 }
96
97 /**
98  * get property and value from line, if any present
99  */
100 static bool getContent(const string &line,
101                        string &property,
102                        string &value,
103                        bool &isComment,
104                        bool fuzzyComments)
105 {
106     size_t start = 0;
107     while (start < line.size() &&
108            isspace(line[start])) {
109         start++;
110     }
111
112     // empty line?
113     if (start == line.size()) {
114         return false;
115     }
116
117     // Comment? Potentially keep reading, might be commented out assignment.
118     isComment = false;
119     if (line[start] == '#') {
120         if (!fuzzyComments) {
121             return false;
122         }
123         isComment = true;
124     }
125
126     // recognize # <word> = <value> as commented out (= default) value
127     if (isComment) {
128         start++;
129         while (start < line.size() &&
130                isspace(line[start])) {
131             start++;
132         }
133     }
134
135     // extract property
136     size_t end = start;
137     while (end < line.size() &&
138            !isspace(line[end])) {
139         end++;
140     }
141     property = line.substr(start, end - start);
142
143     // skip assignment 
144     start = end;
145     while (start < line.size() &&
146            isspace(line[start])) {
147         start++;
148     }
149     if (start == line.size() ||
150         line[start] != '=') {
151         // invalid syntax or we tried to read a comment as assignment
152         return false;
153     }
154
155     // extract value
156     start++;
157     while (start < line.size() &&
158            isspace(line[start])) {
159         start++;
160     }
161
162     value = line.substr(start);
163     // remove trailing white space: usually it is
164     // added accidentally by users
165     size_t numspaces = 0;
166     while (numspaces < value.size() &&
167            isspace(value[value.size() - 1 - numspaces])) {
168         numspaces++;
169     }
170     value.erase(value.size() - numspaces);
171
172     // @TODO: strip quotation marks around value?!
173     
174     return true;    
175 }
176
177 /**
178  * check whether the line contains the property and if so, extract its value
179  */
180 static bool getValue(const string &line,
181                      const string &property,
182                      string &value,
183                      bool &isComment,
184                      bool fuzzyComments)
185
186 {
187     string curProp;
188     return getContent(line, curProp, value, isComment, fuzzyComments) &&
189         !strcasecmp(curProp.c_str(), property.c_str());
190 }
191
192 string FileConfigNode::readProperty(const string &property) const {
193     string value;
194
195     for (list<string>::const_iterator it = m_lines.begin();
196          it != m_lines.end();
197          it++) {
198         const string &line = *it;
199         bool isComment;
200
201         if (getValue(line, property, value, isComment, false)) {
202             return value;
203         }
204     }
205     return "";
206 }
207
208
209
210 void FileConfigNode::readProperties(map<string, string> &props) const {
211     map<string, string> res;
212     string value, property;
213
214     for (list<string>::const_iterator it = m_lines.begin();
215          it != m_lines.end();
216          it++) {
217         const string &line = *it;
218         bool isComment;
219         if (getContent(line, property, value, isComment, false)) {
220             // don't care about the result: only the first instance
221             // of the property counts, so it doesn't matter when
222             // inserting it again later fails
223             props.insert(pair<string, string>(property, value));
224         }
225     }
226 }
227
228 void FileConfigNode::removeProperty(const string &property)
229 {
230     string value;
231
232     list<string>::iterator it = m_lines.begin();
233     while (it != m_lines.end()) {
234         const string &line = *it;
235         bool isComment;
236         if (getValue(line, property, value, isComment, false)) {
237             it = m_lines.erase(it);
238             m_modified = true;
239         } else {
240             it++;
241         }
242     }
243 }
244
245 void FileConfigNode::setProperty(const string &property,
246                                  const string &newvalue,
247                                  const string &comment,
248                                  const string *defValue) {
249     string newstr;
250     string oldvalue;
251     bool isDefault = false;
252
253     if (defValue &&
254         *defValue == newvalue) {
255         newstr += "# ";
256         isDefault = true;
257     }
258     newstr += property + " = " + newvalue;
259
260     for (list<string>::iterator it = m_lines.begin();
261          it != m_lines.end();
262          it++) {
263         const string &line = *it;
264         bool isComment;
265
266         if (getValue(line, property, oldvalue, isComment, true)) {
267             if (newvalue != oldvalue ||
268                 isComment && !isDefault) {
269                 *it = newstr;
270                 m_modified = true;
271             }
272             return;
273         }
274     }
275
276     // add each line of the comment as separate line in .ini file
277     if (comment.size()) {
278         list<string> commentLines;
279         ConfigProperty::splitComment(comment, commentLines);
280         if (m_lines.size()) {
281             m_lines.push_back("");
282         }
283         for (list<string>::const_iterator it = commentLines.begin();
284              it != commentLines.end();
285              ++it) {
286             m_lines.push_back(string("# " + *it));
287         }
288     }
289
290     m_lines.push_back(newstr);
291     m_modified = true;
292 }
293