evolution-3.6.4/calendar/alarm-notify/alarm.c

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 }