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