5a22847a7ba13f247ac3217ddf650e462670bbc2
[gregoa/zavai.git] / src / clock.vala
1 /*
2  * clock - clock resource for zavai
3  *
4  * Copyright (C) 2009  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 clock {
25
26 public enum SourceType
27 {
28         SYSTEM,
29         GPS
30 }
31
32 /*
33 TODO: schedule alarms via at
34
35 Uses the 'z' queue.
36
37 atq -q z  can be used to list the jobs (for example, at startup, or after a job has run)
38 at -c id  can be used to query a job, parsing its contents (which can have
39           comments or variables being set)
40 zavai --notify ...  can be used to notify the job (and start zavai if it's not running)
41
42 Alarm needs to be able to serialize itself to an at invocation and to
43 deserialize itself from the output of at -c
44
45 Alarm needs to deserialize also a job with no special markers whatsoever: a
46 generic at job.
47
48
49
50 refresh_alarms()
51 {
52         oldtime = next_alarm ? next_alarm.time : 0
53         next_alarm = the first alarm from atq
54         if (oldtime != next_alarm.time)
55         {
56                 remove existing triggers
57                   (triggers can be skipped if we don't need to support non-zavai alarms)
58                 schedule a trigger calling refresh_alarms() at next_alarm.time + 30 seconds
59                   (triggers can be skipped if we don't need to support non-zavai alarms)
60         }
61 }
62
63 at clock constructor: refresh_alarms()
64 inotifywait -e close /usr/bin/at -> refresh_alarms()  (shows when someone has just used at)
65   (can be skipped if we don't need to support non-zavai alarms)
66 at alarm triggered through zavai: refresh_alarms()
67
68
69 */
70
71
72 public class Alarm : Object
73 {
74         // TODO old stuff
75         // Notify of an alarm being triggered
76         public signal void trigger(Alarm a);
77
78         public time_t deadline;
79         public string label;
80
81         public Alarm(time_t deadline, string label)
82         {
83                 this.deadline = deadline;
84                 this.label = label;
85         }
86
87
88         // TODO new stuff
89
90         // Schedule with at
91         public static void schedule(string timespec, string label) throws Error
92         {
93                 string argv[4];
94                 argv[0] = "/usr/bin/at";
95                 argv[1] = timespec;
96                 argv[2] = null;
97
98                 Pid pid;
99                 int stdinfd;
100
101                 if (!Process.spawn_async_with_pipes("/", argv, null, SpawnFlags.STDERR_TO_DEV_NULL, null, out pid, out stdinfd, null, null))
102                         return;
103
104                 {
105                         FileStream fs = FileStream.fdopen(stdinfd, "w");
106                         fs.printf("# Zavai variables start here\n");
107                         fs.printf("ZAVAI_LABEL=\"%s\"\n", label.escape(""));
108                         fs.printf("# Zavai commands starts here\n");
109                         fs.printf("echo \"$ZAVAI_LABEL\" | zavai --notify");
110                 }
111                 
112                 Process.close_pid(pid);
113         }
114
115         // Get the label of the job with the given at ID
116         public static string? getLabel(int atID)
117         {
118                 string label = null;
119                 at.jobContents(atID, fd => {
120                         FileStream fs = FileStream.fdopen(fd, "r");
121                         while (true)
122                         {
123                                 string? line = fs.read_line();
124                                 if (line == null) break;
125                                 if (line.has_prefix("ZAVAI_LABEL=\""))
126                                 {
127                                         size_t size = line.size();
128                                         if (size < 15) continue;
129                                         label = line.substring(13, (long)(size - 14));
130                                         label = label.compress();
131                                         break;
132                                 }
133                         }
134                         return true;
135                 });
136                 return label;
137         }
138 }
139
140 private int alarm_compare(void* a, void* b)
141 {
142         return (int)(((Alarm*)a)->deadline - ((Alarm*)b)->deadline);
143 }
144
145 [DBus (name = "org.freesmartphone.Notification")]
146 public class AlarmNotification : Object {
147         public void Alarm () {
148                 clock.check_alarms();
149         }
150 }
151
152 [DBus (name = "org.enricozini.zavai.Alarm")]
153 public class ZavaiClock : Object {
154         public void Notify (string label) {
155                 clock.notify(label);
156         }
157 }
158
159 public class Clock: zavai.Service
160 {
161         protected time_t last_gps_time;
162         protected time_t last_gps_time_system_time;
163         protected time_t last_system_time;
164         protected dynamic DBus.Object gps_time;
165         protected uint system_time_timeout;
166         protected time_t last_minute;
167         protected time_t chosen_time;
168         protected SourceType chosen_type;
169         protected AlarmNotification listener;
170         protected ZavaiClock dbusClock;
171
172         protected dynamic DBus.Object otimed_alarm;
173         protected dynamic DBus.Object rtc;
174         protected SList<Alarm> alarms;
175
176         // Ticks once a minute
177         public signal void minute_changed(long time, SourceType source);
178         public signal void schedule_changed();
179
180         public Clock()
181         {
182                 Object(name: "clock");
183                 alarms = null;
184                 listener = new AlarmNotification();
185                 dbusClock = new ZavaiClock();
186                 last_minute = 0;
187                 last_gps_time = 0;
188                 last_gps_time_system_time = 0;
189                 last_system_time = time_t();
190                 chosen_time = last_system_time;
191
192                 gps_time = zavai.registry.sbus.get_object(
193                                 "org.freesmartphone.ogpsd",
194                                 "/org/freedesktop/Gypsy",
195                                 "org.freedesktop.Gypsy.Time");
196
197                 // FSO alarm system
198                 otimed_alarm = zavai.registry.sbus.get_object(
199                                 "org.freesmartphone.otimed",
200                                 "/org/freesmartphone/Time/Alarm",
201                                 "org.freesmartphone.Time.Alarm");
202
203                 rtc = zavai.registry.sbus.get_object(
204                                 "org.freesmartphone.odeviced",
205                                 "/org/freesmartphone/Device/RTC/0",
206                                 "org.freesmartphone.Device.RealtimeClock");
207
208                 zavai.registry.sbus.register_object("/", listener);
209                 zavai.registry.sbus.register_object("/org/enricozini/Zavai/Clock", dbusClock);
210         }
211
212         public void notify(string label)
213         {
214                 stderr.printf("HAHA %s\n", label);
215         }
216
217         public Alarm? next_alarm()
218         {
219                 if (alarms == null)
220                         return null;
221                 return alarms.data;
222         }
223
224         public void schedule(Alarm a)
225         {
226                 alarms.insert_sorted(a, alarm_compare);
227                 otimed_reschedule();
228         }
229
230         private void otimed_reschedule()
231         {
232                 if (alarms != null)
233                 {
234                         zavai.log.info("Scheduling next alarm: " + alarms.data.label + " at " + Time.local(alarms.data.deadline).to_string());
235                         zavai.log.info("Scheduling at abs " + "%d".printf((int)alarms.data.deadline));
236
237                         try {
238                                 otimed_alarm.ClearAlarm(zavai.registry.bus_name);
239                         } catch (Error e) {
240                                 zavai.log.error("Cannot clear alarms: " + e.message);
241                         }
242                         try {
243                                 otimed_alarm.SetAlarm(zavai.registry.bus_name, (int)alarms.data.deadline);
244                         } catch (Error e) {
245                                 zavai.log.error("Cannot reschedule alarms: " + e.message);
246                         }
247
248                         int t = rtc.GetCurrentTime();
249                         stderr.printf("Current time: %d, RTC time: %d\n", (int)time_t(), t);
250                         t = rtc.GetWakeupTime();
251                         stderr.printf("Scheduled alarm: %d, RTC wakeup time: %d\n", (int)alarms.data.deadline, t);
252                 } else
253                         zavai.log.info("No alarms left to reschedule");
254                 schedule_changed();
255         }
256
257         public void check_alarms()
258         {
259                 last_system_time = time_t();
260                 update_time();
261                 while (alarms != null && alarms.data.deadline <= chosen_time)
262                 {
263                         Alarm a = alarms.data;
264                         alarms.remove(a);
265                         zavai.log.info("Triggering " + a.label);
266                         a.trigger(a);
267                 }
268
269                 otimed_reschedule();
270         }
271
272         private void on_gps_time(dynamic DBus.Object pos, int t)
273         {
274                 if (t == 0)
275                 {
276                         last_gps_time_system_time = 0;
277                         update_time();
278                 } else {
279                         last_gps_time = (time_t)t;
280                         last_gps_time_system_time = time_t();
281                         update_time();
282                 }
283         }
284
285         private bool on_system_time()
286         {
287                 last_system_time = time_t();
288                 update_time();
289                 return true;
290         }
291
292         private void update_time()
293         {
294                 if (last_gps_time_system_time + 10 > last_system_time)
295                 {
296                         chosen_time = last_gps_time;
297                         chosen_type = SourceType.GPS;
298                 }
299                 else
300                 {
301                         chosen_time = last_system_time;
302                         chosen_type = SourceType.SYSTEM;
303                 }
304                 if (chosen_time / 60 != last_minute)
305                 {
306                         last_minute = chosen_time / 60;
307                         minute_changed(chosen_time, chosen_type);
308                 }
309         }
310
311         /// Request GPS resource
312         public override void start()
313         {
314                 if (started) return;
315
316                 system_time_timeout = Timeout.add(5000, on_system_time);
317                 gps_time.TimeChanged += on_gps_time;
318                 last_system_time = time_t();
319                 update_time();
320
321                 base.start();
322         }
323
324         public override void stop()
325         {
326                 if (!started) return;
327
328                 Source.remove(system_time_timeout);
329                 gps_time.TimeChanged -= on_gps_time;
330
331                 base.stop();
332         }
333 }
334
335 public Clock clock = null;
336
337 public void init()
338 {
339         clock = new Clock();
340
341         zavai.registry.register_service(clock);
342 }
343
344 }
345 }