No issues found
1 /*
2 * Evolution calendar - Low-level alarm timer mechanism
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
16 *
17 *
18 * Authors:
19 * Miguel de Icaza <miguel@ximian.com>
20 * Federico Mena-Quintero <federico@ximian.com>
21 *
22 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
23 *
24 */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <unistd.h>
31 #include <time.h>
32 #include <fcntl.h>
33 #include <signal.h>
34 #include <sys/time.h>
35 #include <gdk/gdk.h>
36 #include "alarm.h"
37 #include "config-data.h"
38
39 /* Our glib timeout */
40 static guint timeout_id;
41
42 /* The list of pending alarms */
43 static GList *alarms = NULL;
44
45 /* A queued alarm structure */
46 typedef struct {
47 time_t trigger;
48 AlarmFunction alarm_fn;
49 gpointer data;
50 AlarmDestroyNotify destroy_notify_fn;
51 } AlarmRecord;
52
53 static void setup_timeout (void);
54
55 /* Removes the head alarm from the queue. Does not touch the timeout_id. */
56 static void
57 pop_alarm (void)
58 {
59 AlarmRecord *ar;
60 GList *l;
61
62 if (!alarms) {
63 g_warning ("Nothing to pop from the alarm queue");
64 return;
65 }
66
67 ar = alarms->data;
68
69 l = alarms;
70 alarms = g_list_delete_link (alarms, l);
71
72 g_free (ar);
73 }
74
75 /* Callback from the alarm timeout */
76 static gboolean
77 alarm_ready_cb (gpointer data)
78 {
79 time_t now;
80
81 if (!alarms) {
82 g_warning ("Alarm triggered, but no alarm present\n");
83 return FALSE;
84 }
85
86 timeout_id = 0;
87
88 now = time (NULL);
89
90 debug (("Alarm callback!"));
91 while (alarms) {
92 AlarmRecord *notify_id, *ar;
93 AlarmRecord ar_copy;
94
95 ar = alarms->data;
96
97 if (ar->trigger > now)
98 break;
99
100 debug (("Process alarm with trigger %lu", ar->trigger));
101 notify_id = ar;
102
103 ar_copy = *ar;
104 ar = &ar_copy;
105
106 /* This will free the original AlarmRecord;
107 * that's why we copy it. */
108 pop_alarm ();
109
110 (* ar->alarm_fn) (notify_id, ar->trigger, ar->data);
111
112 if (ar->destroy_notify_fn)
113 (* ar->destroy_notify_fn) (notify_id, ar->data);
114 }
115
116 /* We need this check because one of the alarm_fn above may have
117 * re-entered and added an alarm of its own, so the timer will
118 * already be set up.
119 */
120 if (alarms)
121 setup_timeout ();
122
123 return FALSE;
124 }
125
126 /* Sets up a timeout for the next minute. We do not need to be concerned with
127 * timezones here, as this is just a periodic check on the alarm queue.
128 */
129 static void
130 setup_timeout (void)
131 {
132 const AlarmRecord *ar;
133 guint diff;
134 time_t now;
135
136 if (!alarms) {
137 g_warning ("No alarm to setup\n");
138 return;
139 }
140
141 ar = alarms->data;
142
143 /* Remove the existing time out */
144 if (timeout_id != 0) {
145 g_source_remove (timeout_id);
146 timeout_id = 0;
147 }
148
149 /* Ensure that if the trigger managed to get behind the
150 * current time we timeout immediately */
151 diff = MAX (0, ar->trigger - time (NULL));
152 now = time (NULL);
153
154 /* Add the time out */
155 debug (
156 ("Setting timeout for %d.%2d (from now) %lu %lu",
157 diff / 60, diff % 60, ar->trigger, now));
158 debug ((" %s", ctime (&ar->trigger)));
159 debug ((" %s", ctime (&now)));
160 timeout_id = g_timeout_add_seconds (diff, alarm_ready_cb, NULL);
161
162 }
163
164 /* Used from g_list_insert_sorted(); compares the
165 * trigger times of two AlarmRecord structures. */
166 static gint
167 compare_alarm_by_time (gconstpointer a,
168 gconstpointer b)
169 {
170 const AlarmRecord *ara = a;
171 const AlarmRecord *arb = b;
172 time_t diff;
173
174 diff = ara->trigger - arb->trigger;
175 return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
176 }
177
178 /* Adds an alarm to the queue and sets up the timer */
179 static void
180 queue_alarm (AlarmRecord *ar)
181 {
182 GList *old_head;
183
184 /* Track the current head of the list in case there are changes */
185 old_head = alarms;
186
187 /* Insert the new alarm in order if the alarm's trigger time is
188 * after the current time */
189 alarms = g_list_insert_sorted (alarms, ar, compare_alarm_by_time);
190
191 /* If there first item on the list didn't change, the time out is fine */
192 if (old_head == alarms)
193 return;
194
195 /* Set the timer for removal upon activation */
196 setup_timeout ();
197 }
198
199 /**
200 * alarm_add:
201 * @trigger: Time at which alarm will trigger.
202 * @alarm_fn: Callback for trigger.
203 * @data: Closure data for callback.
204 * @destroy_notify_fn: destroy notification callback.
205 *
206 * Adds an alarm to trigger at the specified time. The @alarm_fn will be called
207 * with the provided data and the alarm will be removed from the trigger list.
208 *
209 * Return value: An identifier for this alarm; it can be used to remove the
210 * alarm later with alarm_remove(). If the trigger time occurs in the past, then
211 * the alarm will not be queued and the function will return NULL.
212 **/
213 gpointer
214 alarm_add (time_t trigger,
215 AlarmFunction alarm_fn,
216 gpointer data,
217 AlarmDestroyNotify destroy_notify_fn)
218 {
219 AlarmRecord *ar;
220
221 g_return_val_if_fail (trigger != -1, NULL);
222 g_return_val_if_fail (alarm_fn != NULL, NULL);
223
224 ar = g_new (AlarmRecord, 1);
225 ar->trigger = trigger;
226 ar->alarm_fn = alarm_fn;
227 ar->data = data;
228 ar->destroy_notify_fn = destroy_notify_fn;
229
230 queue_alarm (ar);
231
232 return ar;
233 }
234
235 /**
236 * alarm_remove:
237 * @alarm: A queued alarm identifier.
238 *
239 * Removes an alarm from the alarm queue.
240 **/
241 void
242 alarm_remove (gpointer alarm)
243 {
244 AlarmRecord *notify_id, *ar;
245 AlarmRecord ar_copy;
246 AlarmRecord *old_head;
247 GList *l;
248
249 g_return_if_fail (alarm != NULL);
250
251 ar = alarm;
252
253 l = g_list_find (alarms, ar);
254 if (!l) {
255 g_warning (G_STRLOC ": Requested removal of nonexistent alarm!");
256 return;
257 }
258
259 old_head = alarms->data;
260
261 notify_id = ar;
262
263 if (old_head == ar) {
264 ar_copy = *ar;
265 ar = &ar_copy;
266
267 /* This will free the original AlarmRecord;
268 * that's why we copy it. */
269 pop_alarm ();
270 } else {
271 alarms = g_list_delete_link (alarms, l);
272 }
273
274 /* Reset the timeout */
275 if (!alarms) {
276 g_source_remove (timeout_id);
277 timeout_id = 0;
278 }
279
280 /* Notify about destructiono of the alarm */
281
282 if (ar->destroy_notify_fn)
283 (* ar->destroy_notify_fn) (notify_id, ar->data);
284
285 }
286
287 /**
288 * alarm_done:
289 *
290 * Terminates the alarm timer mechanism. This should be called at the end of
291 * the program.
292 **/
293 void
294 alarm_done (void)
295 {
296 GList *l;
297
298 if (timeout_id == 0) {
299 if (alarms)
300 g_warning ("No timeout, but queue is not NULL\n");
301 return;
302 }
303
304 g_source_remove (timeout_id);
305 timeout_id = 0;
306
307 if (!alarms) {
308 g_warning ("timeout present, freed, but no alarms active\n");
309 return;
310 }
311
312 for (l = alarms; l; l = l->next) {
313 AlarmRecord *ar;
314
315 ar = l->data;
316
317 if (ar->destroy_notify_fn)
318 (* ar->destroy_notify_fn) (ar, ar->data);
319
320 g_free (ar);
321 }
322
323 g_list_free (alarms);
324 alarms = NULL;
325 }
326
327 /**
328 * alarm_reschedule_timeout:
329 *
330 * Re-sets timeout for alarms, if any.
331 **/
332 void
333 alarm_reschedule_timeout (void)
334 {
335 if (alarms)
336 setup_timeout ();
337 }