Added config options for call and sms ringtones
[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 bool acked;
94     public List<LogEntry> entries;
95     public List<TrackEntry> track;
96
97     public Log(string tag, string title, bool acked=false)
98     {
99         this.tag = tag;
100         this.title = title;
101         this.acked = acked;
102         entries = null;
103         track = null;
104     }
105
106     public void add(string msg)
107     {
108         var entry = new LogEntry();
109         entry.msg = msg;
110         entries.append(entry);
111     }
112
113     public void add_trackpoint()
114     {
115         track.append(new TrackEntry());
116     }
117
118     public void save()
119     {
120         if (entries == null) return;
121
122         // Directory where we save the log
123         string dir;
124         if (acked)
125             dir = config.homedir + "/archive";
126         else
127             dir = config.homedir + "/log";
128         DirUtils.create(dir, 0777);
129
130         // First try with a plain name
131         var t = Time.local(entries.data.ts);
132         string basename = dir + "/" + t.format("%Y%m%d-%H%M%S") + "-" + tag;
133
134         string pathname = basename + ".gpx";
135
136         // Find a pathname that does not exist already
137         for (int i = 1; FileUtils.test(pathname, FileTest.EXISTS); ++i)
138             pathname = "%s-%d.gpx".printf(basename, i);
139
140         // Write out
141         var outfd = FileStream.open(pathname, "w");
142         if (outfd == null)
143         {
144             zavai.log.error("opening " + pathname + ": " + strerror(errno));
145             return;
146         }
147
148         write(outfd);
149         outfd.flush();
150     }
151
152     public void dump()
153     {
154         write(stderr);
155     }
156
157     protected void writeTrack(FileStream outfd)
158     {
159         outfd.puts("   <trk>\n");
160         outfd.puts("     <trkseg>\n");
161         for (weak List<TrackEntry> i = track; i != null; i = i.next)
162             i.data.write(outfd);
163         outfd.puts("     </trkseg>\n");
164         outfd.puts("   </trk>\n");
165     }
166
167     protected void writeEntries(FileStream outfd)
168     {
169         for (weak List<LogEntry> i = entries; i != null; i = i.next)
170             i.data.write(outfd);
171     }
172
173     protected void write(FileStream outfd)
174     {
175         outfd.puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
176         outfd.puts("<gpx version=\"1.0\"\n");
177         outfd.printf("     creator=\"zavai %s\"\n", zavai.config.version);
178         outfd.puts("     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
179         outfd.puts("     xmlns=\"http://www.topografix.com/GPX/1/0\"\n");
180         outfd.puts("     xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
181         outfd.puts("  <metadata>\n");
182         outfd.printf("    <name>%s</name>\n", Markup.escape_text(title));
183         outfd.printf("    <keywords>%s</keywords>\n", Markup.escape_text(tag));
184         outfd.puts("  </metadata>\n");
185         if (track != null) writeTrack(outfd);
186         if (entries != null) writeEntries(outfd);
187         outfd.puts(" </gpx>\n");
188     }
189 }
190
191 enum LogParserState {
192     NONE,
193     METADATA,
194     TRACK,
195     WPT,
196 }
197
198 class LogParser: Object
199 {
200     const MarkupParser parser = { // It's a structure, not an object
201         start,// when an element opens
202         end,  // when an element closes
203         text, // when text is found
204         null, // when comments are found
205         null  // when errors occur
206     };
207
208     MarkupParseContext context = null;
209     public Log result = null;
210     LogParserState state = LogParserState.NONE;
211     string cur_text = "";
212     LogEntry cur_logentry = null;
213     TrackEntry cur_trackentry = null;
214
215     construct
216     {
217         context = new MarkupParseContext(
218             parser, // the structure with the callbacks
219             0,      // MarkupParseFlags
220             this,   // extra argument for the callbacks, methods in this case
221             destroy // when the parsing ends
222         );
223     }
224
225     void destroy()
226     {
227         cur_text = "";
228         cur_logentry = null;
229         cur_trackentry = null;
230     }
231
232     public bool parse(string content, ssize_t len = -1) throws MarkupError
233     {
234         string oldtz = Environment.get_variable("TZ");
235         Environment.set_variable("TZ", "UTC", true);
236         bool res = context.parse(content, len);
237         if (oldtz == null)
238             Environment.unset_variable("TZ");
239         else
240             Environment.set_variable("TZ", oldtz, true);
241         return res;
242     }
243
244     void parse_attrs(Waypoint w, string[] attr_names, string[] attr_values)
245     {
246         w.lat = 1000;
247         w.lon = 1000;
248         for (int i = 0; attr_names[i] != null; ++i)
249         {
250             if (attr_names[i] == "lat")
251                 w.lat = attr_values[i].to_double();
252             else if (attr_names[i] == "lon")
253                 w.lon = attr_values[i].to_double();
254         }
255     }
256
257     void start (MarkupParseContext context, string name,
258                 string[] attr_names, string[] attr_values) throws MarkupError
259     {
260         if (name == "gpx")
261         {
262             state = LogParserState.NONE;
263             result = new Log("TODO:TAG", "TODO:TITLE");
264         } else if (name == "metadata") {
265             state = LogParserState.METADATA;
266         } else if (name == "wpt") {
267             cur_logentry = new LogEntry();
268             parse_attrs(cur_logentry, attr_names, attr_values);
269             result.entries.append(cur_logentry);
270             state = LogParserState.WPT;
271         } else if (name == "trkpt") {
272             cur_trackentry = new TrackEntry();
273             parse_attrs(cur_trackentry, attr_names, attr_values);
274             result.track.append(cur_trackentry);
275             state = LogParserState.TRACK;
276         }
277         cur_text = "";
278     }
279
280     void end (MarkupParseContext context, string name) throws MarkupError
281     {
282         if (name == "name")
283         {
284             switch (state)
285             {
286                 case LogParserState.METADATA:
287                     result.title = cur_text;
288                     break;
289                 case LogParserState.WPT:
290                     cur_logentry.msg = cur_text;
291                     break;
292             }
293         }
294         else if (name == "keywords")
295         {
296             result.tag = cur_text;
297         }
298         else if (name == "time")
299         {
300             Time t = Time();
301             t.strptime(cur_text, "%Y-%m-%dT%H:%M:%S%z");
302             if (state == LogParserState.WPT)
303                 cur_logentry.ts = t.mktime();
304             else if (state == LogParserState.TRACK)
305                 cur_trackentry.ts = t.mktime();
306         }
307     }
308
309     void text (MarkupParseContext context,
310                string text, size_t text_len) throws MarkupError
311     {
312         cur_text += text;
313     }
314 }
315
316 public class Logger : Resource, Object
317 {
318     protected List<Log> logs;
319
320     public signal void entries_changed();
321
322     public Logger()
323     {
324         logs = null;
325         zavai.registry.register(this);
326     }
327
328     protected void start_trace()
329     {
330         gps.gps.pos_changed += on_pos_changed;
331     }
332
333     protected void end_trace()
334     {
335         gps.gps.pos_changed -= on_pos_changed;
336     }
337
338     protected void on_pos_changed()
339     {
340         for (weak List<Log> i = logs; i != null; i = i.next)
341             i.data.add_trackpoint();
342     }
343
344     protected void pop(Log log)
345     {
346         for (weak List<Log> i = logs; i != null; i = i.next)
347             if (i.data == log)
348                 logs.delete_link(i);
349     }
350
351     public Log start(string tag, string title)
352     {
353         bool was_empty = (logs == null);
354         Log res = new Log(tag, title);
355         logs.append(res);
356         if (was_empty) start_trace();
357         return res;
358     }
359
360     public void end(Log log)
361     {
362         pop(log);
363         log.save();
364         if (logs == null) end_trace();
365         entries_changed();
366     }
367
368     public Log load(string fname)
369     {
370         string contents;
371         size_t length;
372         FileUtils.get_contents(fname, out contents, out length);
373         LogParser parser = new LogParser();
374         parser.parse(contents, (ssize_t)length);
375         return parser.result;
376     }
377
378     public void set_acknowledged(string name, bool acked)
379     {
380         string from, to;
381         if (acked)
382         {
383             from = config.homedir + "/log/" + name;
384             to = config.homedir + "/archive/" + name;
385             DirUtils.create(config.homedir + "/archive", 0777);
386         } else {
387             from = config.homedir + "/archive/" + name;
388             to = config.homedir + "/log/" + name;
389             DirUtils.create(config.homedir + "/log", 0777);
390         }
391         if (FileUtils.test(from, FileTest.EXISTS))
392         {
393             FileUtils.rename(from, to);
394             entries_changed();
395         }
396     }
397
398     public delegate bool EntriesVisitor(string dir, string name);
399
400     protected void list_dir(string dir, EntriesVisitor visitor)
401     {
402         var d = File.new_for_path(dir);
403         var enumerator = d.enumerate_children(FILE_ATTRIBUTE_STANDARD_NAME, 0, null);
404         FileInfo file_info;
405         while ((file_info = enumerator.next_file(null)) != null)
406         {
407             if (!file_info.get_name().has_suffix(".gpx")) continue;
408             if (!visitor(dir, file_info.get_name()))
409                 break;
410         }
411     }
412
413     public void list_entries(EntriesVisitor visitor, bool only_unacked=true)
414     {
415         if (!only_unacked)
416             list_dir(config.homedir + "/archive", visitor);
417         list_dir(config.homedir + "/log", visitor);
418     }
419
420     public void instant(string tag, string msg)
421     {
422         var log = new Log(tag, msg);
423         log.add(msg);
424         log.save();
425     }
426
427     public void shutdown()
428     {
429         bool had_logs = (logs != null);
430         while (logs != null)
431         {
432             logs.data.save();
433             logs.delete_link(logs);
434         }
435         if (had_logs) end_trace();
436     }
437 }
438
439 public void error(string s)
440 {
441         stderr.printf("%s\n", s);
442 }
443 public void warning(string s)
444 {
445         stderr.printf("%s\n", s);
446 }
447 public void info(string s)
448 {
449         stderr.printf("%s\n", s);
450 }
451 public void debug(string s)
452 {
453         stderr.printf("%s\n", s);
454 }
455
456 Logger log = null;
457
458 public void init()
459 {
460     log = new Logger();
461 }
462
463 }
464 }