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