/*
* 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 signal void entries_changed();
+
+ 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();
+ entries_changed();
+ }
+
+ 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);
stderr.printf("%s\n", s);
}
+Logger log = null;
+
+public void init()
+{
+ log = new Logger();
+}
+
}
}