edc1ca17c642b663ce0bdeb6de4b9bba94418f62
[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         public void AtAlarm (string label) {
151                 clock.notify(label);
152         }
153 }
154
155 [DBus (name = "org.enricozini.zavai.Alarm")]
156 public class ZavaiAlarm : Object {
157         public void Notify () {
158                 clock.notify("ciao");
159         }
160 }
161
162 public class Clock: zavai.Service
163 {
164         protected time_t last_gps_time;
165         protected time_t last_gps_time_system_time;
166         protected time_t last_system_time;
167         protected dynamic DBus.Object gps_time;
168         protected uint system_time_timeout;
169         protected time_t last_minute;
170         protected time_t chosen_time;
171         protected SourceType chosen_type;
172         protected AlarmNotification listener;
173         protected ZavaiAlarm listener1;
174
175         protected dynamic DBus.Object otimed_alarm;
176         protected dynamic DBus.Object rtc;
177         protected SList<Alarm> alarms;
178
179         // Ticks once a minute
180         public signal void minute_changed(long time, SourceType source);
181         public signal void schedule_changed();
182
183         public Clock()
184         {
185                 Object(name: "clock");
186                 alarms = null;
187                 listener = new AlarmNotification();
188                 listener1 = new ZavaiAlarm();
189                 last_minute = 0;
190                 last_gps_time = 0;
191                 last_gps_time_system_time = 0;
192                 last_system_time = time_t();
193                 chosen_time = last_system_time;
194
195                 gps_time = zavai.registry.sbus.get_object(
196                                 "org.freesmartphone.ogpsd",
197                                 "/org/freedesktop/Gypsy",
198                                 "org.freedesktop.Gypsy.Time");
199
200                 // FSO alarm system
201                 otimed_alarm = zavai.registry.sbus.get_object(
202                                 "org.freesmartphone.otimed",
203                                 "/org/freesmartphone/Time/Alarm",
204                                 "org.freesmartphone.Time.Alarm");
205
206                 rtc = zavai.registry.sbus.get_object(
207                                 "org.freesmartphone.odeviced",
208                                 "/org/freesmartphone/Device/RTC/0",
209                                 "org.freesmartphone.Device.RealtimeClock");
210
211                 zavai.registry.sbus.register_object("/", listener);
212
213         }
214
215         public void notify(string label)
216         {
217                 stderr.printf("HAHA %s\n", label);
218         }
219
220         public Alarm? next_alarm()
221         {
222                 if (alarms == null)
223                         return null;
224                 return alarms.data;
225         }
226
227         public void schedule(Alarm a)
228         {
229                 alarms.insert_sorted(a, alarm_compare);
230                 otimed_reschedule();
231         }
232
233         private void otimed_reschedule()
234         {
235                 if (alarms != null)
236                 {
237                         zavai.log.info("Scheduling next alarm: " + alarms.data.label + " at " + Time.local(alarms.data.deadline).to_string());
238                         zavai.log.info("Scheduling at abs " + "%d".printf((int)alarms.data.deadline));
239
240                         try {
241                                 otimed_alarm.ClearAlarm(zavai.registry.bus_name);
242                         } catch (Error e) {
243                                 zavai.log.error("Cannot clear alarms: " + e.message);
244                         }
245                         try {
246                                 otimed_alarm.SetAlarm(zavai.registry.bus_name, (int)alarms.data.deadline);
247                         } catch (Error e) {
248                                 zavai.log.error("Cannot reschedule alarms: " + e.message);
249                         }
250
251                         int t = rtc.GetCurrentTime();
252                         stderr.printf("Current time: %d, RTC time: %d\n", (int)time_t(), t);
253                         t = rtc.GetWakeupTime();
254                         stderr.printf("Scheduled alarm: %d, RTC wakeup time: %d\n", (int)alarms.data.deadline, t);
255                 } else
256                         zavai.log.info("No alarms left to reschedule");
257                 schedule_changed();
258         }
259
260         public void check_alarms()
261         {
262                 last_system_time = time_t();
263                 update_time();
264                 while (alarms != null && alarms.data.deadline <= chosen_time)
265                 {
266                         Alarm a = alarms.data;
267                         alarms.remove(a);
268                         zavai.log.info("Triggering " + a.label);
269                         a.trigger(a);
270                 }
271
272                 otimed_reschedule();
273         }
274
275         private void on_gps_time(dynamic DBus.Object pos, int t)
276         {
277                 if (t == 0)
278                 {
279                         last_gps_time_system_time = 0;
280                         update_time();
281                 } else {
282                         last_gps_time = (time_t)t;
283                         last_gps_time_system_time = time_t();
284                         update_time();
285                 }
286         }
287
288         private bool on_system_time()
289         {
290                 last_system_time = time_t();
291                 update_time();
292                 return true;
293         }
294
295         private void update_time()
296         {
297                 if (last_gps_time_system_time + 10 > last_system_time)
298                 {
299                         chosen_time = last_gps_time;
300                         chosen_type = SourceType.GPS;
301                 }
302                 else
303                 {
304                         chosen_time = last_system_time;
305                         chosen_type = SourceType.SYSTEM;
306                 }
307                 if (chosen_time / 60 != last_minute)
308                 {
309                         last_minute = chosen_time / 60;
310                         minute_changed(chosen_time, chosen_type);
311                 }
312         }
313
314         /// Request GPS resource
315         public override void start()
316         {
317                 if (started) return;
318
319                 system_time_timeout = Timeout.add(5000, on_system_time);
320                 gps_time.TimeChanged += on_gps_time;
321                 last_system_time = time_t();
322                 update_time();
323
324                 base.start();
325         }
326
327         public override void stop()
328         {
329                 if (!started) return;
330
331                 Source.remove(system_time_timeout);
332                 gps_time.TimeChanged -= on_gps_time;
333
334                 base.stop();
335         }
336 }
337
338 public Clock clock = null;
339
340 public void init()
341 {
342         clock = new Clock();
343
344         zavai.registry.register_service(clock);
345 }
346
347 }
348 }