Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
alarm-queue.c:1144:19 | clang-analyzer | Access to field 'cqa' results in a dereference of a null pointer (loaded from variable 'tray_data') |
1 /*
2 * Alarm queueing engine
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 * Federico Mena-Quintero <federico@ximian.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 *
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <string.h>
30 #include <gtk/gtk.h>
31 #include <glib/gi18n.h>
32
33 #ifdef HAVE_CANBERRA
34 #include <canberra-gtk.h>
35 #endif
36
37 #ifdef HAVE_LIBNOTIFY
38 #include <libnotify/notify.h>
39 #endif
40
41 #include "alarm.h"
42 #include "alarm-notify-dialog.h"
43 #include "alarm-queue.h"
44 #include "alarm-notify.h"
45 #include "config-data.h"
46 #include "util.h"
47
48 #include "calendar/gui/print.h"
49
50 /* The dialog with alarm nofications */
51 static AlarmNotificationsDialog *alarm_notifications_dialog = NULL;
52
53 /* Whether the queueing system has been initialized */
54 static gboolean alarm_queue_inited = FALSE;
55
56 /* Clients we are monitoring for alarms */
57 static GHashTable *client_alarms_hash = NULL;
58
59 /* List of tray icons being displayed */
60 static GList *tray_icons_list = NULL;
61
62 /* Top Tray Image */
63 static GtkStatusIcon *tray_icon = NULL;
64 static gint tray_blink_id = -1;
65 static gint tray_blink_countdown = 0;
66 static AlarmNotify *an;
67
68 /* Structure that stores a client we are monitoring */
69 typedef struct {
70 /* Monitored client */
71 ECalClient *cal_client;
72
73 /* The live view to the calendar */
74 ECalClientView *view;
75
76 /* Hash table of component UID -> CompQueuedAlarms. If an element is
77 * present here, then it means its cqa->queued_alarms contains at least
78 * one queued alarm. When all the alarms for a component have been
79 * dequeued, the CompQueuedAlarms structure is removed from the hash
80 * table. Thus a CQA exists <=> it has queued alarms.
81 */
82 GHashTable *uid_alarms_hash;
83 } ClientAlarms;
84
85 /* Pair of a ECalComponentAlarms and the mapping from queued alarm IDs to the
86 * actual alarm instance structures.
87 */
88 typedef struct {
89 /* The parent client alarms structure */
90 ClientAlarms *parent_client;
91
92 /* The component's UID */
93 ECalComponentId *id;
94
95 /* The actual component and its alarm instances */
96 ECalComponentAlarms *alarms;
97
98 /* List of QueuedAlarm structures */
99 GSList *queued_alarms;
100
101 /* Flags */
102 gboolean expecting_update;
103 } CompQueuedAlarms;
104
105 /* Pair of a queued alarm ID and the alarm trigger instance it refers to */
106 typedef struct {
107 /* Alarm ID from alarm.h */
108 gpointer alarm_id;
109
110 /* Instance from our parent CompQueuedAlarms->alarms->alarms list */
111 ECalComponentAlarmInstance *instance;
112
113 /* original trigger of the instance from component */
114 time_t orig_trigger;
115
116 /* Whether this is a snoozed queued alarm or a normal one */
117 guint snooze : 1;
118 } QueuedAlarm;
119
120 /* Alarm ID for the midnight refresh function */
121 static gpointer midnight_refresh_id = NULL;
122 static time_t midnight = 0;
123
124 static void remove_client_alarms (ClientAlarms *ca);
125 static void display_notification (time_t trigger,
126 CompQueuedAlarms *cqa,
127 gpointer alarm_id,
128 gboolean use_description);
129 static void audio_notification (time_t trigger,
130 CompQueuedAlarms *cqa,
131 gpointer alarm_id);
132 static void mail_notification (time_t trigger,
133 CompQueuedAlarms *cqa,
134 gpointer alarm_id);
135 static void procedure_notification (time_t trigger,
136 CompQueuedAlarms *cqa,
137 gpointer alarm_id);
138 #ifdef HAVE_LIBNOTIFY
139 static void popup_notification (time_t trigger,
140 CompQueuedAlarms *cqa,
141 gpointer alarm_id,
142 gboolean use_description);
143 #endif
144 static void query_objects_modified_cb (ECalClientView *view,
145 const GSList *objects,
146 gpointer data);
147 static void query_objects_removed_cb (ECalClientView *view,
148 const GSList *uids,
149 gpointer data);
150
151 static void update_cqa (CompQueuedAlarms *cqa,
152 ECalComponent *comp);
153 static void update_qa (ECalComponentAlarms *alarms,
154 QueuedAlarm *qa);
155 static void tray_list_remove_cqa (CompQueuedAlarms *cqa);
156 static void on_dialog_objs_removed_cb (ECalClientView *view,
157 const GSList *uids,
158 gpointer data);
159
160 /* Alarm queue engine */
161
162 static void load_alarms_for_today (ClientAlarms *ca);
163 static void midnight_refresh_cb (gpointer alarm_id,
164 time_t trigger,
165 gpointer data);
166
167 /* Simple asynchronous message dispatcher */
168
169 typedef struct _Message Message;
170 typedef void (*MessageFunc) (Message *msg);
171
172 struct _Message {
173 MessageFunc func;
174 };
175
176 /*
177 static void
178 message_proxy (Message *msg)
179 {
180 g_return_if_fail (msg->func != NULL);
181 *
182 msg->func (msg);
183 }
184 *
185 static gpointer
186 create_thread_pool (void)
187 {
188 return g_thread_pool_new ((GFunc) message_proxy, NULL, 1, FALSE, NULL);
189 }*/
190
191 static void
192 message_push (Message *msg)
193 {
194 /* This used be pushed through the thread pool. This fix is made to
195 * work-around the crashers in dbus due to threading. The threading
196 * is not completely removed as its better to have alarm daemon
197 * running in a thread rather than blocking main thread. This is
198 * the reason the creation of thread pool is commented out. */
199 msg->func (msg);
200 }
201
202 /*
203 * use a static ring-buffer so we can call this twice
204 * in a printf without getting nonsense results.
205 */
206 static const gchar *
207 e_ctime (const time_t *timep)
208 {
209 static gchar *buffer[4] = { 0, };
210 static gint next = 0;
211 const gchar *ret;
212
213 g_free (buffer[next]);
214 ret = buffer[next++] = g_strdup (ctime (timep));
215 if (buffer[next - 1] && *buffer[next - 1]) {
216 gint len = strlen (buffer[next - 1]);
217 while (len > 0 && (buffer[next - 1][len - 1] == '\n' ||
218 buffer[next - 1][len - 1] == '\r' ||
219 g_ascii_isspace (buffer[next - 1][len - 1])))
220 len--;
221
222 buffer[next - 1][len - 1] = 0;
223 }
224
225 if (next >= G_N_ELEMENTS (buffer))
226 next = 0;
227
228 return ret;
229 }
230
231 /* Queues an alarm trigger for midnight so that we can load the next
232 * day's worth of alarms. */
233 static void
234 queue_midnight_refresh (void)
235 {
236 icaltimezone *zone;
237
238 if (midnight_refresh_id != NULL) {
239 alarm_remove (midnight_refresh_id);
240 midnight_refresh_id = NULL;
241 }
242
243 zone = config_data_get_timezone ();
244 midnight = time_day_end_with_zone (time (NULL), zone);
245
246 debug (("Refresh at %s", e_ctime (&midnight)));
247
248 midnight_refresh_id = alarm_add (
249 midnight, midnight_refresh_cb, NULL, NULL);
250 if (!midnight_refresh_id) {
251 debug (("Could not setup the midnight refresh alarm"));
252 /* FIXME: what to do? */
253 }
254 }
255
256 /* Loads a client's alarms; called from g_hash_table_foreach() */
257 static void
258 add_client_alarms_cb (gpointer key,
259 gpointer value,
260 gpointer data)
261 {
262 ClientAlarms *ca = (ClientAlarms *) value;
263
264 debug (("Adding %p", ca));
265
266 load_alarms_for_today (ca);
267 }
268
269 struct _midnight_refresh_msg {
270 Message header;
271 gboolean remove;
272 };
273
274 /* Loads the alarms for the new day every midnight */
275 static void
276 midnight_refresh_async (struct _midnight_refresh_msg *msg)
277 {
278 debug (("..."));
279
280 /* Re-load the alarms for all clients */
281 g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL);
282
283 /* Re-schedule the midnight update */
284 if (msg->remove && midnight_refresh_id != NULL) {
285 debug (("Reschedule the midnight update"));
286 alarm_remove (midnight_refresh_id);
287 midnight_refresh_id = NULL;
288 }
289
290 queue_midnight_refresh ();
291
292 g_slice_free (struct _midnight_refresh_msg, msg);
293 }
294
295 static void
296 midnight_refresh_cb (gpointer alarm_id,
297 time_t trigger,
298 gpointer data)
299 {
300 struct _midnight_refresh_msg *msg;
301
302 msg = g_slice_new0 (struct _midnight_refresh_msg);
303 msg->header.func = (MessageFunc) midnight_refresh_async;
304 msg->remove = TRUE;
305
306 message_push ((Message *) msg);
307 }
308
309 /* Looks up a client in the client alarms hash table */
310 static ClientAlarms *
311 lookup_client (ECalClient *cal_client)
312 {
313 return g_hash_table_lookup (client_alarms_hash, cal_client);
314 }
315
316 /* Looks up a queued alarm based on its alarm ID */
317 static QueuedAlarm *
318 lookup_queued_alarm (CompQueuedAlarms *cqa,
319 gpointer alarm_id)
320 {
321 GSList *l;
322 QueuedAlarm *qa;
323
324 qa = NULL;
325
326 for (l = cqa->queued_alarms; l; l = l->next) {
327 qa = l->data;
328 if (qa->alarm_id == alarm_id)
329 return qa;
330 }
331
332 /* not found, might have been updated/removed */
333 return NULL;
334 }
335
336 /* Removes an alarm from the list of alarms of a component. If the alarm was
337 * the last one listed for the component, it removes the component itself.
338 */
339 static gboolean
340 remove_queued_alarm (CompQueuedAlarms *cqa,
341 gpointer alarm_id,
342 gboolean free_object,
343 gboolean remove_alarm)
344 {
345 QueuedAlarm *qa = NULL;
346 GSList *l;
347
348 debug (("..."));
349
350 for (l = cqa->queued_alarms; l; l = l->next) {
351 qa = l->data;
352 if (qa->alarm_id == alarm_id)
353 break;
354 }
355
356 if (!l)
357 return FALSE;
358
359 cqa->queued_alarms = g_slist_delete_link (cqa->queued_alarms, l);
360
361 if (remove_alarm) {
362 GError *error = NULL;
363 ECalComponentId *id;
364
365 id = e_cal_component_get_id (cqa->alarms->comp);
366 if (id) {
367 cqa->expecting_update = TRUE;
368 e_cal_client_discard_alarm_sync (
369 cqa->parent_client->cal_client, id->uid,
370 id->rid, qa->instance->auid, NULL, &error);
371 cqa->expecting_update = FALSE;
372
373 if (error) {
374 if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED))
375 g_warning (
376 "%s: Failed to discard alarm: %s",
377 G_STRFUNC, error->message);
378 g_error_free (error);
379 }
380 e_cal_component_free_id (id);
381 }
382 }
383
384 g_free (qa);
385
386 /* If this was the last queued alarm for this component, remove the
387 * component itself.
388 */
389
390 if (cqa->queued_alarms != NULL)
391 return FALSE;
392
393 debug (("Last Component. Removing CQA- Free=%d", free_object));
394 if (free_object) {
395 cqa->id = NULL;
396 cqa->parent_client = NULL;
397 e_cal_component_alarms_free (cqa->alarms);
398 g_free (cqa);
399 } else {
400 e_cal_component_alarms_free (cqa->alarms);
401 cqa->alarms = NULL;
402 }
403
404 return TRUE;
405 }
406
407 /**
408 * has_known_notification:
409 * Test for notification method and returns if it knows it or not.
410 * @comp: Component with an alarm.
411 * @alarm_uid: ID of the alarm in the comp to test.
412 *
413 * Returns: %TRUE when we know the notification type, %FALSE otherwise.
414 */
415 static gboolean
416 has_known_notification (ECalComponent *comp,
417 const gchar *alarm_uid)
418 {
419 ECalComponentAlarm *alarm;
420 ECalComponentAlarmAction action;
421
422 g_return_val_if_fail (comp != NULL, FALSE);
423 g_return_val_if_fail (alarm_uid != NULL, FALSE);
424
425 alarm = e_cal_component_get_alarm (comp, alarm_uid);
426 if (!alarm)
427 return FALSE;
428
429 e_cal_component_alarm_get_action (alarm, &action);
430 e_cal_component_alarm_free (alarm);
431
432 switch (action) {
433 case E_CAL_COMPONENT_ALARM_AUDIO:
434 case E_CAL_COMPONENT_ALARM_DISPLAY:
435 case E_CAL_COMPONENT_ALARM_EMAIL:
436 case E_CAL_COMPONENT_ALARM_PROCEDURE:
437 return TRUE;
438 default:
439 break;
440 }
441
442 return FALSE;
443 }
444
445 /* Callback used when an alarm triggers */
446 static void
447 alarm_trigger_cb (gpointer alarm_id,
448 time_t trigger,
449 gpointer data)
450 {
451 CompQueuedAlarms *cqa;
452 ECalComponent *comp;
453 QueuedAlarm *qa;
454 ECalComponentAlarm *alarm;
455 ECalComponentAlarmAction action;
456
457 cqa = data;
458 comp = cqa->alarms->comp;
459
460 config_data_set_last_notification_time (
461 cqa->parent_client->cal_client, trigger);
462 debug (("Setting Last notification time to %s", e_ctime (&trigger)));
463
464 qa = lookup_queued_alarm (cqa, alarm_id);
465 if (!qa)
466 return;
467
468 /* Decide what to do based on the alarm action. We use the trigger that
469 * is passed to us instead of the one from the instance structure
470 * because this may be a snoozed alarm instead of an original
471 * occurrence.
472 */
473
474 alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
475 if (!alarm)
476 return;
477
478 e_cal_component_alarm_get_action (alarm, &action);
479 e_cal_component_alarm_free (alarm);
480
481 switch (action) {
482 case E_CAL_COMPONENT_ALARM_AUDIO:
483 audio_notification (trigger, cqa, alarm_id);
484 break;
485
486 case E_CAL_COMPONENT_ALARM_DISPLAY:
487 #ifdef HAVE_LIBNOTIFY
488 popup_notification (trigger, cqa, alarm_id, TRUE);
489 #endif
490 display_notification (trigger, cqa, alarm_id, TRUE);
491 break;
492
493 case E_CAL_COMPONENT_ALARM_EMAIL:
494 mail_notification (trigger, cqa, alarm_id);
495 break;
496
497 case E_CAL_COMPONENT_ALARM_PROCEDURE:
498 procedure_notification (trigger, cqa, alarm_id);
499 break;
500
501 default:
502 g_return_if_reached ();
503 }
504 debug (("Notification sent: %d", action));
505 }
506
507 /* Adds the alarms in a ECalComponentAlarms structure to the alarms queued for a
508 * particular client. Also puts the triggers in the alarm timer queue.
509 */
510 static void
511 add_component_alarms (ClientAlarms *ca,
512 ECalComponentAlarms *alarms)
513 {
514 ECalComponentId *id;
515 CompQueuedAlarms *cqa;
516 GSList *l;
517
518 /* No alarms? */
519 if (alarms == NULL || alarms->alarms == NULL) {
520 debug (("No alarms to add"));
521 if (alarms)
522 e_cal_component_alarms_free (alarms);
523 return;
524 }
525
526 cqa = g_new (CompQueuedAlarms, 1);
527 cqa->parent_client = ca;
528 cqa->alarms = alarms;
529 cqa->expecting_update = FALSE;
530
531 cqa->queued_alarms = NULL;
532 debug (("Creating CQA %p", cqa));
533
534 for (l = alarms->alarms; l; l = l->next) {
535 ECalComponentAlarmInstance *instance;
536 gpointer alarm_id;
537 QueuedAlarm *qa;
538
539 instance = l->data;
540
541 if (!has_known_notification (cqa->alarms->comp, instance->auid))
542 continue;
543
544 alarm_id = alarm_add (
545 instance->trigger, alarm_trigger_cb, cqa, NULL);
546 if (!alarm_id)
547 continue;
548
549 qa = g_new (QueuedAlarm, 1);
550 qa->alarm_id = alarm_id;
551 qa->instance = instance;
552 qa->orig_trigger = instance->trigger;
553 qa->snooze = FALSE;
554
555 cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
556 }
557
558 id = e_cal_component_get_id (alarms->comp);
559
560 /* If we failed to add all the alarms, then we should get rid of the cqa */
561 if (cqa->queued_alarms == NULL) {
562 e_cal_component_alarms_free (cqa->alarms);
563 cqa->alarms = NULL;
564 debug (("Failed to add all : %p", cqa));
565 g_free (cqa);
566 return;
567 }
568
569 cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
570 cqa->id = id;
571 debug (("Alarm added for %s", id->uid));
572 g_hash_table_insert (ca->uid_alarms_hash, cqa->id, cqa);
573 }
574
575 /* Loads the alarms of a client for a given range of time */
576 static void
577 load_alarms (ClientAlarms *ca,
578 time_t start,
579 time_t end)
580 {
581 gchar *str_query, *iso_start, *iso_end;
582 GError *error = NULL;
583
584 debug (("..."));
585
586 iso_start = isodate_from_time_t (start);
587 if (!iso_start)
588 return;
589
590 iso_end = isodate_from_time_t (end);
591 if (!iso_end) {
592 g_free (iso_start);
593 return;
594 }
595
596 str_query = g_strdup_printf (
597 "(has-alarms-in-range? (make-time \"%s\") "
598 "(make-time \"%s\"))", iso_start, iso_end);
599 g_free (iso_start);
600 g_free (iso_end);
601
602 /* create the live query */
603 if (ca->view) {
604 debug (("Disconnecting old queries"));
605 g_signal_handlers_disconnect_matched (
606 ca->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, ca);
607 g_object_unref (ca->view);
608 ca->view = NULL;
609 }
610
611 e_cal_client_get_view_sync (
612 ca->cal_client, str_query, &ca->view, NULL, &error);
613
614 if (error != NULL) {
615 g_warning (
616 "%s: Could not get query for client: %s",
617 G_STRFUNC, error->message);
618 g_error_free (error);
619 } else {
620 debug (("Setting Call backs"));
621
622 g_signal_connect (
623 ca->view, "objects-added",
624 G_CALLBACK (query_objects_modified_cb), ca);
625 g_signal_connect (
626 ca->view, "objects-modified",
627 G_CALLBACK (query_objects_modified_cb), ca);
628 g_signal_connect (
629 ca->view, "objects-removed",
630 G_CALLBACK (query_objects_removed_cb), ca);
631
632 e_cal_client_view_start (ca->view, &error);
633
634 if (error != NULL) {
635 g_warning (
636 "%s: Failed to start view: %s",
637 G_STRFUNC, error->message);
638 g_error_free (error);
639 }
640 }
641
642 g_free (str_query);
643 }
644
645 /* Loads today's remaining alarms for a client */
646 static void
647 load_alarms_for_today (ClientAlarms *ca)
648 {
649 time_t now, from, day_end, day_start;
650 icaltimezone *zone;
651
652 now = time (NULL);
653 zone = config_data_get_timezone ();
654 day_start = time_day_begin_with_zone (now, zone);
655
656 /* Make sure we don't miss some events from the last notification.
657 * We add 1 to the saved notification time to make the time ranges
658 * half-open; we do not want to display the "last" displayed alarm
659 * twice, once when it occurs and once when the alarm daemon restarts.
660 */
661 from = config_data_get_last_notification_time (ca->cal_client) + 1;
662 if (from <= 0)
663 from = MAX (from, day_start);
664
665 /* Add one hour after midnight, just to cover the delay in 30 minutes
666 * midnight checking. */
667 day_end = time_day_end_with_zone (now, zone) + (60 * 60);
668 debug (("From %s to %s", e_ctime (&from), e_ctime (&day_end)));
669 load_alarms (ca, from, day_end);
670 }
671
672 /* Looks up a component's queued alarm structure in a client alarms structure */
673 static CompQueuedAlarms *
674 lookup_comp_queued_alarms (ClientAlarms *ca,
675 const ECalComponentId *id)
676 {
677 return g_hash_table_lookup (ca->uid_alarms_hash, id);
678 }
679
680 static void
681 remove_alarms (CompQueuedAlarms *cqa,
682 gboolean free_object)
683 {
684 GSList *l;
685
686 debug (("Removing for %p", cqa));
687 for (l = cqa->queued_alarms; l;) {
688 QueuedAlarm *qa;
689
690 qa = l->data;
691
692 /* Get the next element here because the list element will go
693 * away in remove_queued_alarm(). The qa will be freed there as
694 * well.
695 */
696 l = l->next;
697
698 alarm_remove (qa->alarm_id);
699 remove_queued_alarm (cqa, qa->alarm_id, free_object, FALSE);
700 }
701 }
702
703 /* Removes a component an its alarms */
704 static void
705 remove_comp (ClientAlarms *ca,
706 ECalComponentId *id)
707 {
708 CompQueuedAlarms *cqa;
709
710 debug (("Removing uid %s", id->uid));
711
712 if (id->rid && !(*(id->rid))) {
713 g_free (id->rid);
714 id->rid = NULL;
715 }
716
717 cqa = lookup_comp_queued_alarms (ca, id);
718 if (!cqa)
719 return;
720
721 /* If a component is present, then it means we must have alarms queued
722 * for it.
723 */
724 g_return_if_fail (cqa->queued_alarms != NULL);
725
726 debug (("Removing CQA %p", cqa));
727 remove_alarms (cqa, TRUE);
728 }
729
730 /* Called when a calendar component changes; we must reload its corresponding
731 * alarms.
732 */
733 struct _query_msg {
734 Message header;
735 GSList *objects;
736 gpointer data;
737 };
738
739 static GSList *
740 duplicate_ical (const GSList *in_list)
741 {
742 const GSList *l;
743 GSList *out_list = NULL;
744 for (l = in_list; l; l = l->next) {
745 out_list = g_slist_prepend (
746 out_list, icalcomponent_new_clone (l->data));
747 }
748
749 return g_slist_reverse (out_list);
750 }
751
752 static GSList *
753 duplicate_ecal (const GSList *in_list)
754 {
755 const GSList *l;
756 GSList *out_list = NULL;
757 for (l = in_list; l; l = l->next) {
758 ECalComponentId *id, *old;
759 old = l->data;
760 id = g_new0 (ECalComponentId, 1);
761 id->uid = g_strdup (old->uid);
762 id->rid = g_strdup (old->rid);
763 out_list = g_slist_prepend (out_list, id);
764 }
765
766 return g_slist_reverse (out_list);
767 }
768
769 static gboolean
770 get_alarms_for_object (ECalClient *cal_client,
771 const ECalComponentId *id,
772 time_t start,
773 time_t end,
774 ECalComponentAlarms **alarms)
775 {
776 icalcomponent *icalcomp;
777 ECalComponent *comp;
778 ECalComponentAlarmAction omit[] = {-1};
779
780 g_return_val_if_fail (cal_client != NULL, FALSE);
781 g_return_val_if_fail (id != NULL, FALSE);
782 g_return_val_if_fail (alarms != NULL, FALSE);
783 g_return_val_if_fail (start >= 0 && end >= 0, FALSE);
784 g_return_val_if_fail (start <= end, FALSE);
785
786 if (!e_cal_client_get_object_sync (
787 cal_client, id->uid, id->rid, &icalcomp, NULL, NULL))
788 return FALSE;
789
790 if (!icalcomp)
791 return FALSE;
792
793 comp = e_cal_component_new ();
794 if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
795 icalcomponent_free (icalcomp);
796 g_object_unref (comp);
797 return FALSE;
798 }
799
800 *alarms = e_cal_util_generate_alarms_for_comp (
801 comp, start, end, omit, e_cal_client_resolve_tzid_cb,
802 cal_client, e_cal_client_get_default_timezone (cal_client));
803
804 g_object_unref (comp);
805
806 return TRUE;
807 }
808
809 static void
810 query_objects_changed_async (struct _query_msg *msg)
811 {
812 ClientAlarms *ca;
813 time_t from, day_end;
814 ECalComponentAlarms *alarms;
815 gboolean found;
816 icaltimezone *zone;
817 CompQueuedAlarms *cqa;
818 GSList *l;
819 GSList *objects;
820
821 ca = msg->data;
822 objects = msg->objects;
823
824 from = config_data_get_last_notification_time (ca->cal_client);
825 if (from == -1)
826 from = time (NULL);
827 else
828 from += 1; /* we add 1 to make sure the alarm is not displayed twice */
829
830 zone = config_data_get_timezone ();
831
832 day_end = time_day_end_with_zone (time (NULL), zone);
833
834 for (l = objects; l != NULL; l = l->next) {
835 ECalComponentId *id;
836 GSList *sl;
837 ECalComponent *comp = e_cal_component_new ();
838
839 e_cal_component_set_icalcomponent (comp, l->data);
840
841 id = e_cal_component_get_id (comp);
842 found = get_alarms_for_object (ca->cal_client, id, from, day_end, &alarms);
843
844 if (!found) {
845 debug (("No Alarm found for client %p", ca->cal_client));
846 tray_list_remove_cqa (lookup_comp_queued_alarms (ca, id));
847 remove_comp (ca, id);
848 g_hash_table_remove (ca->uid_alarms_hash, id);
849 e_cal_component_free_id (id);
850 g_object_unref (comp);
851 comp = NULL;
852 continue;
853 }
854
855 cqa = lookup_comp_queued_alarms (ca, id);
856 if (!cqa) {
857 debug (("No currently queued alarms for %s", id->uid));
858 add_component_alarms (ca, alarms);
859 g_object_unref (comp);
860 comp = NULL;
861 continue;
862 }
863
864 debug (("Alarm Already Exist for %s", id->uid));
865 /* If the alarms or the alarms list is empty,
866 * remove it after updating the cqa structure. */
867 if (alarms == NULL || alarms->alarms == NULL) {
868
869 /* Update the cqa and its queued alarms
870 * for changes in summary and alarm_uid. */
871 update_cqa (cqa, comp);
872
873 if (alarms)
874 e_cal_component_alarms_free (alarms);
875 continue;
876 }
877
878 /* if already in the list, just update it */
879 remove_alarms (cqa, FALSE);
880 cqa->alarms = alarms;
881 cqa->queued_alarms = NULL;
882
883 /* add the new alarms */
884 for (sl = cqa->alarms->alarms; sl; sl = sl->next) {
885 ECalComponentAlarmInstance *instance;
886 gpointer alarm_id;
887 QueuedAlarm *qa;
888
889 instance = sl->data;
890
891 if (!has_known_notification (cqa->alarms->comp, instance->auid))
892 continue;
893
894 alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, NULL);
895 if (!alarm_id)
896 continue;
897
898 qa = g_new (QueuedAlarm, 1);
899 qa->alarm_id = alarm_id;
900 qa->instance = instance;
901 qa->snooze = FALSE;
902 qa->orig_trigger = instance->trigger;
903 cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
904 debug (("Adding %p to queue", qa));
905 }
906
907 cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
908 g_object_unref (comp);
909 comp = NULL;
910 }
911 g_slist_free (objects);
912
913 g_slice_free (struct _query_msg, msg);
914 }
915
916 static void
917 query_objects_modified_cb (ECalClientView *view,
918 const GSList *objects,
919 gpointer data)
920 {
921 struct _query_msg *msg;
922
923 msg = g_slice_new0 (struct _query_msg);
924 msg->header.func = (MessageFunc) query_objects_changed_async;
925 msg->objects = duplicate_ical (objects);
926 msg->data = data;
927
928 message_push ((Message *) msg);
929 }
930
931 /* Called when a calendar component is removed; we must delete its corresponding
932 * alarms.
933 */
934 static void
935 query_objects_removed_async (struct _query_msg *msg)
936 {
937 ClientAlarms *ca;
938 GSList *l;
939 GSList *objects;
940
941 ca = msg->data;
942 objects = msg->objects;
943
944 debug (("Removing %d objects", g_slist_length (objects)));
945
946 for (l = objects; l != NULL; l = l->next) {
947 /* If the alarm is already triggered remove it. */
948 tray_list_remove_cqa (lookup_comp_queued_alarms (ca, l->data));
949 remove_comp (ca, l->data);
950 g_hash_table_remove (ca->uid_alarms_hash, l->data);
951 e_cal_component_free_id (l->data);
952 }
953
954 g_slist_free (objects);
955
956 g_slice_free (struct _query_msg, msg);
957 }
958
959 static void
960 query_objects_removed_cb (ECalClientView *view,
961 const GSList *uids,
962 gpointer data)
963 {
964 struct _query_msg *msg;
965
966 msg = g_slice_new0 (struct _query_msg);
967 msg->header.func = (MessageFunc) query_objects_removed_async;
968 msg->objects = duplicate_ecal (uids);
969 msg->data = data;
970
971 message_push ((Message *) msg);
972 }
973
974 /* Notification functions */
975
976 /* Creates a snooze alarm based on an existing one. The snooze offset is
977 * compued with respect to the current time.
978 */
979 static void
980 create_snooze (CompQueuedAlarms *cqa,
981 gpointer alarm_id,
982 gint snooze_mins)
983 {
984 QueuedAlarm *orig_qa;
985 time_t t;
986 gpointer new_id;
987
988 orig_qa = lookup_queued_alarm (cqa, alarm_id);
989 if (!orig_qa)
990 return;
991
992 t = time (NULL);
993 t += snooze_mins * 60;
994
995 new_id = alarm_add (t, alarm_trigger_cb, cqa, NULL);
996 if (!new_id) {
997 debug (("Unable to schedule trigger for %s", e_ctime (&t)));
998 return;
999 }
1000
1001 orig_qa->instance->trigger = t;
1002 orig_qa->alarm_id = new_id;
1003 orig_qa->snooze = TRUE;
1004 debug (("Adding a alarm at %s", e_ctime (&t)));
1005 }
1006
1007 /* Launches a component editor for a component */
1008 static void
1009 edit_component (ECalClient *cal_client,
1010 ECalComponent *comp)
1011 {
1012 ESource *source;
1013 gchar *command_line;
1014 const gchar *scheme;
1015 const gchar *comp_uid;
1016 const gchar *source_uid;
1017 GError *error = NULL;
1018
1019 /* XXX Don't we have a function to construct these URIs?
1020 * How are other apps expected to know this stuff? */
1021
1022 source = e_client_get_source (E_CLIENT (cal_client));
1023 source_uid = e_source_get_uid (source);
1024
1025 e_cal_component_get_uid (comp, &comp_uid);
1026
1027 switch (e_cal_client_get_source_type (cal_client)) {
1028 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
1029 scheme = "calendar:";
1030 break;
1031 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
1032 scheme = "task:";
1033 break;
1034 case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
1035 scheme = "memo:";
1036 break;
1037 default:
1038 g_return_if_reached ();
1039 }
1040
1041 command_line = g_strdup_printf (
1042 "%s %s///?source-uid=%s&comp-uid=%s",
1043 PACKAGE, scheme, source_uid, comp_uid);
1044
1045 if (!g_spawn_command_line_async (command_line, &error)) {
1046 g_critical ("%s", error->message);
1047 g_error_free (error);
1048 }
1049
1050 g_free (command_line);
1051 }
1052
1053 static void
1054 print_component (ECalClient *cal_client,
1055 ECalComponent *comp)
1056 {
1057 print_comp (
1058 comp,
1059 cal_client,
1060 config_data_get_timezone (),
1061 config_data_get_24_hour_format (),
1062 GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
1063 }
1064
1065 typedef struct {
1066 gchar *summary;
1067 gchar *description;
1068 gchar *location;
1069 gboolean blink_state;
1070 gboolean snooze_set;
1071 gint blink_id;
1072 time_t trigger;
1073 CompQueuedAlarms *cqa;
1074 gpointer alarm_id;
1075 ECalComponent *comp;
1076 ECalClient *cal_client;
1077 ECalClientView *view;
1078 GdkPixbuf *image;
1079 GtkTreeIter iter;
1080 } TrayIconData;
1081
1082 static void
1083 free_tray_icon_data (TrayIconData *tray_data)
1084 {
1085 g_return_if_fail (tray_data != NULL);
1086
1087 if (tray_data->summary) {
1088 g_free (tray_data->summary);
1089 tray_data->summary = NULL;
1090 }
1091
1092 if (tray_data->description) {
1093 g_free (tray_data->description);
1094 tray_data->description = NULL;
1095 }
1096
1097 if (tray_data->location) {
1098 g_free (tray_data->location);
1099 tray_data->location = NULL;
1100 }
1101
1102 g_object_unref (tray_data->cal_client);
1103 tray_data->cal_client = NULL;
1104
1105 g_signal_handlers_disconnect_matched (
1106 tray_data->view, G_SIGNAL_MATCH_FUNC,
1107 0, 0, NULL, on_dialog_objs_removed_cb, NULL);
1108 g_object_unref (tray_data->view);
1109 tray_data->view = NULL;
1110
1111 g_object_unref (tray_data->comp);
1112 tray_data->comp = NULL;
1113
1114 tray_data->cqa = NULL;
1115 tray_data->alarm_id = NULL;
1116 tray_data->image = NULL;
1117
1118 g_free (tray_data);
1119 }
1120
1121 static void
1122 on_dialog_objs_removed_async (struct _query_msg *msg)
1123 {
1124 TrayIconData *tray_data;
1125 GSList *l, *objects;
1126 ECalComponentId *our_id;
1127
1128 debug (("..."));
1129
1130 tray_data = msg->data;
1131 objects = msg->objects;
1132
1133 our_id = e_cal_component_get_id (tray_data->comp);
1134 g_return_if_fail (our_id);
1135
1136 for (l = objects; l != NULL; l = l->next) {
1137 ECalComponentId *id = l->data;
1138
1139 if (!id)
1140 continue;
1141
1142 if (g_strcmp0 (id->uid, our_id->uid) == 0 &&
1143 g_strcmp0 (id->rid, our_id->rid) == 0) {
1144 tray_data->cqa = NULL;
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
1145 tray_data->alarm_id = NULL;
1146 tray_icons_list = g_list_remove (
1147 tray_icons_list, tray_data);
1148 tray_data = NULL;
1149 }
1150
1151 e_cal_component_free_id (id);
1152 }
1153
1154 e_cal_component_free_id (our_id);
1155 g_slist_free (objects);
1156 g_slice_free (struct _query_msg, msg);
1157 }
1158
1159 static void
1160 on_dialog_objs_removed_cb (ECalClientView *view,
1161 const GSList *uids,
1162 gpointer data)
1163 {
1164 struct _query_msg *msg;
1165
1166 msg = g_slice_new0 (struct _query_msg);
1167 msg->header.func = (MessageFunc) on_dialog_objs_removed_async;
1168 msg->objects = duplicate_ecal (uids);
1169 msg->data = data;
1170
1171 message_push ((Message *) msg);
1172 }
1173
1174 struct _tray_cqa_msg {
1175 Message header;
1176 CompQueuedAlarms *cqa;
1177 };
1178
1179 static void
1180 tray_list_remove_cqa_async (struct _tray_cqa_msg *msg)
1181 {
1182 CompQueuedAlarms *cqa = msg->cqa;
1183 GList *list = tray_icons_list;
1184
1185 debug (("Removing CQA %p from tray list", cqa));
1186
1187 while (list) {
1188 TrayIconData *tray_data = list->data;
1189 GList *tmp = list;
1190 GtkTreeModel *model;
1191
1192 list = list->next;
1193 if (tray_data->cqa == cqa) {
1194 debug (("Found"));
1195 tray_icons_list = g_list_delete_link (tray_icons_list, tmp);
1196 if (alarm_notifications_dialog) {
1197 model = gtk_tree_view_get_model (
1198 GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
1199 gtk_list_store_remove (GTK_LIST_STORE (model), &(tray_data->iter));
1200 }
1201 free_tray_icon_data (tray_data);
1202 }
1203 }
1204
1205 debug (("%d alarms left", g_list_length (tray_icons_list)));
1206
1207 if (alarm_notifications_dialog) {
1208 if (!g_list_length (tray_icons_list)) {
1209 gtk_widget_destroy (alarm_notifications_dialog->dialog);
1210 g_free (alarm_notifications_dialog);
1211 alarm_notifications_dialog = NULL;
1212 } else {
1213 GtkTreeIter iter;
1214 GtkTreeModel *model;
1215 GtkTreeSelection *sel;
1216
1217 model = gtk_tree_view_get_model (
1218 GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
1219 gtk_tree_model_get_iter_first (model, &iter);
1220 sel = gtk_tree_view_get_selection (
1221 GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
1222 gtk_tree_selection_select_iter (sel, &iter);
1223 }
1224 }
1225
1226 g_slice_free (struct _tray_cqa_msg, msg);
1227 }
1228
1229 static void
1230 tray_list_remove_cqa (CompQueuedAlarms *cqa)
1231 {
1232 struct _tray_cqa_msg *msg;
1233
1234 msg = g_slice_new0 (struct _tray_cqa_msg);
1235 msg->header.func = (MessageFunc) tray_list_remove_cqa_async;
1236 msg->cqa = cqa;
1237
1238 message_push ((Message *) msg);
1239 }
1240
1241 /* Callback used from the alarm notify dialog */
1242 static void
1243 tray_list_remove_async (Message *msg)
1244 {
1245 GList *list = tray_icons_list;
1246
1247 debug (("Removing %d alarms", g_list_length (list)));
1248 while (list != NULL) {
1249
1250 TrayIconData *tray_data = list->data;
1251
1252 if (!tray_data->snooze_set) {
1253 GList *temp = list->next;
1254 gboolean status;
1255
1256 tray_icons_list = g_list_remove_link (tray_icons_list, list);
1257 status = remove_queued_alarm (
1258 tray_data->cqa,
1259 tray_data->alarm_id, FALSE, TRUE);
1260 if (status) {
1261 g_hash_table_remove (
1262 tray_data->cqa->parent_client->uid_alarms_hash,
1263 tray_data->cqa->id);
1264 e_cal_component_free_id (tray_data->cqa->id);
1265 g_free (tray_data->cqa);
1266 }
1267 free_tray_icon_data (tray_data);
1268 tray_data = NULL;
1269 g_list_free_1 (list);
1270 if (tray_icons_list != list) /* List head is modified */
1271 list = tray_icons_list;
1272 else
1273 list = temp;
1274 } else
1275 list = list->next;
1276 }
1277
1278 g_slice_free (Message, msg);
1279 }
1280
1281 static void
1282 tray_list_remove_icons (void)
1283 {
1284 Message *msg;
1285
1286 msg = g_slice_new0 (Message);
1287 msg->func = tray_list_remove_async;
1288
1289 message_push (msg);
1290 }
1291
1292 struct _tray_msg {
1293 Message header;
1294 TrayIconData *data;
1295 };
1296
1297 static void
1298 tray_list_remove_data_async (struct _tray_msg *msg)
1299 {
1300 TrayIconData *tray_data = msg->data;
1301
1302 debug (("Removing %p from tray list", tray_data));
1303
1304 tray_icons_list = g_list_remove_all (tray_icons_list, tray_data);
1305 free_tray_icon_data (tray_data);
1306 tray_data = NULL;
1307
1308 g_slice_free (struct _tray_msg, msg);
1309 }
1310
1311 static void
1312 tray_list_remove_data (TrayIconData *data)
1313 {
1314 struct _tray_msg *msg;
1315
1316 msg = g_slice_new0 (struct _tray_msg);
1317 msg->header.func = (MessageFunc) tray_list_remove_data_async;
1318 msg->data = data;
1319
1320 message_push ((Message *) msg);
1321 }
1322
1323 static void
1324 notify_dialog_cb (AlarmNotifyResult result,
1325 gint snooze_mins,
1326 gpointer data)
1327 {
1328 TrayIconData *tray_data = data;
1329
1330 debug (("Received from dialog"));
1331
1332 g_signal_handlers_disconnect_matched (
1333 tray_data->view, G_SIGNAL_MATCH_FUNC,
1334 0, 0, NULL, on_dialog_objs_removed_cb, NULL);
1335
1336 switch (result) {
1337 case ALARM_NOTIFY_SNOOZE:
1338 debug (("Creating a snooze"));
1339 create_snooze (tray_data->cqa, tray_data->alarm_id, snooze_mins);
1340 tray_data->snooze_set = TRUE;
1341 tray_list_remove_data (tray_data);
1342 if (alarm_notifications_dialog) {
1343 GtkTreeSelection *selection =
1344 gtk_tree_view_get_selection (
1345 GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
1346 GtkTreeIter iter;
1347 GtkTreeModel *model = NULL;
1348
1349 /* We can also use tray_data->iter */
1350 if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1351 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
1352 if (!gtk_tree_model_get_iter_first (model, &iter)) {
1353 /* We removed the last one */
1354 gtk_widget_destroy (alarm_notifications_dialog->dialog);
1355 g_free (alarm_notifications_dialog);
1356 alarm_notifications_dialog = NULL;
1357 } else {
1358 /* Select the first */
1359 gtk_tree_selection_select_iter (selection, &iter);
1360 }
1361 }
1362
1363 }
1364
1365 break;
1366
1367 case ALARM_NOTIFY_EDIT:
1368 edit_component (tray_data->cal_client, tray_data->comp);
1369
1370 break;
1371
1372 case ALARM_NOTIFY_PRINT:
1373 print_component (tray_data->cal_client, tray_data->comp);
1374
1375 break;
1376
1377 case ALARM_NOTIFY_DISMISS:
1378 if (alarm_notifications_dialog) {
1379 GtkTreeModel *model;
1380
1381 model = gtk_tree_view_get_model (
1382 GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
1383 gtk_list_store_remove (GTK_LIST_STORE (model), &tray_data->iter);
1384 }
1385 break;
1386
1387 case ALARM_NOTIFY_CLOSE:
1388 debug (("Dialog close"));
1389 if (alarm_notifications_dialog) {
1390 GtkTreeIter iter;
1391 GtkTreeModel *model =
1392 gtk_tree_view_get_model (
1393 GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
1394 gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
1395
1396 /* Maybe we should warn about this first? */
1397 while (valid) {
1398 valid = gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
1399 }
1400
1401 gtk_widget_destroy (alarm_notifications_dialog->dialog);
1402 g_free (alarm_notifications_dialog);
1403 alarm_notifications_dialog = NULL;
1404
1405 /* Task to remove the tray icons */
1406 tray_list_remove_icons ();
1407 }
1408
1409 break;
1410
1411 default:
1412 g_return_if_reached ();
1413 }
1414
1415 return;
1416 }
1417
1418 static void
1419 remove_tray_icon (void)
1420 {
1421 if (tray_blink_id > -1)
1422 g_source_remove (tray_blink_id);
1423 tray_blink_id = -1;
1424
1425 if (tray_icon) {
1426 gtk_status_icon_set_visible (tray_icon, FALSE);
1427 g_object_unref (tray_icon);
1428 tray_icon = NULL;
1429 }
1430 }
1431
1432 /* Callbacks. */
1433 static gboolean
1434 open_alarm_dialog (TrayIconData *tray_data)
1435 {
1436 QueuedAlarm *qa;
1437
1438 debug (("..."));
1439 qa = lookup_queued_alarm (tray_data->cqa, tray_data->alarm_id);
1440 if (qa) {
1441 remove_tray_icon ();
1442
1443 if (!alarm_notifications_dialog)
1444 alarm_notifications_dialog = notified_alarms_dialog_new ();
1445
1446 if (alarm_notifications_dialog) {
1447
1448 GtkTreeSelection *selection = NULL;
1449
1450 selection = gtk_tree_view_get_selection (
1451 GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
1452
1453 tray_data->iter = add_alarm_to_notified_alarms_dialog (
1454 alarm_notifications_dialog,
1455 tray_data->trigger,
1456 qa->instance->occur_start,
1457 qa->instance->occur_end,
1458 e_cal_component_get_vtype (tray_data->comp),
1459 tray_data->summary,
1460 tray_data->description,
1461 tray_data->location,
1462 notify_dialog_cb, tray_data);
1463
1464 gtk_tree_selection_select_iter (
1465 selection, &tray_data->iter);
1466
1467 gtk_window_present (GTK_WINDOW (alarm_notifications_dialog->dialog));
1468 }
1469
1470 } else {
1471 remove_tray_icon ();
1472 }
1473
1474 return TRUE;
1475 }
1476
1477 static gint
1478 tray_icon_clicked_cb (GtkWidget *widget,
1479 GdkEventButton *event,
1480 gpointer user_data)
1481 {
1482 if (event->type == GDK_BUTTON_PRESS) {
1483 debug (("left click and %d alarms", g_list_length (tray_icons_list)));
1484 if (event->button == 1 && g_list_length (tray_icons_list) > 0) {
1485 GList *tmp;
1486 for (tmp = tray_icons_list; tmp; tmp = tmp->next) {
1487 open_alarm_dialog (tmp->data);
1488 }
1489
1490 return TRUE;
1491 } else if (event->button == 3) {
1492 debug (("right click"));
1493
1494 remove_tray_icon ();
1495 return TRUE;
1496 }
1497 }
1498
1499 return FALSE;
1500 }
1501
1502 static void
1503 icon_activated (GtkStatusIcon *icon)
1504 {
1505 GdkEventButton event;
1506
1507 event.type = GDK_BUTTON_PRESS;
1508 event.button = 1;
1509 event.time = gtk_get_current_event_time ();
1510
1511 tray_icon_clicked_cb (NULL, &event, NULL);
1512 }
1513
1514 static void
1515 popup_menu (GtkStatusIcon *icon,
1516 guint button,
1517 guint activate_time)
1518 {
1519 if (button == 3) {
1520 /* right click */
1521 GdkEventButton event;
1522
1523 event.type = GDK_BUTTON_PRESS;
1524 event.button = 3;
1525 event.time = gtk_get_current_event_time ();
1526
1527 tray_icon_clicked_cb (NULL, &event, NULL);
1528 }
1529 }
1530
1531 static gboolean
1532 tray_icon_blink_cb (gpointer data)
1533 {
1534 static gboolean tray_blink_state = FALSE;
1535 const gchar *icon_name;
1536
1537 tray_blink_countdown--;
1538 tray_blink_state = !tray_blink_state;
1539
1540 if (tray_blink_state || tray_blink_countdown <= 0)
1541 icon_name = "appointment-missed";
1542 else
1543 icon_name = "appointment-soon";
1544
1545 if (tray_icon)
1546 gtk_status_icon_set_from_icon_name (tray_icon, icon_name);
1547
1548 if (tray_blink_countdown <= 0)
1549 tray_blink_id = -1;
1550
1551 return tray_blink_countdown > 0;
1552 }
1553
1554 /* Add a new data to tray list */
1555
1556 static void
1557 tray_list_add_async (struct _tray_msg *msg)
1558 {
1559 tray_icons_list = g_list_prepend (tray_icons_list, msg->data);
1560
1561 g_slice_free (struct _tray_msg, msg);
1562 }
1563
1564 static void
1565 tray_list_add_new (TrayIconData *data)
1566 {
1567 struct _tray_msg *msg;
1568
1569 msg = g_slice_new0 (struct _tray_msg);
1570 msg->header.func = (MessageFunc) tray_list_add_async;
1571 msg->data = data;
1572
1573 message_push ((Message *) msg);
1574 }
1575
1576 /* Performs notification of a display alarm */
1577 static void
1578 display_notification (time_t trigger,
1579 CompQueuedAlarms *cqa,
1580 gpointer alarm_id,
1581 gboolean use_description)
1582 {
1583 QueuedAlarm *qa;
1584 ECalComponent *comp;
1585 const gchar *summary, *description, *location;
1586 TrayIconData *tray_data;
1587 ECalComponentText text;
1588 GSList *text_list;
1589 gchar *str, *start_str, *end_str, *alarm_str, *time_str;
1590 icaltimezone *current_zone;
1591 ECalComponentOrganizer organiser;
1592
1593 debug (("..."));
1594
1595 comp = cqa->alarms->comp;
1596 qa = lookup_queued_alarm (cqa, alarm_id);
1597 if (!qa)
1598 return;
1599
1600 /* get a sensible description for the event */
1601 e_cal_component_get_summary (comp, &text);
1602 e_cal_component_get_organizer (comp, &organiser);
1603
1604 if (text.value)
1605 summary = text.value;
1606 else
1607 summary = _("No summary available.");
1608
1609 e_cal_component_get_description_list (comp, &text_list);
1610
1611 if (text_list) {
1612 text = *((ECalComponentText *) text_list->data);
1613 if (text.value)
1614 description = text.value;
1615 else
1616 description = _("No description available.");
1617 } else {
1618 description = _("No description available.");
1619 }
1620
1621 e_cal_component_free_text_list (text_list);
1622
1623 e_cal_component_get_location (comp, &location);
1624
1625 if (!location)
1626 location = _("No location information available.");
1627
1628 /* create the tray icon */
1629 if (tray_icon == NULL) {
1630 tray_icon = gtk_status_icon_new ();
1631 gtk_status_icon_set_from_icon_name (
1632 tray_icon, "appointment-soon");
1633 g_signal_connect (
1634 tray_icon, "activate",
1635 G_CALLBACK (icon_activated), NULL);
1636 g_signal_connect (
1637 tray_icon, "popup-menu",
1638 G_CALLBACK (popup_menu), NULL);
1639 }
1640
1641 current_zone = config_data_get_timezone ();
1642 alarm_str = timet_to_str_with_zone (trigger, current_zone);
1643 start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone);
1644 end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone);
1645 time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end);
1646
1647 str = g_strdup_printf (
1648 "%s\n%s %s",
1649 summary, start_str, time_str);
1650
1651 /* create the private structure */
1652 tray_data = g_new0 (TrayIconData, 1);
1653 tray_data->summary = g_strdup (summary);
1654 tray_data->description = g_strdup (description);
1655 tray_data->location = g_strdup (location);
1656 tray_data->trigger = trigger;
1657 tray_data->cqa = cqa;
1658 tray_data->alarm_id = alarm_id;
1659 tray_data->comp = g_object_ref (e_cal_component_clone (comp));
1660 tray_data->cal_client = cqa->parent_client->cal_client;
1661 tray_data->view = g_object_ref (cqa->parent_client->view);
1662 tray_data->blink_state = FALSE;
1663 tray_data->snooze_set = FALSE;
1664 g_object_ref (tray_data->cal_client);
1665
1666 /* Task to add tray_data to the global tray_icon_list */
1667 tray_list_add_new (tray_data);
1668
1669 if (g_list_length (tray_icons_list) > 1) {
1670 gchar *tip;
1671
1672 tip = g_strdup_printf (ngettext (
1673 "You have %d reminder", "You have %d reminders",
1674 g_list_length (tray_icons_list)),
1675 g_list_length (tray_icons_list));
1676 gtk_status_icon_set_tooltip_text (tray_icon, tip);
1677 }
1678 else {
1679 gtk_status_icon_set_tooltip_text (tray_icon, str);
1680 }
1681
1682 g_free (start_str);
1683 g_free (end_str);
1684 g_free (alarm_str);
1685 g_free (time_str);
1686 g_free (str);
1687
1688 g_signal_connect (
1689 tray_data->view, "objects_removed",
1690 G_CALLBACK (on_dialog_objs_removed_cb), tray_data);
1691
1692 /* FIXME: We should remove this check */
1693 if (!config_data_get_notify_with_tray ()) {
1694 tray_blink_id = -1;
1695 open_alarm_dialog (tray_data);
1696 if (alarm_notifications_dialog)
1697 gtk_window_stick (GTK_WINDOW (
1698 alarm_notifications_dialog->dialog));
1699 } else {
1700 if (tray_blink_id == -1) {
1701 tray_blink_countdown = 30;
1702 tray_blink_id = g_timeout_add (500, tray_icon_blink_cb, tray_data);
1703 }
1704 }
1705 }
1706
1707 #ifdef HAVE_LIBNOTIFY
1708 static void
1709 popup_notification (time_t trigger,
1710 CompQueuedAlarms *cqa,
1711 gpointer alarm_id,
1712 gboolean use_description)
1713 {
1714 QueuedAlarm *qa;
1715 ECalComponent *comp;
1716 const gchar *summary, *location;
1717 ECalComponentText text;
1718 gchar *str, *start_str, *end_str, *alarm_str, *time_str;
1719 icaltimezone *current_zone;
1720 ECalComponentOrganizer organiser;
1721 NotifyNotification *n;
1722 gchar *body;
1723
1724 debug (("..."));
1725
1726 comp = cqa->alarms->comp;
1727 qa = lookup_queued_alarm (cqa, alarm_id);
1728 if (!qa)
1729 return;
1730 if (!notify_is_initted ())
1731 notify_init ("Evolution Alarm Notify");
1732
1733 /* get a sensible description for the event */
1734 e_cal_component_get_summary (comp, &text);
1735 e_cal_component_get_organizer (comp, &organiser);
1736
1737 if (text.value)
1738 summary = text.value;
1739 else
1740 summary = _("No summary available.");
1741
1742 e_cal_component_get_location (comp, &location);
1743
1744 /* create the tray icon */
1745
1746 current_zone = config_data_get_timezone ();
1747 alarm_str = timet_to_str_with_zone (trigger, current_zone);
1748 start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone);
1749 end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone);
1750 time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end);
1751
1752 str = g_strdup_printf (
1753 "%s %s",
1754 start_str, time_str);
1755
1756 if (organiser.cn) {
1757 if (location)
1758 body = g_strdup_printf (
1759 "<b>%s</b>\n%s %s\n%s %s",
1760 organiser.cn, _("Location:"),
1761 location, start_str, time_str);
1762 else
1763 body = g_strdup_printf (
1764 "<b>%s</b>\n%s %s",
1765 organiser.cn, start_str, time_str);
1766 }
1767 else {
1768 if (location)
1769 body = g_strdup_printf (
1770 "%s %s\n%s %s", _("Location:"),
1771 location, start_str, time_str);
1772 else
1773 body = g_strdup_printf (
1774 "%s %s", start_str, time_str);
1775 }
1776
1777 n = notify_notification_new (summary, body, "appointment-soon");
1778 if (!notify_notification_show (n, NULL))
1779 g_warning ("Could not send notification to daemon\n");
1780
1781 /* create the private structure */
1782 g_free (start_str);
1783 g_free (end_str);
1784 g_free (alarm_str);
1785 g_free (time_str);
1786 g_free (str);
1787
1788 }
1789 #endif
1790
1791 /* Performs notification of an audio alarm */
1792 static void
1793 audio_notification (time_t trigger,
1794 CompQueuedAlarms *cqa,
1795 gpointer alarm_id)
1796 {
1797 QueuedAlarm *qa;
1798 ECalComponent *comp;
1799 ECalComponentAlarm *alarm;
1800 icalattach *attach;
1801 gint flag = 0;
1802
1803 debug (("..."));
1804
1805 comp = cqa->alarms->comp;
1806 qa = lookup_queued_alarm (cqa, alarm_id);
1807 if (!qa)
1808 return;
1809
1810 alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
1811 g_return_if_fail (alarm != NULL);
1812
1813 e_cal_component_alarm_get_attach (alarm, &attach);
1814 e_cal_component_alarm_free (alarm);
1815
1816 if (attach && icalattach_get_is_url (attach)) {
1817 const gchar *url;
1818
1819 url = icalattach_get_url (attach);
1820 if (url && *url) {
1821 gchar *filename;
1822 GError *error = NULL;
1823
1824 filename = g_filename_from_uri (url, NULL, &error);
1825
1826 if (error != NULL) {
1827 g_warning ("%s: %s", G_STRFUNC, error->message);
1828 g_error_free (error);
1829 } else if (filename && g_file_test (filename, G_FILE_TEST_EXISTS)) {
1830 #ifdef HAVE_CANBERRA
1831 flag = 1;
1832 ca_context_play (
1833 ca_gtk_context_get (), 0,
1834 CA_PROP_MEDIA_FILENAME, filename, NULL);
1835 #endif
1836 }
1837
1838 g_free (filename);
1839 }
1840 }
1841
1842 if (!flag)
1843 gdk_beep ();
1844
1845 if (attach)
1846 icalattach_unref (attach);
1847
1848 }
1849
1850 /* Performs notification of a mail alarm */
1851 static void
1852 mail_notification (time_t trigger,
1853 CompQueuedAlarms *cqa,
1854 gpointer alarm_id)
1855 {
1856 GtkWidget *container;
1857 GtkWidget *dialog;
1858 GtkWidget *label;
1859
1860 /* FIXME */
1861
1862 debug (("..."));
1863
1864 if (!e_client_check_capability (
1865 E_CLIENT (cqa->parent_client->cal_client),
1866 CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS))
1867 return;
1868
1869 dialog = gtk_dialog_new_with_buttons (
1870 _("Warning"), NULL, 0,
1871 GTK_STOCK_OK, GTK_RESPONSE_CANCEL,
1872 NULL);
1873 label = gtk_label_new (
1874 _("Evolution does not support calendar reminders with\n"
1875 "email notifications yet, but this reminder was\n"
1876 "configured to send an email. Evolution will display\n"
1877 "a normal reminder dialog box instead."));
1878 gtk_widget_show (label);
1879
1880 container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
1881 gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4);
1882
1883 gtk_dialog_run (GTK_DIALOG (dialog));
1884 gtk_widget_destroy (dialog);
1885 }
1886
1887 /* Performs notification of a procedure alarm */
1888 static gboolean
1889 procedure_notification_dialog (const gchar *cmd,
1890 const gchar *url)
1891 {
1892 GtkWidget *container;
1893 GtkWidget *dialog;
1894 GtkWidget *label;
1895 GtkWidget *checkbox;
1896 gchar *str;
1897 gint btn;
1898
1899 debug (("..."));
1900
1901 if (config_data_is_blessed_program (url))
1902 return TRUE;
1903
1904 dialog = gtk_dialog_new_with_buttons (
1905 _("Warning"), NULL, 0,
1906 GTK_STOCK_NO, GTK_RESPONSE_CANCEL,
1907 GTK_STOCK_YES, GTK_RESPONSE_OK,
1908 NULL);
1909
1910 str = g_strdup_printf (
1911 _("An Evolution Calendar reminder is about to trigger. "
1912 "This reminder is configured to run the following program:\n\n"
1913 " %s\n\n"
1914 "Are you sure you want to run this program?"),
1915 cmd);
1916 label = gtk_label_new (str);
1917 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1918 gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
1919 gtk_widget_show (label);
1920
1921 container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
1922 gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4);
1923 g_free (str);
1924
1925 checkbox = gtk_check_button_new_with_label
1926 (_("Do not ask me about this program again."));
1927 gtk_widget_show (checkbox);
1928 gtk_box_pack_start (GTK_BOX (container), checkbox, TRUE, TRUE, 4);
1929
1930 /* Run the dialog */
1931 btn = gtk_dialog_run (GTK_DIALOG (dialog));
1932 if (btn == GTK_RESPONSE_OK &&
1933 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox)))
1934 config_data_save_blessed_program (url);
1935 gtk_widget_destroy (dialog);
1936
1937 return (btn == GTK_RESPONSE_OK);
1938 }
1939
1940 static void
1941 procedure_notification (time_t trigger,
1942 CompQueuedAlarms *cqa,
1943 gpointer alarm_id)
1944 {
1945 QueuedAlarm *qa;
1946 ECalComponent *comp;
1947 ECalComponentAlarm *alarm;
1948 ECalComponentText description;
1949 icalattach *attach;
1950 const gchar *url;
1951 gchar *cmd;
1952 gboolean result = TRUE;
1953
1954 debug (("..."));
1955
1956 comp = cqa->alarms->comp;
1957 qa = lookup_queued_alarm (cqa, alarm_id);
1958 if (!qa)
1959 return;
1960
1961 alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
1962 g_return_if_fail (alarm != NULL);
1963
1964 e_cal_component_alarm_get_attach (alarm, &attach);
1965 e_cal_component_alarm_get_description (alarm, &description);
1966 e_cal_component_alarm_free (alarm);
1967
1968 /* If the alarm has no attachment, simply display a notification dialog. */
1969 if (!attach)
1970 goto fallback;
1971
1972 if (!icalattach_get_is_url (attach)) {
1973 icalattach_unref (attach);
1974 goto fallback;
1975 }
1976
1977 url = icalattach_get_url (attach);
1978 g_return_if_fail (url != NULL);
1979
1980 /* Ask for confirmation before executing the stuff */
1981 if (description.value)
1982 cmd = g_strconcat (url, " ", description.value, NULL);
1983 else
1984 cmd = (gchar *) url;
1985
1986 if (procedure_notification_dialog (cmd, url))
1987 result = g_spawn_command_line_async (cmd, NULL);
1988
1989 if (cmd != (gchar *) url)
1990 g_free (cmd);
1991
1992 icalattach_unref (attach);
1993
1994 /* Fall back to display notification if we got an error */
1995 if (result == FALSE)
1996 goto fallback;
1997
1998 return;
1999
2000 fallback:
2001
2002 display_notification (trigger, cqa, alarm_id, FALSE);
2003 }
2004
2005 static gboolean
2006 check_midnight_refresh (gpointer user_data)
2007 {
2008 time_t new_midnight;
2009 icaltimezone *zone;
2010
2011 debug (("..."));
2012
2013 zone = config_data_get_timezone ();
2014 new_midnight = time_day_end_with_zone (time (NULL), zone);
2015
2016 if (new_midnight > midnight) {
2017 struct _midnight_refresh_msg *msg;
2018
2019 msg = g_slice_new0 (struct _midnight_refresh_msg);
2020 msg->header.func = (MessageFunc) midnight_refresh_async;
2021 msg->remove = FALSE;
2022
2023 message_push ((Message *) msg);
2024 }
2025
2026 return TRUE;
2027 }
2028
2029 static gboolean
2030 check_wall_clock_time_changed (gpointer user_data)
2031 {
2032 static gint64 expected_wall_clock_time = 0;
2033 gint64 wall_clock_time;
2034
2035 #define ADD_SECONDS(to, secs) ((to) + ((secs) * 1000000))
2036
2037 wall_clock_time = g_get_real_time ();
2038
2039 /* use one second margin */
2040 if (wall_clock_time > ADD_SECONDS (expected_wall_clock_time, 1) ||
2041 wall_clock_time < ADD_SECONDS (expected_wall_clock_time, -1)) {
2042 debug (("Current wall-clock time differs from expected, rescheduling alarms"));
2043 check_midnight_refresh (NULL);
2044 alarm_reschedule_timeout ();
2045 }
2046
2047 expected_wall_clock_time = ADD_SECONDS (wall_clock_time, 60);
2048
2049 #undef ADD_SECONDS
2050
2051 return TRUE;
2052 }
2053
2054 /**
2055 * alarm_queue_init:
2056 *
2057 * Initializes the alarm queueing system. This should be called near the
2058 * beginning of the program.
2059 **/
2060 void
2061 alarm_queue_init (gpointer data)
2062 {
2063 an = data;
2064 g_return_if_fail (alarm_queue_inited == FALSE);
2065
2066 debug (("..."));
2067
2068 client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
2069 queue_midnight_refresh ();
2070
2071 if (config_data_get_last_notification_time (NULL) == -1) {
2072 time_t tmval = time_day_begin (time (NULL));
2073 debug (("Setting last notification time to %s", e_ctime (&tmval)));
2074 config_data_set_last_notification_time (NULL, tmval);
2075 }
2076
2077 /* install timeout handler (every 30 mins) for not missing the midnight refresh */
2078 g_timeout_add_seconds (1800, check_midnight_refresh, NULL);
2079
2080 /* monotonic time doesn't change during hibernation, while the wall clock time does,
2081 * thus check for wall clock time changes and reschedule alarms when it changes */
2082 g_timeout_add_seconds (60, check_wall_clock_time_changed, NULL);
2083
2084 #ifdef HAVE_LIBNOTIFY
2085 notify_init ("Evolution Alarms");
2086 #endif
2087
2088 alarm_queue_inited = TRUE;
2089 }
2090
2091 static gboolean
2092 free_client_alarms_cb (gpointer key,
2093 gpointer value,
2094 gpointer user_data)
2095 {
2096 ClientAlarms *ca = value;
2097
2098 debug (("ca=%p", ca));
2099
2100 if (ca) {
2101 remove_client_alarms (ca);
2102 if (ca->cal_client) {
2103 debug (("Disconnecting Client"));
2104
2105 g_signal_handlers_disconnect_matched (
2106 ca->cal_client, G_SIGNAL_MATCH_DATA,
2107 0, 0, NULL, NULL, ca);
2108 g_object_unref (ca->cal_client);
2109 }
2110
2111 if (ca->view) {
2112 debug (("Disconnecting Query"));
2113
2114 g_signal_handlers_disconnect_matched (
2115 ca->view, G_SIGNAL_MATCH_DATA,
2116 0, 0, NULL, NULL, ca);
2117 g_object_unref (ca->view);
2118 }
2119
2120 g_hash_table_destroy (ca->uid_alarms_hash);
2121
2122 g_free (ca);
2123 return TRUE;
2124 }
2125
2126 return FALSE;
2127 }
2128
2129 /**
2130 * alarm_queue_done:
2131 *
2132 * Shuts down the alarm queueing system. This should be called near the end
2133 * of the program. All the monitored calendar clients should already have been
2134 * unregistered with alarm_queue_remove_client().
2135 **/
2136 void
2137 alarm_queue_done (void)
2138 {
2139 g_return_if_fail (alarm_queue_inited);
2140
2141 /* All clients must be unregistered by now */
2142 g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0);
2143
2144 debug (("..."));
2145
2146 g_hash_table_foreach_remove (
2147 client_alarms_hash, (GHRFunc) free_client_alarms_cb, NULL);
2148 g_hash_table_destroy (client_alarms_hash);
2149 client_alarms_hash = NULL;
2150
2151 if (midnight_refresh_id != NULL) {
2152 alarm_remove (midnight_refresh_id);
2153 midnight_refresh_id = NULL;
2154 }
2155
2156 alarm_queue_inited = FALSE;
2157 }
2158
2159 static gboolean
2160 compare_ids (gpointer a,
2161 gpointer b)
2162 {
2163 ECalComponentId *id, *id1;
2164
2165 id = a;
2166 id1 = b;
2167 if (id->uid != NULL && id1->uid != NULL) {
2168 if (g_str_equal (id->uid, id1->uid)) {
2169
2170 if (id->rid && id1->rid)
2171 return g_str_equal (id->rid, id1->rid);
2172 else if (!(id->rid && id1->rid))
2173 return TRUE;
2174 }
2175 }
2176 return FALSE;
2177 }
2178
2179 static guint
2180 hash_ids (gpointer a)
2181 {
2182 ECalComponentId *id =a;
2183
2184 return g_str_hash (id->uid);
2185 }
2186
2187 struct _alarm_client_msg {
2188 Message header;
2189 ECalClient *cal_client;
2190 };
2191
2192 static void
2193 alarm_queue_add_async (struct _alarm_client_msg *msg)
2194 {
2195 ClientAlarms *ca;
2196 ECalClient *cal_client = msg->cal_client;
2197
2198 g_return_if_fail (alarm_queue_inited);
2199 g_return_if_fail (cal_client != NULL);
2200 g_return_if_fail (E_IS_CAL_CLIENT (cal_client));
2201
2202 ca = lookup_client (cal_client);
2203 if (ca) {
2204 /* We already have it. Unref the passed one*/
2205 g_object_unref (cal_client);
2206 return;
2207 }
2208
2209 debug (("client=%p", cal_client));
2210
2211 ca = g_new (ClientAlarms, 1);
2212
2213 ca->cal_client = cal_client;
2214 ca->view = NULL;
2215
2216 g_hash_table_insert (client_alarms_hash, cal_client, ca);
2217
2218 ca->uid_alarms_hash = g_hash_table_new (
2219 (GHashFunc) hash_ids, (GEqualFunc) compare_ids);
2220
2221 load_alarms_for_today (ca);
2222
2223 g_slice_free (struct _alarm_client_msg, msg);
2224 }
2225
2226 /**
2227 * alarm_queue_add_client:
2228 * @cal_client: A calendar client.
2229 *
2230 * Adds a calendar client to the alarm queueing system. Alarm trigger
2231 * notifications will be presented at the appropriate times. The client should
2232 * be removed with alarm_queue_remove_client() when receiving notifications
2233 * from it is no longer desired.
2234 *
2235 * A client can be added any number of times to the alarm queueing system,
2236 * but any single alarm trigger will only be presented once for a particular
2237 * client. The client must still be removed the same number of times from the
2238 * queueing system when it is no longer wanted.
2239 **/
2240 void
2241 alarm_queue_add_client (ECalClient *cal_client)
2242 {
2243 struct _alarm_client_msg *msg;
2244
2245 msg = g_slice_new0 (struct _alarm_client_msg);
2246 msg->header.func = (MessageFunc) alarm_queue_add_async;
2247 msg->cal_client = g_object_ref (cal_client);
2248
2249 message_push ((Message *) msg);
2250 }
2251
2252 /* Removes a component an its alarms */
2253 static void
2254 remove_cqa (ClientAlarms *ca,
2255 ECalComponentId *id,
2256 CompQueuedAlarms *cqa)
2257 {
2258
2259 /* If a component is present, then it means we must have alarms queued
2260 * for it.
2261 */
2262 g_return_if_fail (cqa->queued_alarms != NULL);
2263
2264 debug (("removing %d alarms", g_slist_length (cqa->queued_alarms)));
2265 remove_alarms (cqa, TRUE);
2266 }
2267
2268 static gboolean
2269 remove_comp_by_id (gpointer key,
2270 gpointer value,
2271 gpointer userdata)
2272 {
2273
2274 ClientAlarms *ca = (ClientAlarms *) userdata;
2275
2276 debug (("..."));
2277
2278 /* if (!g_hash_table_size (ca->uid_alarms_hash)) */
2279 /* return; */
2280
2281 remove_cqa (ca, (ECalComponentId *) key, (CompQueuedAlarms *) value);
2282
2283 return TRUE;
2284 }
2285
2286 /* Removes all the alarms queued for a particular calendar client */
2287 static void
2288 remove_client_alarms (ClientAlarms *ca)
2289 {
2290 debug (("size %d", g_hash_table_size (ca->uid_alarms_hash)));
2291
2292 g_hash_table_foreach_remove (
2293 ca->uid_alarms_hash, (GHRFunc) remove_comp_by_id, ca);
2294
2295 /* The hash table should be empty now */
2296 g_return_if_fail (g_hash_table_size (ca->uid_alarms_hash) == 0);
2297 }
2298
2299 /**
2300 * alarm_queue_remove_client:
2301 * @client: A calendar client.
2302 *
2303 * Removes a calendar client from the alarm queueing system.
2304 **/
2305 static void
2306 alarm_queue_remove_async (struct _alarm_client_msg *msg)
2307 {
2308 ClientAlarms *ca;
2309 ECalClient *cal_client = msg->cal_client;
2310
2311 g_return_if_fail (alarm_queue_inited);
2312 g_return_if_fail (cal_client != NULL);
2313 g_return_if_fail (E_IS_CAL_CLIENT (cal_client));
2314
2315 ca = lookup_client (cal_client);
2316 g_return_if_fail (ca != NULL);
2317
2318 debug (("..."));
2319 remove_client_alarms (ca);
2320
2321 /* Clean up */
2322 if (ca->cal_client) {
2323 debug (("Disconnecting Client"));
2324
2325 g_signal_handlers_disconnect_matched (
2326 ca->cal_client, G_SIGNAL_MATCH_DATA,
2327 0, 0, NULL, NULL, ca);
2328 g_object_unref (ca->cal_client);
2329 ca->cal_client = NULL;
2330 }
2331
2332 if (ca->view) {
2333 debug (("Disconnecting Query"));
2334
2335 g_signal_handlers_disconnect_matched (
2336 ca->view, G_SIGNAL_MATCH_DATA,
2337 0, 0, NULL, NULL, ca);
2338 g_object_unref (ca->view);
2339 ca->view = NULL;
2340 }
2341
2342 g_hash_table_destroy (ca->uid_alarms_hash);
2343 ca->uid_alarms_hash = NULL;
2344
2345 g_free (ca);
2346
2347 g_hash_table_remove (client_alarms_hash, cal_client);
2348
2349 g_slice_free (struct _alarm_client_msg, msg);
2350 }
2351
2352 /** alarm_queue_remove_client
2353 *
2354 * asynchronously remove client from alarm queue.
2355 * @cal_client: Client to remove.
2356 * @immediately: Indicates whether use thread or do it right now.
2357 */
2358
2359 void
2360 alarm_queue_remove_client (ECalClient *cal_client,
2361 gboolean immediately)
2362 {
2363 struct _alarm_client_msg *msg;
2364
2365 msg = g_slice_new0 (struct _alarm_client_msg);
2366 msg->header.func = (MessageFunc) alarm_queue_remove_async;
2367 msg->cal_client = cal_client;
2368
2369 if (immediately) {
2370 alarm_queue_remove_async (msg);
2371 } else
2372 message_push ((Message *) msg);
2373 }
2374
2375 /* Update non-time related variables for various structures on modification
2376 * of an existing component to be called only from query_objects_changed_cb */
2377 static void
2378 update_cqa (CompQueuedAlarms *cqa,
2379 ECalComponent *newcomp)
2380 {
2381 ECalComponent *oldcomp;
2382 ECalComponentAlarms *alarms = NULL;
2383 GSList *qa_list; /* List of current QueuedAlarms corresponding to cqa */
2384 time_t from, to;
2385 icaltimezone *zone;
2386 ECalComponentAlarmAction omit[] = {-1};
2387
2388 oldcomp = cqa->alarms->comp;
2389
2390 zone = config_data_get_timezone ();
2391 from = time_day_begin_with_zone (time (NULL), zone);
2392 to = time_day_end_with_zone (time (NULL), zone);
2393
2394 debug (("Generating alarms between %s and %s", e_ctime (&from), e_ctime (&to)));
2395 alarms = e_cal_util_generate_alarms_for_comp (
2396 newcomp, from, to, omit, e_cal_client_resolve_tzid_cb,
2397 cqa->parent_client->cal_client, zone);
2398
2399 /* Update auids in Queued Alarms*/
2400 for (qa_list = cqa->queued_alarms; qa_list; qa_list = qa_list->next) {
2401 QueuedAlarm *qa = qa_list->data;
2402 gchar *check_auid = (gchar *) qa->instance->auid;
2403 ECalComponentAlarm *alarm;
2404
2405 alarm = e_cal_component_get_alarm (newcomp, check_auid);
2406 if (alarm) {
2407 e_cal_component_alarm_free (alarm);
2408 continue;
2409 } else {
2410 alarm = e_cal_component_get_alarm (oldcomp, check_auid);
2411 if (alarm) { /* Need to update QueuedAlarms */
2412 e_cal_component_alarm_free (alarm);
2413 if (alarms == NULL) {
2414 debug (("No alarms found in the modified component"));
2415 break;
2416 }
2417 update_qa (alarms, qa);
2418 }
2419 else
2420 g_warning ("Failed in auid lookup for old component also\n");
2421 }
2422 }
2423
2424 /* Update the actual component stored in CompQueuedAlarms structure */
2425 g_object_unref (cqa->alarms->comp);
2426 cqa->alarms->comp = newcomp;
2427 if (alarms != NULL)
2428 e_cal_component_alarms_free (alarms);
2429 }
2430
2431 static void
2432 update_qa (ECalComponentAlarms *alarms,
2433 QueuedAlarm *qa)
2434 {
2435 ECalComponentAlarmInstance *al_inst;
2436 GSList *instance_list;
2437
2438 debug (("..."));
2439 for (instance_list = alarms->alarms;
2440 instance_list;
2441 instance_list = instance_list->next) {
2442 al_inst = instance_list->data;
2443 /* FIXME If two or more alarm instances (audio, note)
2444 * for same component have same trigger... */
2445 if (al_inst->trigger == qa->orig_trigger) {
2446 g_free ((gchar *) qa->instance->auid);
2447 qa->instance->auid = g_strdup (al_inst->auid);
2448 break;
2449 }
2450 }
2451 }