]> ToastFreeware Gitweb - gregoa/zavai.git/blobdiff - src/clock.vala
Notes about remotising devices
[gregoa/zavai.git] / src / clock.vala
index d6b2d13f01137d1f20a67936e23ab5e058005035..d08717bcd9ad82e9e5562c4d8e49ec38a4d6bd1a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * clock - clock resource for zavai
  *
- * 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
@@ -25,212 +25,332 @@ namespace clock {
 
 public enum SourceType
 {
-       SYSTEM,
-       GPS
+    SYSTEM,
+    GPS
 }
 
-public class Alarm : Object
+/*
+TODO: schedule alarms via at
+
+Uses the 'z' queue.
+
+atq -q z  can be used to list the jobs (for example, at startup, or after a job has run)
+at -c id  can be used to query a job, parsing its contents (which can have
+          comments or variables being set)
+zavai --notify ...  can be used to notify the job (and start zavai if it's not running)
+
+Alarm needs to be able to serialize itself to an at invocation and to
+deserialize itself from the output of at -c
+
+Alarm needs to deserialize also a job with no special markers whatsoever: a
+generic at job.
+
+
+
+refresh_alarms()
 {
-       public signal void trigger(Alarm a);
+    oldtime = next_alarm ? next_alarm.time : 0
+    next_alarm = the first alarm from atq
+    if (oldtime != next_alarm.time)
+    {
+        remove existing triggers
+          (triggers can be skipped if we don't need to support non-zavai alarms)
+        schedule a trigger calling refresh_alarms() at next_alarm.time + 30 seconds
+          (triggers can be skipped if we don't need to support non-zavai alarms)
+    }
+}
 
-       public time_t deadline;
-       public string label;
+at clock constructor: refresh_alarms()
+inotifywait -e close /usr/bin/at -> refresh_alarms()  (shows when someone has just used at)
+  (can be skipped if we don't need to support non-zavai alarms)
+at alarm triggered through zavai: refresh_alarms()
 
-       public Alarm(time_t deadline, string label)
-       {
-               this.deadline = deadline;
-               this.label = label;
-       }
+
+*/
+
+
+public class Alarm : Object
+{
+    public at.Event ev;
+    public string label;
+
+    // Schedule with at
+    public static void schedule(string timespec, string label) throws Error
+    {
+        string argv[5];
+        argv[0] = "/usr/bin/at";
+        argv[1] = "-q";
+        argv[2] = "z";
+        argv[3] = timespec;
+        argv[4] = null;
+
+        Pid pid;
+        int stdinfd;
+
+        if (!Process.spawn_async_with_pipes("/", argv, null, SpawnFlags.STDERR_TO_DEV_NULL, null, out pid, out stdinfd, null, null))
+            return;
+
+        {
+            FileStream fs = FileStream.fdopen(stdinfd, "w");
+            string display = GLib.Environment.get_variable("DISPLAY");
+            if (display != null)
+                fs.printf("DISPLAY=\"%s\"; export DISPLAY\n", display);
+            fs.printf("# Zavai variables start here\n");
+            fs.printf("ZAVAI_LABEL=\"%s\"\n", label.escape(""));
+            fs.printf("# Zavai commands starts here\n");
+            fs.printf("%s notify \"$ZAVAI_LABEL\"", zavai.config.argv0);
+        }
+        
+        Process.close_pid(pid);
+    }
+
+    // Get the label of the job with the given at ID
+    public static string? getLabel(int atID)
+    {
+        string label = null;
+        at.jobContents(atID, fd => {
+            FileStream fs = FileStream.fdopen(fd, "r");
+            while (true)
+            {
+                string? line = fs.read_line();
+                if (line == null) break;
+                if (line.has_prefix("ZAVAI_LABEL=\""))
+                {
+                    size_t size = line.size();
+                    if (size < 15) continue;
+                    label = line.substring(13, (long)(size - 14));
+                    label = label.compress();
+                    break;
+                }
+            }
+            return true;
+        });
+        return label;
+    }
 }
 
-private int alarm_compare(void* a, void* b)
+[DBus (name = "org.enricozini.zavai.Alarm")]
+public class ZavaiClock : Object {
+    public void Notify (string label) {
+        clock.notify_alarm(label);
+    }
+}
+
+public class AlarmTriggerInfo
 {
-       return (int)(((Alarm*)a)->deadline - ((Alarm*)b)->deadline);
+    public uint id;
+    public string label;
+    public bool acked;
+    public bool canceled;
+
+    public AlarmTriggerInfo(string label)
+    {
+        id = 0;
+        this.label = label;
+        acked = false;
+        canceled = false;
+    }
 }
 
-[DBus (name = "org.freesmartphone.Notification")]
-public class AlarmNotification : Object {
-       public void Alarm () {
-               clock.check_alarms();
-       }
+public class AlarmTriggerQueue : zavai.Service
+{
+    protected List<AlarmTriggerInfo> queue;
+
+    public signal void triggered(AlarmTriggerInfo info);
+    public signal void acked(AlarmTriggerInfo info);
+    public signal void canceled(AlarmTriggerInfo info);
+
+    public AlarmTriggerQueue()
+    {
+        queue = new List<AlarmTriggerInfo>();
+    }
+
+    public uint enqueue_trigger(AlarmTriggerInfo info)
+    {
+        // Reuse IDs from the associated logger object
+        info.id = zavai.log.log.start("alarm", "Alarm " + info.label);
+        queue.append(info);
+        if (queue.data.id == info.id)
+            triggered(queue.data);
+        return info.id;
+    }
+
+    protected void done_with_first()
+    {
+        var first = queue.data;
+        queue.remove_link(queue);
+        if (queue != null)
+            triggered(queue.data);
+    }
+
+    public void ack(AlarmTriggerInfo info)
+    {
+        if (queue == null || info.id != queue.data.id) return;
+        if (!info.acked && !info.canceled)
+        {
+            info.acked = true;
+            acked(info);
+            zavai.log.log.add(info.id, "alarm acknowledged");
+            zavai.log.log.end(info.id);
+        }
+        done_with_first();
+    }
+
+    public void cancel(AlarmTriggerInfo info)
+    {
+        if (queue == null || info.id != queue.data.id) return;
+        if (!info.acked && !info.canceled)
+        {
+            info.canceled = true;
+            canceled(info);
+            zavai.log.log.add(info.id, "alarm canceled");
+            zavai.log.log.end(info.id);
+        }
+        done_with_first();
+    }
 }
 
 public class Clock: zavai.Service
 {
-       protected time_t last_gps_time;
-       protected time_t last_gps_time_system_time;
-       protected time_t last_system_time;
-       protected dynamic DBus.Object gps_time;
-       protected uint system_time_timeout;
-       protected time_t last_minute;
-       protected time_t chosen_time;
-       protected SourceType chosen_type;
-       protected AlarmNotification listener;
-
-       protected dynamic DBus.Object otimed_alarm;
-       protected dynamic DBus.Object rtc;
-       protected SList<Alarm> alarms;
-
-       // Ticks once a minute
-       public signal void minute_changed(long time, SourceType source);
-       public signal void schedule_changed();
-
-       public Clock()
-       {
-               name = "clock";
-               alarms = null;
-               listener = new AlarmNotification();
-               last_minute = 0;
-               last_gps_time = 0;
-               last_gps_time_system_time = 0;
-               last_system_time = time_t();
-               chosen_time = last_system_time;
-
-               gps_time = zavai.registry.sbus.get_object(
-                               "org.freesmartphone.ogpsd",
-                               "/org/freedesktop/Gypsy",
-                               "org.freedesktop.Gypsy.Time");
-
-               // FSO alarm system
-               otimed_alarm = zavai.registry.sbus.get_object(
-                               "org.freesmartphone.otimed",
-                               "/org/freesmartphone/Time/Alarm",
-                               "org.freesmartphone.Time.Alarm");
-
-               rtc = zavai.registry.sbus.get_object(
-                               "org.freesmartphone.odeviced",
-                               "/org/freesmartphone/Device/RTC/0",
-                               "org.freesmartphone.Device.RealtimeClock");
-
-               zavai.registry.sbus.register_object("/", listener);
-
-       }
-
-       public Alarm? next_alarm()
-       {
-               if (alarms == null)
-                       return null;
-               return alarms.data;
-       }
-
-       public void schedule(Alarm a)
-       {
-               alarms.insert_sorted(a, alarm_compare);
-               otimed_reschedule();
-       }
-
-       private void otimed_reschedule()
-       {
-               if (alarms != null)
-               {
-                       zavai.log.info("Scheduling next alarm: " + alarms.data.label + " at " + Time.local(alarms.data.deadline).to_string());
-                       zavai.log.info("Scheduling at abs " + "%d".printf((int)alarms.data.deadline));
-
-                       try {
-                               otimed_alarm.ClearAlarm(zavai.registry.bus_name);
-                       } catch (Error e) {
-                               zavai.log.error("Cannot clear alarms: " + e.message);
-                       }
-                       try {
-                               otimed_alarm.SetAlarm(zavai.registry.bus_name, (int)alarms.data.deadline);
-                       } catch (Error e) {
-                               zavai.log.error("Cannot reschedule alarms: " + e.message);
-                       }
-
-                       string t = rtc.GetCurrentTime();
-                       stderr.printf("Current time: %d, RTC time: %s\n", (int)time_t(), t);
-                       t = rtc.GetWakeupTime();
-                       stderr.printf("Scheduled alarm: %d, RTC wakeup time: %s\n", (int)alarms.data.deadline, t);
-               } else
-                       zavai.log.info("No alarms left to reschedule");
-               schedule_changed();
-       }
-
-       public void check_alarms()
-       {
-               last_system_time = time_t();
-               update_time();
-               while (alarms != null && alarms.data.deadline <= chosen_time)
-               {
-                       Alarm a = alarms.data;
-                       alarms.remove(a);
-                       zavai.log.info("Triggering " + a.label);
-                       a.trigger(a);
-               }
-
-               otimed_reschedule();
-       }
-
-       private void on_gps_time(dynamic DBus.Object pos, int t)
-       {
-               if (t == 0)
-               {
-                       last_gps_time_system_time = 0;
-                       update_time();
-               } else {
-                       last_gps_time = (time_t)t;
-                       last_gps_time_system_time = time_t();
-                       update_time();
-               }
-       }
-
-       private bool on_system_time()
-       {
-               last_system_time = time_t();
-               update_time();
-               return true;
-       }
-
-       private void update_time()
-       {
-               if (last_gps_time_system_time + 10 > last_system_time)
-               {
-                       chosen_time = last_gps_time;
-                       chosen_type = SourceType.GPS;
-               }
-               else
-               {
-                       chosen_time = last_system_time;
-                       chosen_type = SourceType.SYSTEM;
-               }
-               if (chosen_time / 60 != last_minute)
-               {
-                       last_minute = chosen_time / 60;
-                       minute_changed(chosen_time, chosen_type);
-               }
-       }
-
-       /// Request GPS resource
-       public override void start()
-       {
-               if (started) return;
-
-               system_time_timeout = Timeout.add(5000, on_system_time);
-               gps_time.TimeChanged += on_gps_time;
-               last_system_time = time_t();
-               update_time();
-
-               base.start();
-       }
-
-       public override void stop()
-       {
-               if (!started) return;
-
-               Source.remove(system_time_timeout);
-               gps_time.TimeChanged -= on_gps_time;
-
-               base.stop();
-       }
+    protected time_t last_gps_time;
+    protected time_t last_gps_time_system_time;
+    protected time_t last_system_time;
+    protected uint system_time_timeout;
+    protected time_t last_minute;
+    protected time_t chosen_time;
+    protected SourceType chosen_type;
+    protected ZavaiClock dbusClock;
+
+    protected dynamic DBus.Object otimed_alarm;
+    protected dynamic DBus.Object rtc;
+    protected SList<Alarm> alarms;
+
+    // Ticks once a minute
+    public signal void minute_changed(long time, SourceType source);
+    public signal void schedule_changed(Alarm? next);
+
+    public Clock()
+    {
+        Object(name: "clock");
+        alarms = null;
+        dbusClock = new ZavaiClock();
+        last_minute = 0;
+        last_gps_time = 0;
+        last_gps_time_system_time = 0;
+        last_system_time = time_t();
+        chosen_time = last_system_time;
+
+        // FSO alarm system
+        otimed_alarm = zavai.registry.sbus.get_object(
+                "org.freesmartphone.otimed",
+                "/org/freesmartphone/Time/Alarm",
+                "org.freesmartphone.Time.Alarm");
+
+        rtc = zavai.registry.sbus.get_object(
+                "org.freesmartphone.odeviced",
+                "/org/freesmartphone/Device/RTC/0",
+                "org.freesmartphone.Device.RealtimeClock");
+
+        zavai.registry.sbus.register_object("/org/enricozini/Zavai/Clock", dbusClock);
+    }
+
+    public void notify_alarm(string label)
+    {
+        stderr.printf("Notifying %s\n", label);
+        AlarmTriggerInfo info = new AlarmTriggerInfo(label);
+        alarm_trigger_queue.enqueue_trigger(info);
+        schedule_changed(next_alarm());
+    }
+
+    public Alarm? next_alarm()
+    {
+        at.Event ev;
+        ev = at.earliestID("z");
+        if (ev.deadline == 0)
+            return null;
+        string label = Alarm.getLabel(ev.id);
+        Alarm res = new Alarm();
+        res.ev = ev;
+        res.label = label;
+        return res;
+    }
+
+    public void schedule(string timespec, string label) throws Error
+    {
+        Alarm.schedule(timespec, label);
+        schedule_changed(next_alarm());
+    }
+
+    private void on_gps_time(uint t)
+    {
+        if (t == 0)
+        {
+            last_gps_time_system_time = 0;
+            update_time();
+        } else {
+            last_gps_time = (time_t)t;
+            last_gps_time_system_time = time_t();
+            update_time();
+        }
+    }
+
+    private bool on_system_time()
+    {
+        last_system_time = time_t();
+        update_time();
+        return true;
+    }
+
+    private void update_time()
+    {
+        if (last_gps_time_system_time + 10 > last_system_time)
+        {
+            chosen_time = last_gps_time;
+            chosen_type = SourceType.GPS;
+        }
+        else
+        {
+            chosen_time = last_system_time;
+            chosen_type = SourceType.SYSTEM;
+        }
+        if (chosen_time / 60 != last_minute)
+        {
+            last_minute = chosen_time / 60;
+            minute_changed(chosen_time, chosen_type);
+        }
+    }
+
+    /// Request GPS resource
+    public override void start()
+    {
+        if (started) return;
+
+        system_time_timeout = Timeout.add(5000, on_system_time);
+        zavai.gps.gps.time_changed += on_gps_time;
+        last_system_time = time_t();
+        update_time();
+
+        base.start();
+    }
+
+    public override void stop()
+    {
+        if (!started) return;
+
+        Source.remove(system_time_timeout);
+        zavai.gps.gps.time_changed -= on_gps_time;
+
+        base.stop();
+    }
 }
 
 public Clock clock = null;
+public AlarmTriggerQueue alarm_trigger_queue = null;
 
 public void init()
 {
-       clock = new Clock();
-
-       zavai.registry.register_service(clock);
+    clock = new Clock();
+    alarm_trigger_queue = new AlarmTriggerQueue();
 }
 
 }