root/todo/tags/0.1.20/src/Loaders.cc

Revision 443, 8.8 kB (checked in by athomas, 1 year ago)

Dev Todo:

  • A few bugfixes.
  • Added XML declaration parsing/saving. This is a stop-gap at best, but will
    hopefully be useful to some.
  • Removed reliance on builtin regex library. Hopefully the completely broken
    version of glibc that instigated the inclusion of it is now out of
    circulation.
  • Fixed a whole bunch of compiler warnings on more recent GCC versions.
Line 
1 #include <sys/stat.h>
2 #include <unistd.h>
3 #include "Loaders.h"
4 #include "Strings.h"
5 #include "support.h"
6 #include "config.h"
7
8 using namespace str;
9
10 Loader getLoaders() {
11 Loader out;
12
13         out["xml"] = xmlLoad;
14         out["binary"] = binaryLoad;
15         return out;
16 }
17
18 Saver getSavers() {
19 Saver out;
20
21         out["xml"] = xmlSave;
22         out["binary"] = binarySave;
23         return out;
24 }
25
26 void checkVersion(string const &version) {
27 vector<string> vtdver = str::split(".", version);
28 vector<string> vmyver = str::split(".", VERSION);
29 int tdver[3], myver[3];
30
31         if (options.verbose > 1)
32                 cout << "todo: loading database version '" << version << "' (binary version is '" << VERSION << "')" << endl;
33
34         if (vtdver.size() != 3) throw load_error("invalid database version number '" + version + "'");
35
36         tdver[0] = destringify<int>(vtdver[0]);
37         tdver[1] = destringify<int>(vtdver[1]);
38         tdver[2] = destringify<int>(vtdver[2]);
39
40         myver[0] = destringify<int>(vmyver[0]);
41         myver[1] = destringify<int>(vmyver[1]);
42         myver[2] = destringify<int>(vmyver[2]);
43
44         if (tdver[0] != myver[0] || tdver[1] != myver[1]) {
45                 cerr << "todo: error, version of database (" << version << ") is different than binary (" << VERSION << ")" << endl;
46                 exit(1);
47         }
48         // minor version differences shouldn't be fatal
49         if (tdver[2] > myver[2]) cerr << "warning: version of database (" << version << ") is more recent than binary (" << VERSION << ")" << endl;
50 }
51
52 void xmlParse(TodoDB &outdb, vector<XML*>::const_iterator begin,
53         vector<XML*>::const_iterator end, multiset<Todo> &out) {
54         for (vector<XML*>::const_iterator i = begin; i != end; i++) {
55         XML &x = *(*i);
56
57                 switch (x.type()) {
58                         case XML::Element : {
59                                 if (x.name() == "title") {
60                                         outdb.titleText = trim((*x.child().begin())->body());
61                                         break;
62                                 } else
63                                 if (x.name() == "note") {
64                                 Todo todo;
65                                 // const_cast so I can use attrib[...]
66                                 map<string, string> &attrib = *const_cast<map<string, string>* >(&x.attrib());
67
68                                         if (attrib.find("priority") == attrib.end() || attrib.find("time") == attrib.end())
69                                                 throw load_error("require both 'priority' and 'time' attributes for 'note' element");
70
71                                         if (attrib.find("done") != attrib.end()) {
72                                                         todo.done = true;
73                                                         todo.doneTime = destringify<time_t>(attrib["done"]);
74                                                 } else
75                                                         todo.done = false;
76
77                                         todo.priority = desymbolisePriority(attrib["priority"]);
78                                         todo.text = trim((*x.child().begin())->body());
79                                         todo.added = destringify<time_t>(attrib["time"]);
80                                         todo.db = &outdb;
81
82                                         for (vector<XML*>::const_iterator j = x.child().begin(); j != x.child().end(); ++j)
83                                                 if ((*j)->name() == "comment") {
84                                                         todo.comment = trim((*(*j)->child().begin())->body());
85                                                         break;
86                                                 }
87
88                                         xmlParse(outdb, x.child().begin(), x.child().end(), *todo.child);
89                                         out.insert(todo);
90                                 } else if (x.name() == "link") {
91                                 TodoDB *newDb = new TodoDB();
92                                 Todo todo;
93                                 map<string, string> &attrib = *const_cast<map<string, string>* >(&x.attrib());
94
95                                         if (attrib.find("priority") == attrib.end() || attrib.find("filename") == attrib.end() || attrib.find("time") == attrib.end())
96                                                 throw load_error("require 'priority', 'time', and 'filename' attributes for 'link' element");
97
98                                         try {
99                                                 if (outdb.basepath == "" || attrib["filename"][0] == '/')
100                                                         newDb->load(attrib["filename"]);
101                                                 else
102                                                         newDb->load(outdb.basepath + "/" + attrib["filename"]);
103                                                 if (newDb->titleText == "") {
104                                                         if (newDb->basepath == "")
105                                                                 todo.text = attrib["filename"];
106                                                         else
107                                                                 todo.text = newDb->basepath.substr(newDb->basepath.rfind("/") + 1);
108                                                 }
109                                                 else
110                                                         todo.text = newDb->titleText;
111
112                                                 delete todo.child;
113
114                                                
115                                                 todo.type = Todo::Link;
116                                                 todo.child = &newDb->todo;
117                                                 todo.priority = desymbolisePriority(attrib["priority"]);
118                                                 todo.todofile = attrib["filename"];
119                                                 todo.db = newDb;
120
121                                                 out.insert(todo);
122                                         } catch (...) {
123                                                 delete newDb;
124                                         }
125                                 } else
126                                 if (x.name() != "comment")
127                                         throw load_error("expected 'note' element, got '" + x.name() + "'");
128                         }
129                         break;
130                         default :
131                         break;
132                 }
133         }
134
135         // number the items
136 int n = 1;
137         for (multiset<Todo>::iterator i = out.begin(); i != out.end(); ++i, ++n) {
138         Todo &t = const_cast<Todo&>(*i);
139                
140                 t.index = n;
141         }
142 }
143
144 bool xmlLoad(TodoDB &todo, string const &file) {
145 ifstream in(file.c_str());
146
147         if (in.bad()||in.fail()||in.eof()) return false;
148 struct stat s;
149         stat(file.c_str(), &s);
150
151 char *str;
152         str = new char[s.st_size + 1];
153
154         in.read(str, s.st_size);
155         str[s.st_size] = 0;
156
157 XML x(str);
158
159         if (x.name() != "todo")
160                 throw load_error("primary element not 'todo'");
161 map<string, string> const &attrib = x.attrib();
162
163         // do version checking
164         if (attrib.find("version") != attrib.end())
165                 checkVersion(const_cast<map<string, string>&>(attrib)["version"]);
166
167         xmlParse(todo, x.child().begin(), x.child().end(), todo.todo);
168         return true;
169 }
170
171 void xmlSave(TodoDB const &db, multiset<Todo> const &todo, ostream &of, int ind) {
172         for (multiset<Todo>::const_iterator i = todo.begin(); i != todo.end(); i++) {
173                 if (i->type == Todo::Link) {
174                         of  << string(ind * 4, ' ') << "<link"
175                             << " filename=\"" << i->todofile << "\""
176                                 << " priority=\"" << symbolisePriority(i->priority) << "\""
177                                 << " time=\"" << i->added << "\""
178                                 << "/>" << endl;
179
180                         if (i->db)
181                                 i->db->save(i->todofile);
182
183                         /* Restore the TODODB environment variable. */
184 string envar = "TODODB=" + db.filename;
185
186                         putenv(strdup(const_cast<char*>(envar.c_str())));
187
188                 } else {
189                         of      << string(ind * 4, ' ') << "<note"
190                                 << " priority=\"" << symbolisePriority((*i).priority) << "\""
191                                 << " time=\"" << (*i).added << "\"";
192                         if ((*i).done) {
193                                 of      << " done=\"" << (*i).doneTime<< "\"";
194                         }
195                         of << ">" << endl;
196                         of << string((ind + 1) * 4, ' ');
197                         of << htmlify((*i).text) << endl;
198                         if ((*i).comment != "") {
199                                 of << string((ind + 1) * 4, ' ');
200                                 of << "<comment>" << endl;
201                                 of << string((ind + 2) * 4, ' ');
202                                 of << htmlify((*i).comment) << endl;
203                                 of << string((ind + 1) * 4, ' ');
204                                 of << "</comment>" << endl;
205                         }
206                         xmlSave(db, *i->child, of, ind + 1);
207                         of << string(ind * 4, ' ');
208                         of << "</note>" << endl;
209                 }
210         }
211 }
212
213 bool xmlSave(TodoDB const &todo, string const &file) {
214         if (todo.isDirty())
215         {
216         ofstream of(file.c_str());
217
218                 if (of.bad()) return false;
219                 if (options.verbose > 1)
220                         cout << "todo: saving to database '" << file << "'" << endl;
221                 of << "<?xml version=\"1.0\"?>" << endl;
222                 of << "<todo version=\"" << VERSION << "\">" << endl;
223                 if (todo.titleText != "")
224                         of      << "    <title>" << endl
225                                 << "        " << todo.titleText << endl
226                                 << "    </title>" << endl;
227
228                 /* Set the TODODB environment variable. */
229         string envar = "TODODB=" + todo.filename;
230
231                 putenv(strdup(const_cast<char*>(envar.c_str())));
232
233                 xmlSave(todo, todo.todo, of, 1);
234                 of << "</todo>" << endl;
235                 of.close();
236                 return true;
237         }
238         else
239         {
240         ofstream out("/dev/null");
241
242                 xmlSave(todo, todo.todo, out, 1);
243                 return false;
244         }
245 }
246
247 void binaryLoad(ifstream &in, int &n) {
248         in.read((char *)&n, sizeof(n));
249 }
250
251 void binaryLoad(ifstream &in, string &str) {
252 int size;
253
254         binaryLoad(in, size);
255 char buffer[size + 1];
256         in.read(buffer, size);
257         buffer[size] = 0;
258         str = buffer;
259 }
260
261 void binaryLoad(TodoDB &db, ifstream &in, multiset<Todo> &out) {
262 int n;
263
264         binaryLoad(in, n);
265         for (int i = 0; i < n; i++) {
266         Todo t;
267         int tmp;
268
269                 binaryLoad(in, tmp); t.priority = (Todo::Priority)tmp;
270                 binaryLoad(in, tmp); t.added = tmp;
271                 binaryLoad(in, tmp); t.doneTime = tmp;
272                 if (t.doneTime) t.done = true;
273                 binaryLoad(in, t.text);
274                 binaryLoad(db, in, *t.child);
275
276                 t.db = &db;
277                 out.insert(t);
278         }
279
280         // number the items
281         n = 1;
282         for (multiset<Todo>::iterator i = out.begin(); i != out.end(); ++i, ++n) {
283         Todo &t = const_cast<Todo&>(*i);
284                
285                 t.index = n;
286         }
287 }
288
289 bool binaryLoad(TodoDB &out, string const &file) {
290 ifstream in(file.c_str());
291
292         if (in.bad()||in.fail()||in.eof()) return false;
293 char buffer[5];
294
295         in.read(buffer, 4);
296         buffer[4] = 0;
297         if (string(buffer) != "TODO") return false;
298 string version;
299         binaryLoad(in, version);
300         checkVersion(version);
301         binaryLoad(in, out.titleText);
302         binaryLoad(out, in, out.todo);
303         return true;
304 }
305
306 void binarySave(ofstream &of, int n) {
307         of.write((char*)&n, sizeof(n));
308 }
309
310 void binarySave(ofstream &of, string const &str) {
311         binarySave(of, str.size());
312         of.write(str.c_str(), str.size());
313 }
314
315 void binarySave(ofstream &of, multiset<Todo> const &db) {
316         binarySave(of, db.size());
317         for (multiset<Todo>::const_iterator i = db.begin(); i != db.end(); ++i) {
318                 binarySave(of, (*i).priority);
319                 binarySave(of, (*i).added);
320                 if ((*i).done)
321                         binarySave(of, (*i).doneTime);
322                 else
323                         binarySave(of, 0);
324                 binarySave(of, (*i).text);
325                 binarySave(of, *i->child);
326         }
327 }
328
329 bool binarySave(TodoDB const &in, string const &file) {
330 ofstream of(file.c_str());
331
332         if (of.bad()) return false;
333
334 string envar = "TODODB=" + in.filename;
335
336         putenv(strdup(const_cast<char*>(envar.c_str())));
337
338         of.write("TODO", 4);
339         binarySave(of, VERSION);
340         binarySave(of, in.titleText);
341         binarySave(of, in.todo);
342         return true;
343 }
Note: See TracBrowser for help on using the browser.