Code cleanup
[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         public at.Event ev;
75         public string label;
76
77         // Schedule with at
78         public static void schedule(string timespec, string label) throws Error
79         {
80                 string argv[5];
81                 argv[0] = "/usr/bin/at";
82                 argv[1] = "-q";
83                 argv[2] = "z";
84                 argv[3] = timespec;
85                 argv[4] = null;
86
87                 Pid pid;
88                 int stdinfd;
89
90                 if (!Process.spawn_async_with_pipes("/", argv, null, SpawnFlags.STDERR_TO_DEV_NULL, null, out pid, out stdinfd, null, null))
91                         return;
92
93                 {
94                         FileStream fs = FileStream.fdopen(stdinfd, "w");
95                         string display = GLib.Environment.get_variable("DISPLAY");
96                         if (display != null)
97                                 fs.printf("DISPLAY=\"%s\"; export DISPLAY\n", display);
98                         fs.printf("# Zavai variables start here\n");
99                         fs.printf("ZAVAI_LABEL=\"%s\"\n", label.escape(""));
100                         fs.printf("# Zavai commands starts here\n");
101                         fs.printf("%s notify \"$ZAVAI_LABEL\"", zavai.config.argv0);
102                 }
103                 
104                 Process.close_pid(pid);
105         }
106
107         // Get the label of the job with the given at ID
108         public static string? getLabel(int atID)
109         {
110                 string label = null;
111                 at.jobContents(atID, fd => {
112                         FileStream fs = FileStream.fdopen(fd, "r");
113                         while (true)
114                         {
115                                 string? line = fs.read_line();
116                                 if (line == null) break;
117                                 if (line.has_prefix("ZAVAI_LABEL=\""))
118                                 {
119                                         size_t size = line.size();
120                                         if (size < 15) continue;
121                                         label = line.substring(13, (long)(size - 14));
122                                         label = label.compress();
123                                         break;
124                                 }
125                         }
126                         return true;
127                 });
128                 return label;
129         }
130 }
131
132 [DBus (name = "org.enricozini.zavai.Alarm")]
133 public class ZavaiClock : Object {
134         public void Notify (string label) {
135                 clock.notify_alarm(label);
136         }
137 }
138
139 public class Clock: zavai.Service
140 {
141         protected time_t last_gps_time;
142         protected time_t last_gps_time_system_time;
143         protected time_t last_system_time;
144         protected dynamic DBus.Object gps_time;
145         protected uint system_time_timeout;
146         protected time_t last_minute;
147         protected time_t chosen_time;
148         protected SourceType chosen_type;
149         protected ZavaiClock dbusClock;
150
151         protected dynamic DBus.Object otimed_alarm;
152         protected dynamic DBus.Object rtc;
153         protected SList<Alarm> alarms;
154
155         // Ticks once a minute
156         public signal void minute_changed(long time, SourceType source);
157         public signal void schedule_changed(Alarm? next);
158         public signal void alarm_triggered(string label);
159
160         public Clock()
161         {
162                 Object(name: "clock");
163                 alarms = null;
164                 dbusClock = new ZavaiClock();
165                 last_minute = 0;
166                 last_gps_time = 0;
167                 last_gps_time_system_time = 0;
168                 last_system_time = time_t();
169                 chosen_time = last_system_time;
170
171                 gps_time = zavai.registry.sbus.get_object(
172                                 "org.freesmartphone.ogpsd",
173                                 "/org/freedesktop/Gypsy",
174                                 "org.freedesktop.Gypsy.Time");
175
176                 // FSO alarm system
177                 otimed_alarm = zavai.registry.sbus.get_object(
178                                 "org.freesmartphone.otimed",
179                                 "/org/freesmartphone/Time/Alarm",
180                                 "org.freesmartphone.Time.Alarm");
181
182                 rtc = zavai.registry.sbus.get_object(
183                                 "org.freesmartphone.odeviced",
184                                 "/org/freesmartphone/Device/RTC/0",
185                                 "org.freesmartphone.Device.RealtimeClock");
186
187                 zavai.registry.sbus.register_object("/org/enricozini/Zavai/Clock", dbusClock);
188         }
189
190         public void notify_alarm(string label)
191         {
192                 stderr.printf("Notifying %s\n", label);
193                 alarm_triggered(label);
194                 schedule_changed(next_alarm());
195         }
196
197         public Alarm? next_alarm()
198         {
199                 at.Event ev;
200                 ev = at.earliestID("z");
201                 if (ev.deadline == 0)
202                         return null;
203                 string label = Alarm.getLabel(ev.id);
204                 Alarm res = new Alarm();
205                 res.ev = ev;
206                 res.label = label;
207                 return res;
208         }
209
210         public void schedule(string timespec, string label) throws Error
211         {
212                 Alarm.schedule(timespec, label);
213                 schedule_changed(next_alarm());
214         }
215
216         private void on_gps_time(dynamic DBus.Object pos, int t)
217         {
218                 if (t == 0)
219                 {
220                         last_gps_time_system_time = 0;
221                         update_time();
222                 } else {
223                         last_gps_time = (time_t)t;
224                         last_gps_time_system_time = time_t();
225                         update_time();
226                 }
227         }
228
229         private bool on_system_time()
230         {
231                 last_system_time = time_t();
232                 update_time();
233                 return true;
234         }
235
236         private void update_time()
237         {
238                 if (last_gps_time_system_time + 10 > last_system_time)
239                 {
240                         chosen_time = last_gps_time;
241                         chosen_type = SourceType.GPS;
242                 }
243                 else
244                 {
245                         chosen_time = last_system_time;
246                         chosen_type = SourceType.SYSTEM;
247                 }
248                 if (chosen_time / 60 != last_minute)
249                 {
250                         last_minute = chosen_time / 60;
251                         minute_changed(chosen_time, chosen_type);
252                 }
253         }
254
255         /// Request GPS resource
256         public override void start()
257         {
258                 if (started) return;
259
260                 system_time_timeout = Timeout.add(5000, on_system_time);
261                 gps_time.TimeChanged += on_gps_time;
262                 last_system_time = time_t();
263                 update_time();
264
265                 base.start();
266         }
267
268         public override void stop()
269         {
270                 if (!started) return;
271
272                 Source.remove(system_time_timeout);
273                 gps_time.TimeChanged -= on_gps_time;
274
275                 base.stop();
276         }
277 }
278
279 public Clock clock = null;
280
281 public void init()
282 {
283         clock = new Clock();
284
285         zavai.registry.register_service(clock);
286 }
287
288 }
289 }