Enumerate log entries using delegates and lambdas
[gregoa/zavai.git] / src / log.vala
index e9499395ef04a118212ef7e1d298e632b751eec0..2f10dbc8b5766b69ffe60e728e0cc2870e86cced 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * log - logging functions
  *
- * Copyright (C) 2009  Enrico Zini <enrico@enricozini.org>
+ * Copyright (C) 2009--2010  Enrico Zini <enrico@enricozini.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+using GLib;
+
 namespace zavai {
 namespace log {
 
+public class Waypoint : Object
+{
+    public time_t ts;
+    public double lat;
+    public double lon;
+
+    public Waypoint()
+    {
+        if (gps.gps.fix_status() != libgps.STATUS_NO_FIX)
+        {
+            lat = gps.gps.info().fix.latitude;
+            lon = gps.gps.info().fix.longitude;
+            ts = (time_t)gps.gps.info().fix.time;
+        } else {
+            // Use 1000 as missing values
+            lat = 1000;
+            lon = 1000;
+            ts = time_t();
+        }
+    }
+
+    public void writeInside(FileStream outfd)
+    {
+        var t = Time.gm(ts);
+        outfd.printf("   <time>%s</time>\n", t.format("%Y-%m-%dT%H:%M:%S%z"));
+    }
+}
+
+
+public class LogEntry : Waypoint
+{
+    public string msg;
+
+    public LogEntry()
+    {
+        base();
+    }
+
+    public void write(FileStream outfd)
+    {
+        outfd.printf("   <wpt lat=\"%f\" lon=\"%f\">\n", lat, lon);
+        writeInside(outfd);
+        outfd.printf("   <name>%s</name>\n", Markup.escape_text(msg));
+        outfd.puts("   </wpt>\n");
+    }
+}
+
+public class TrackEntry : Waypoint
+{
+    public TrackEntry()
+    {
+        base();
+    }
+
+    public void write(FileStream outfd)
+    {
+        outfd.printf("   <trkpt lat=\"%f\" lon=\"%f\">\n", lat, lon);
+        writeInside(outfd);
+        outfd.puts("   </trkpt>\n");
+    }
+}
+
+
+public class Log : Object
+{
+    public string tag;
+    public string title;
+    public bool acked;
+    public List<LogEntry> entries;
+    public List<TrackEntry> track;
+
+    public Log(string tag, string title, bool acked=false)
+    {
+        this.tag = tag;
+        this.title = title;
+        this.acked = acked;
+        entries = null;
+        track = null;
+    }
+
+    public void add(string msg)
+    {
+        var entry = new LogEntry();
+        entry.msg = msg;
+        entries.append(entry);
+    }
+
+    public void add_trackpoint()
+    {
+        track.append(new TrackEntry());
+    }
+
+    public void save()
+    {
+        if (entries == null) return;
+
+        // Directory where we save the log
+        string dir;
+        if (acked)
+            dir = config.homedir + "/archive";
+        else
+            dir = config.homedir + "/log";
+        DirUtils.create(dir, 0777);
+
+        // First try with a plain name
+        var t = Time.local(entries.data.ts);
+        string basename = dir + "/" + t.format("%Y%m%d-%H%M%S") + "-" + tag;
+
+        string pathname = basename + ".gpx";
+
+        // Find a pathname that does not exist already
+        for (int i = 1; FileUtils.test(pathname, FileTest.EXISTS); ++i)
+            pathname = "%s-%d.gpx".printf(basename, i);
+
+        // Write out
+        var outfd = FileStream.open(pathname, "w");
+        if (outfd == null)
+        {
+            zavai.log.error("opening " + pathname + ": " + strerror(errno));
+            return;
+        }
+
+        write(outfd);
+        outfd.flush();
+    }
+
+    public void dump()
+    {
+        write(stderr);
+    }
+
+    protected void writeTrack(FileStream outfd)
+    {
+        outfd.puts("   <trk>\n");
+        outfd.puts("     <trkseg>\n");
+        for (weak List<TrackEntry> i = track; i != null; i = i.next)
+            i.data.write(outfd);
+        outfd.puts("     </trkseg>\n");
+        outfd.puts("   </trk>\n");
+    }
+
+    protected void writeEntries(FileStream outfd)
+    {
+        for (weak List<LogEntry> i = entries; i != null; i = i.next)
+            i.data.write(outfd);
+    }
+
+    protected void write(FileStream outfd)
+    {
+        outfd.puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+        outfd.puts("<gpx version=\"1.0\"\n");
+        outfd.printf("     creator=\"zavai %s\"\n", zavai.config.version);
+        outfd.puts("     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
+        outfd.puts("     xmlns=\"http://www.topografix.com/GPX/1/0\"\n");
+        outfd.puts("     xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
+        outfd.puts("  <metadata>\n");
+        outfd.printf("    <name>%s</name>\n", Markup.escape_text(title));
+        outfd.puts("  </metadata>\n");
+        if (track != null) writeTrack(outfd);
+        if (entries != null) writeEntries(outfd);
+        outfd.puts(" </gpx>\n");
+    }
+}
+
+enum LogParserState {
+    NONE,
+    METADATA,
+    TRACK,
+    WPT,
+}
+
+class LogParser: Object
+{
+    const MarkupParser parser = { // It's a structure, not an object
+        start,// when an element opens
+        end,  // when an element closes
+        text, // when text is found
+        null, // when comments are found
+        null  // when errors occur
+    };
+
+    MarkupParseContext context = null;
+    public Log result = null;
+    LogParserState state = LogParserState.NONE;
+    string cur_text = "";
+    LogEntry cur_logentry = null;
+    TrackEntry cur_trackentry = null;
+
+    construct
+    {
+        context = new MarkupParseContext(
+            parser, // the structure with the callbacks
+            0,      // MarkupParseFlags
+            this,   // extra argument for the callbacks, methods in this case
+            destroy // when the parsing ends
+        );
+    }
+
+    void destroy()
+    {
+        cur_text = "";
+        cur_logentry = null;
+        cur_trackentry = null;
+    }
+
+    public bool parse(string content, ssize_t len = -1) throws MarkupError
+    {
+        string oldtz = Environment.get_variable("TZ");
+        Environment.set_variable("TZ", "UTC", true);
+        bool res = context.parse(content, len);
+        if (oldtz == null)
+            Environment.unset_variable("TZ");
+        else
+            Environment.set_variable("TZ", oldtz, true);
+        return res;
+    }
+
+    void parse_attrs(Waypoint w, string[] attr_names, string[] attr_values)
+    {
+        w.lat = 1000;
+        w.lon = 1000;
+        for (int i = 0; attr_names[i] != null; ++i)
+        {
+            if (attr_names[i] == "lat")
+                w.lat = attr_values[i].to_double();
+            else if (attr_names[i] == "lon")
+                w.lon = attr_values[i].to_double();
+        }
+    }
+
+    void start (MarkupParseContext context, string name,
+                string[] attr_names, string[] attr_values) throws MarkupError
+    {
+        if (name == "gpx")
+        {
+            state = LogParserState.NONE;
+            result = new Log("TODO:TAG", "TODO:TITLE");
+        } else if (name == "metadata") {
+            state = LogParserState.METADATA;
+        } else if (name == "wpt") {
+            cur_logentry = new LogEntry();
+            parse_attrs(cur_logentry, attr_names, attr_values);
+            result.entries.append(cur_logentry);
+            state = LogParserState.WPT;
+        } else if (name == "trkpt") {
+            cur_trackentry = new TrackEntry();
+            parse_attrs(cur_trackentry, attr_names, attr_values);
+            result.track.append(cur_trackentry);
+            state = LogParserState.TRACK;
+        }
+        cur_text = "";
+    }
+
+    void end (MarkupParseContext context, string name) throws MarkupError
+    {
+        if (name == "name")
+        {
+            switch (state)
+            {
+                case LogParserState.METADATA:
+                    result.title = cur_text;
+                    break;
+                case LogParserState.WPT:
+                    cur_logentry.msg = cur_text;
+                    break;
+            }
+        }
+        else if (name == "time")
+        {
+            Time t = Time();
+            t.strptime(cur_text, "%Y-%m-%dT%H:%M:%S%z");
+            if (state == LogParserState.WPT)
+                cur_logentry.ts = t.mktime();
+            else if (state == LogParserState.TRACK)
+                cur_trackentry.ts = t.mktime();
+        }
+    }
+
+    void text (MarkupParseContext context,
+               string text, size_t text_len) throws MarkupError
+    {
+        cur_text += text;
+    }
+}
+
+public class Logger : Resource, Object
+{
+    protected List<Log> logs;
+
+    public Logger()
+    {
+        logs = null;
+        zavai.registry.register(this);
+    }
+
+    protected void start_trace()
+    {
+        gps.gps.pos_changed += on_pos_changed;
+    }
+
+    protected void end_trace()
+    {
+        gps.gps.pos_changed -= on_pos_changed;
+    }
+
+    protected void on_pos_changed()
+    {
+        for (weak List<Log> i = logs; i != null; i = i.next)
+            i.data.add_trackpoint();
+    }
+
+    protected void pop(Log log)
+    {
+        for (weak List<Log> i = logs; i != null; i = i.next)
+            if (i.data == log)
+                logs.delete_link(i);
+    }
+
+    public Log start(string tag, string title)
+    {
+        bool was_empty = (logs == null);
+        Log res = new Log(tag, title);
+        logs.append(res);
+        if (was_empty) start_trace();
+        return res;
+    }
+
+    public void end(Log log)
+    {
+        pop(log);
+        log.save();
+        if (logs == null) end_trace();
+    }
+
+    public Log load(string fname)
+    {
+        string contents;
+        size_t length;
+        FileUtils.get_contents(fname, out contents, out length);
+        LogParser parser = new LogParser();
+        parser.parse(contents, (ssize_t)length);
+        return parser.result;
+    }
+
+    public delegate bool EntriesVisitor(string dir, string name);
+
+
+    protected void list_dir(string dir, EntriesVisitor visitor)
+    {
+        var d = File.new_for_path(dir);
+        var enumerator = d.enumerate_children(FILE_ATTRIBUTE_STANDARD_NAME, 0, null);
+        FileInfo file_info;
+        while ((file_info = enumerator.next_file(null)) != null)
+        {
+            if (!file_info.get_name().has_suffix(".gpx")) continue;
+            if (!visitor(dir, file_info.get_name()))
+                break;
+        }
+    }
+
+    public void list_entries(EntriesVisitor visitor, bool only_unacked=true)
+    {
+        if (!only_unacked)
+            list_dir(config.homedir + "/archive", visitor);
+        list_dir(config.homedir + "/log", visitor);
+    }
+
+    public void instant(string tag, string msg)
+    {
+        var log = new Log(tag, msg);
+        log.add(msg);
+        log.save();
+    }
+
+    public void shutdown()
+    {
+        bool had_logs = (logs != null);
+        while (logs != null)
+        {
+            logs.data.save();
+            logs.delete_link(logs);
+        }
+        if (had_logs) end_trace();
+    }
+}
+
 public void error(string s)
 {
        stderr.printf("%s\n", s);
@@ -38,5 +426,12 @@ public void debug(string s)
        stderr.printf("%s\n", s);
 }
 
+Logger log = null;
+
+public void init()
+{
+    log = new Logger();
+}
+
 }
 }