Parse back the log entries
[gregoa/zavai.git] / src / log.vala
1 /*
2  * log - logging functions
3  *
4  * Copyright (C) 2009--2010  Enrico Zini <enrico@enricozini.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 using GLib;
22
23 namespace zavai {
24 namespace log {
25
26 public class Waypoint : Object
27 {
28     public time_t ts;
29     public double lat;
30     public double lon;
31
32     public Waypoint()
33     {
34         if (gps.gps.fix_status() != libgps.STATUS_NO_FIX)
35         {
36             lat = gps.gps.info().fix.latitude;
37             lon = gps.gps.info().fix.longitude;
38             ts = (time_t)gps.gps.info().fix.time;
39         } else {
40             // Use 1000 as missing values
41             lat = 1000;
42             lon = 1000;
43             ts = time_t();
44         }
45     }
46
47     public void writeInside(FileStream outfd)
48     {
49         var t = Time.gm(ts);
50         outfd.printf("   <time>%s</time>\n", t.format("%Y-%m-%dT%H:%M:%S%z"));
51     }
52 }
53
54
55 public class LogEntry : Waypoint
56 {
57     public string msg;
58
59     public LogEntry()
60     {
61         base();
62     }
63
64     public void write(FileStream outfd)
65     {
66         outfd.printf("   <wpt lat=\"%f\" lon=\"%f\">\n", lat, lon);
67         writeInside(outfd);
68         outfd.printf("   <name>%s</name>\n", Markup.escape_text(msg));
69         outfd.puts("   </wpt>\n");
70     }
71 }
72
73 public class TrackEntry : Waypoint
74 {
75     public TrackEntry()
76     {
77         base();
78     }
79
80     public void write(FileStream outfd)
81     {
82         outfd.printf("   <trkpt lat=\"%f\" lon=\"%f\">\n", lat, lon);
83         writeInside(outfd);
84         outfd.puts("   </trkpt>\n");
85     }
86 }
87
88
89 public class Log : Object
90 {
91     public string tag;
92     public string title;
93     public List<LogEntry> entries;
94     public List<TrackEntry> track;
95
96     public Log(string tag, string title)
97     {
98         this.tag = tag;
99         this.title = title;
100         entries = null;
101         track = null;
102     }
103
104     public void add(string msg)
105     {
106         var entry = new LogEntry();
107         entry.msg = msg;
108         entries.append(entry);
109     }
110
111     public void add_trackpoint()
112     {
113         track.append(new TrackEntry());
114     }
115
116     public void save()
117     {
118         if (entries == null) return;
119
120         // Directory where we save the log
121         string dir = config.homedir + "/log-" + tag;
122         DirUtils.create(dir, 0777);
123
124         // First try with a plain name
125         var t = Time.local(entries.data.ts);
126         string basename = dir + "/" + t.format("%Y%m%d-%H%M%S");
127
128         string pathname = basename + ".gpx";
129
130         // Find a pathname that does not exist already
131         for (int i = 1; FileUtils.test(pathname, FileTest.EXISTS); ++i)
132             pathname = "%s-%d.gpx".printf(basename, i);
133
134         // Write out
135         var outfd = FileStream.open(pathname, "w");
136         if (outfd == null)
137         {
138             zavai.log.error("opening " + pathname + ": " + strerror(errno));
139             return;
140         }
141
142         write(outfd);
143         outfd.flush();
144     }
145
146     public void dump()
147     {
148         write(stderr);
149     }
150
151     protected void writeTrack(FileStream outfd)
152     {
153         outfd.puts("   <trk>\n");
154         outfd.puts("     <trkseg>\n");
155         for (weak List<TrackEntry> i = track; i != null; i = i.next)
156             i.data.write(outfd);
157         outfd.puts("     </trkseg>\n");
158         outfd.puts("   </trk>\n");
159     }
160
161     protected void writeEntries(FileStream outfd)
162     {
163         for (weak List<LogEntry> i = entries; i != null; i = i.next)
164             i.data.write(outfd);
165     }
166
167     protected void write(FileStream outfd)
168     {
169         outfd.puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
170         outfd.puts("<gpx version=\"1.0\"\n");
171         outfd.printf("     creator=\"zavai %s\"\n", zavai.config.version);
172         outfd.puts("     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
173         outfd.puts("     xmlns=\"http://www.topografix.com/GPX/1/0\"\n");
174         outfd.puts("     xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
175         outfd.puts("  <metadata>\n");
176         outfd.printf("    <name>%s</name>\n", Markup.escape_text(title));
177         outfd.puts("  </metadata>\n");
178         if (track != null) writeTrack(outfd);
179         if (entries != null) writeEntries(outfd);
180         outfd.puts(" </gpx>\n");
181     }
182 }
183
184 enum LogParserState {
185     NONE,
186     METADATA,
187     TRACK,
188     WPT,
189 }
190
191 class LogParser: Object
192 {
193     const MarkupParser parser = { // It's a structure, not an object
194         start,// when an element opens
195         end,  // when an element closes
196         text, // when text is found
197         null, // when comments are found
198         null  // when errors occur
199     };
200
201     MarkupParseContext context = null;
202     public Log result = null;
203     LogParserState state = LogParserState.NONE;
204     string cur_text = "";
205     LogEntry cur_logentry = null;
206     TrackEntry cur_trackentry = null;
207
208     construct
209     {
210         context = new MarkupParseContext(
211             parser, // the structure with the callbacks
212             0,      // MarkupParseFlags
213             this,   // extra argument for the callbacks, methods in this case
214             destroy // when the parsing ends
215         );
216     }
217
218     void destroy()
219     {
220         cur_text = "";
221         cur_logentry = null;
222         cur_trackentry = null;
223     }
224
225     public bool parse(string content, ssize_t len = -1) throws MarkupError
226     {
227         string oldtz = Environment.get_variable("TZ");
228         Environment.set_variable("TZ", "UTC", true);
229         bool res = context.parse(content, len);
230         if (oldtz == null)
231             Environment.unset_variable("TZ");
232         else
233             Environment.set_variable("TZ", oldtz, true);
234         return res;
235     }
236
237     void parse_attrs(Waypoint w, string[] attr_names, string[] attr_values)
238     {
239         w.lat = 1000;
240         w.lon = 1000;
241         for (int i = 0; attr_names[i] != null; ++i)
242         {
243             if (attr_names[i] == "lat")
244                 w.lat = attr_values[i].to_double();
245             else if (attr_names[i] == "lon")
246                 w.lon = attr_values[i].to_double();
247         }
248     }
249
250     void start (MarkupParseContext context, string name,
251                 string[] attr_names, string[] attr_values) throws MarkupError
252     {
253         if (name == "gpx")
254         {
255             state = LogParserState.NONE;
256             result = new Log("TODO:TAG", "TODO:TITLE");
257         } else if (name == "metadata") {
258             state = LogParserState.METADATA;
259         } else if (name == "wpt") {
260             cur_logentry = new LogEntry();
261             parse_attrs(cur_logentry, attr_names, attr_values);
262             result.entries.append(cur_logentry);
263             state = LogParserState.WPT;
264         } else if (name == "trkpt") {
265             cur_trackentry = new TrackEntry();
266             parse_attrs(cur_trackentry, attr_names, attr_values);
267             result.track.append(cur_trackentry);
268             state = LogParserState.TRACK;
269         }
270         cur_text = "";
271     }
272
273     void end (MarkupParseContext context, string name) throws MarkupError
274     {
275         if (name == "name")
276         {
277             switch (state)
278             {
279                 case LogParserState.METADATA:
280                     result.title = cur_text;
281                     break;
282                 case LogParserState.WPT:
283                     cur_logentry.msg = cur_text;
284                     break;
285             }
286         }
287         else if (name == "time")
288         {
289             Time t = Time();
290             t.strptime(cur_text, "%Y-%m-%dT%H:%M:%S%z");
291             if (state == LogParserState.WPT)
292                 cur_logentry.ts = t.mktime();
293             else if (state == LogParserState.TRACK)
294                 cur_trackentry.ts = t.mktime();
295         }
296     }
297
298     void text (MarkupParseContext context,
299                string text, size_t text_len) throws MarkupError
300     {
301         cur_text += text;
302     }
303 }
304
305 public class Logger : Resource, Object
306 {
307     protected List<Log> logs;
308
309     public Logger()
310     {
311         logs = null;
312         zavai.registry.register(this);
313     }
314
315     protected void start_trace()
316     {
317         gps.gps.pos_changed += on_pos_changed;
318     }
319
320     protected void end_trace()
321     {
322         gps.gps.pos_changed -= on_pos_changed;
323     }
324
325     protected void on_pos_changed()
326     {
327         for (weak List<Log> i = logs; i != null; i = i.next)
328             i.data.add_trackpoint();
329     }
330
331     protected void pop(Log log)
332     {
333         for (weak List<Log> i = logs; i != null; i = i.next)
334             if (i.data == log)
335                 logs.delete_link(i);
336     }
337
338     public Log start(string tag, string title)
339     {
340         bool was_empty = (logs == null);
341         Log res = new Log(tag, title);
342         logs.append(res);
343         if (was_empty) start_trace();
344         return res;
345     }
346
347     public void end(Log log)
348     {
349         pop(log);
350         log.save();
351         if (logs == null) end_trace();
352     }
353
354     public Log load(string fname)
355     {
356         string contents;
357         size_t length;
358         FileUtils.get_contents(fname, out contents, out length);
359         LogParser parser = new LogParser();
360         parser.parse(contents, (ssize_t)length);
361         return parser.result;
362     }
363
364     public void instant(string tag, string msg)
365     {
366         var log = new Log(tag, msg);
367         log.add(msg);
368         log.save();
369     }
370
371     public void shutdown()
372     {
373         bool had_logs = (logs != null);
374         while (logs != null)
375         {
376             logs.data.save();
377             logs.delete_link(logs);
378         }
379         if (had_logs) end_trace();
380     }
381 }
382
383 public void error(string s)
384 {
385         stderr.printf("%s\n", s);
386 }
387 public void warning(string s)
388 {
389         stderr.printf("%s\n", s);
390 }
391 public void info(string s)
392 {
393         stderr.printf("%s\n", s);
394 }
395 public void debug(string s)
396 {
397         stderr.printf("%s\n", s);
398 }
399
400 Logger log = null;
401
402 public void init()
403 {
404     log = new Logger();
405 }
406
407 }
408 }