aboutsummaryrefslogtreecommitdiffstats
path: root/calendar/alarm-notify/alarm-queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/alarm-notify/alarm-queue.c')
-rw-r--r--calendar/alarm-notify/alarm-queue.c2418
1 files changed, 2418 insertions, 0 deletions
diff --git a/calendar/alarm-notify/alarm-queue.c b/calendar/alarm-notify/alarm-queue.c
new file mode 100644
index 0000000000..f7597338a7
--- /dev/null
+++ b/calendar/alarm-notify/alarm-queue.c
@@ -0,0 +1,2418 @@
+/*
+ * Alarm queueing engine
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Federico Mena-Quintero <federico@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#ifdef HAVE_CANBERRA
+#include <canberra-gtk.h>
+#endif
+
+#include <libecal/e-cal-client-view.h>
+#include <libecal/e-cal-time-util.h>
+#include <libecal/e-cal-component.h>
+
+#ifdef HAVE_LIBNOTIFY
+#include <libnotify/notify.h>
+#endif
+
+#include "alarm.h"
+#include "alarm-notify-dialog.h"
+#include "alarm-queue.h"
+#include "alarm-notify.h"
+#include "config-data.h"
+#include "util.h"
+
+#include "calendar/gui/print.h"
+
+/* The dialog with alarm nofications */
+static AlarmNotificationsDialog *alarm_notifications_dialog = NULL;
+
+/* Whether the queueing system has been initialized */
+static gboolean alarm_queue_inited = FALSE;
+
+/* Clients we are monitoring for alarms */
+static GHashTable *client_alarms_hash = NULL;
+
+/* List of tray icons being displayed */
+static GList *tray_icons_list = NULL;
+
+/* Top Tray Image */
+static GtkStatusIcon *tray_icon = NULL;
+static gint tray_blink_id = -1;
+static gint tray_blink_countdown = 0;
+static AlarmNotify *an;
+
+/* Structure that stores a client we are monitoring */
+typedef struct {
+ /* Monitored client */
+ ECalClient *cal_client;
+
+ /* The live view to the calendar */
+ ECalClientView *view;
+
+ /* Hash table of component UID -> CompQueuedAlarms. If an element is
+ * present here, then it means its cqa->queued_alarms contains at least
+ * one queued alarm. When all the alarms for a component have been
+ * dequeued, the CompQueuedAlarms structure is removed from the hash
+ * table. Thus a CQA exists <=> it has queued alarms.
+ */
+ GHashTable *uid_alarms_hash;
+} ClientAlarms;
+
+/* Pair of a ECalComponentAlarms and the mapping from queued alarm IDs to the
+ * actual alarm instance structures.
+ */
+typedef struct {
+ /* The parent client alarms structure */
+ ClientAlarms *parent_client;
+
+ /* The component's UID */
+ ECalComponentId *id;
+
+ /* The actual component and its alarm instances */
+ ECalComponentAlarms *alarms;
+
+ /* List of QueuedAlarm structures */
+ GSList *queued_alarms;
+
+ /* Flags */
+ gboolean expecting_update;
+} CompQueuedAlarms;
+
+/* Pair of a queued alarm ID and the alarm trigger instance it refers to */
+typedef struct {
+ /* Alarm ID from alarm.h */
+ gpointer alarm_id;
+
+ /* Instance from our parent CompQueuedAlarms->alarms->alarms list */
+ ECalComponentAlarmInstance *instance;
+
+ /* original trigger of the instance from component */
+ time_t orig_trigger;
+
+ /* Whether this is a snoozed queued alarm or a normal one */
+ guint snooze : 1;
+} QueuedAlarm;
+
+/* Alarm ID for the midnight refresh function */
+static gpointer midnight_refresh_id = NULL;
+static time_t midnight = 0;
+
+static void remove_client_alarms (ClientAlarms *ca);
+static void display_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id,
+ gboolean use_description);
+static void audio_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id);
+static void mail_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id);
+static void procedure_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id);
+#ifdef HAVE_LIBNOTIFY
+static void popup_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id,
+ gboolean use_description);
+#endif
+static void query_objects_modified_cb (ECalClientView *view,
+ const GSList *objects,
+ gpointer data);
+static void query_objects_removed_cb (ECalClientView *view,
+ const GSList *uids,
+ gpointer data);
+
+static void update_cqa (CompQueuedAlarms *cqa,
+ ECalComponent *comp);
+static void update_qa (ECalComponentAlarms *alarms,
+ QueuedAlarm *qa);
+static void tray_list_remove_cqa (CompQueuedAlarms *cqa);
+static void on_dialog_objs_removed_cb (ECalClientView *view,
+ const GSList *uids,
+ gpointer data);
+
+/* Alarm queue engine */
+
+static void load_alarms_for_today (ClientAlarms *ca);
+static void midnight_refresh_cb (gpointer alarm_id,
+ time_t trigger,
+ gpointer data);
+
+/* Simple asynchronous message dispatcher */
+
+typedef struct _Message Message;
+typedef void (*MessageFunc) (Message *msg);
+
+struct _Message {
+ MessageFunc func;
+};
+
+/*
+static void
+message_proxy (Message *msg)
+{
+ g_return_if_fail (msg->func != NULL);
+ *
+ msg->func (msg);
+}
+ *
+static gpointer
+create_thread_pool (void)
+{
+ return g_thread_pool_new ((GFunc) message_proxy, NULL, 1, FALSE, NULL);
+}*/
+
+static void
+message_push (Message *msg)
+{
+ /* This used be pushed through the thread pool. This fix is made to
+ * work-around the crashers in dbus due to threading. The threading
+ * is not completely removed as its better to have alarm daemon
+ * running in a thread rather than blocking main thread. This is
+ * the reason the creation of thread pool is commented out. */
+ msg->func (msg);
+}
+
+/*
+ * use a static ring-buffer so we can call this twice
+ * in a printf without getting nonsense results.
+ */
+static const gchar *
+e_ctime (const time_t *timep)
+{
+ static gchar *buffer[4] = { 0, };
+ static gint next = 0;
+ const gchar *ret;
+
+ g_free (buffer[next]);
+ ret = buffer[next++] = g_strdup (ctime (timep));
+ if (buffer[next - 1] && *buffer[next - 1]) {
+ gint len = strlen (buffer[next - 1]);
+ while (len > 0 && (buffer[next - 1][len - 1] == '\n' ||
+ buffer[next - 1][len - 1] == '\r' ||
+ g_ascii_isspace (buffer[next - 1][len - 1])))
+ len--;
+
+ buffer[next - 1][len - 1] = 0;
+ }
+
+ if (next >= G_N_ELEMENTS (buffer))
+ next = 0;
+
+ return ret;
+}
+
+/* Queues an alarm trigger for midnight so that we can load the next
+ * day's worth of alarms. */
+static void
+queue_midnight_refresh (void)
+{
+ icaltimezone *zone;
+
+ if (midnight_refresh_id != NULL) {
+ alarm_remove (midnight_refresh_id);
+ midnight_refresh_id = NULL;
+ }
+
+ zone = config_data_get_timezone ();
+ midnight = time_day_end_with_zone (time (NULL), zone);
+
+ debug (("Refresh at %s", e_ctime (&midnight)));
+
+ midnight_refresh_id = alarm_add (
+ midnight, midnight_refresh_cb, NULL, NULL);
+ if (!midnight_refresh_id) {
+ debug (("Could not setup the midnight refresh alarm"));
+ /* FIXME: what to do? */
+ }
+}
+
+/* Loads a client's alarms; called from g_hash_table_foreach() */
+static void
+add_client_alarms_cb (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ ClientAlarms *ca = (ClientAlarms *) value;
+
+ debug (("Adding %p", ca));
+
+ load_alarms_for_today (ca);
+}
+
+struct _midnight_refresh_msg {
+ Message header;
+ gboolean remove;
+};
+
+/* Loads the alarms for the new day every midnight */
+static void
+midnight_refresh_async (struct _midnight_refresh_msg *msg)
+{
+ debug (("..."));
+
+ /* Re-load the alarms for all clients */
+ g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL);
+
+ /* Re-schedule the midnight update */
+ if (msg->remove && midnight_refresh_id != NULL) {
+ debug (("Reschedule the midnight update"));
+ alarm_remove (midnight_refresh_id);
+ midnight_refresh_id = NULL;
+ }
+
+ queue_midnight_refresh ();
+
+ g_slice_free (struct _midnight_refresh_msg, msg);
+}
+
+static void
+midnight_refresh_cb (gpointer alarm_id,
+ time_t trigger,
+ gpointer data)
+{
+ struct _midnight_refresh_msg *msg;
+
+ msg = g_slice_new0 (struct _midnight_refresh_msg);
+ msg->header.func = (MessageFunc) midnight_refresh_async;
+ msg->remove = TRUE;
+
+ message_push ((Message *) msg);
+}
+
+/* Looks up a client in the client alarms hash table */
+static ClientAlarms *
+lookup_client (ECalClient *cal_client)
+{
+ return g_hash_table_lookup (client_alarms_hash, cal_client);
+}
+
+/* Looks up a queued alarm based on its alarm ID */
+static QueuedAlarm *
+lookup_queued_alarm (CompQueuedAlarms *cqa,
+ gpointer alarm_id)
+{
+ GSList *l;
+ QueuedAlarm *qa;
+
+ qa = NULL;
+
+ for (l = cqa->queued_alarms; l; l = l->next) {
+ qa = l->data;
+ if (qa->alarm_id == alarm_id)
+ return qa;
+ }
+
+ /* not found, might have been updated/removed */
+ return NULL;
+}
+
+/* Removes an alarm from the list of alarms of a component. If the alarm was
+ * the last one listed for the component, it removes the component itself.
+ */
+static gboolean
+remove_queued_alarm (CompQueuedAlarms *cqa,
+ gpointer alarm_id,
+ gboolean free_object,
+ gboolean remove_alarm)
+{
+ QueuedAlarm *qa = NULL;
+ GSList *l;
+
+ debug (("..."));
+
+ for (l = cqa->queued_alarms; l; l = l->next) {
+ qa = l->data;
+ if (qa->alarm_id == alarm_id)
+ break;
+ }
+
+ if (!l)
+ return FALSE;
+
+ cqa->queued_alarms = g_slist_delete_link (cqa->queued_alarms, l);
+
+ if (remove_alarm) {
+ GError *error = NULL;
+ ECalComponentId *id;
+
+ id = e_cal_component_get_id (cqa->alarms->comp);
+ if (id) {
+ cqa->expecting_update = TRUE;
+ e_cal_client_discard_alarm_sync (
+ cqa->parent_client->cal_client, id->uid,
+ id->rid, qa->instance->auid, NULL, &error);
+ cqa->expecting_update = FALSE;
+
+ if (error) {
+ if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED))
+ g_warning (
+ "%s: Failed to discard alarm: %s",
+ G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+ e_cal_component_free_id (id);
+ }
+ }
+
+ g_free (qa);
+
+ /* If this was the last queued alarm for this component, remove the
+ * component itself.
+ */
+
+ if (cqa->queued_alarms != NULL)
+ return FALSE;
+
+ debug (("Last Component. Removing CQA- Free=%d", free_object));
+ if (free_object) {
+ cqa->id = NULL;
+ cqa->parent_client = NULL;
+ e_cal_component_alarms_free (cqa->alarms);
+ g_free (cqa);
+ } else {
+ e_cal_component_alarms_free (cqa->alarms);
+ cqa->alarms = NULL;
+ }
+
+ return TRUE;
+}
+
+/**
+ * has_known_notification:
+ * Test for notification method and returns if it knows it or not.
+ * @comp: Component with an alarm.
+ * @alarm_uid: ID of the alarm in the comp to test.
+ *
+ * Returns: %TRUE when we know the notification type, %FALSE otherwise.
+ */
+static gboolean
+has_known_notification (ECalComponent *comp,
+ const gchar *alarm_uid)
+{
+ ECalComponentAlarm *alarm;
+ ECalComponentAlarmAction action;
+
+ g_return_val_if_fail (comp != NULL, FALSE);
+ g_return_val_if_fail (alarm_uid != NULL, FALSE);
+
+ alarm = e_cal_component_get_alarm (comp, alarm_uid);
+ if (!alarm)
+ return FALSE;
+
+ e_cal_component_alarm_get_action (alarm, &action);
+ e_cal_component_alarm_free (alarm);
+
+ switch (action) {
+ case E_CAL_COMPONENT_ALARM_AUDIO:
+ case E_CAL_COMPONENT_ALARM_DISPLAY:
+ case E_CAL_COMPONENT_ALARM_EMAIL:
+ case E_CAL_COMPONENT_ALARM_PROCEDURE:
+ return TRUE;
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/* Callback used when an alarm triggers */
+static void
+alarm_trigger_cb (gpointer alarm_id,
+ time_t trigger,
+ gpointer data)
+{
+ CompQueuedAlarms *cqa;
+ ECalComponent *comp;
+ QueuedAlarm *qa;
+ ECalComponentAlarm *alarm;
+ ECalComponentAlarmAction action;
+
+ cqa = data;
+ comp = cqa->alarms->comp;
+
+ config_data_set_last_notification_time (
+ cqa->parent_client->cal_client, trigger);
+ debug (("Setting Last notification time to %s", e_ctime (&trigger)));
+
+ qa = lookup_queued_alarm (cqa, alarm_id);
+ if (!qa)
+ return;
+
+ /* Decide what to do based on the alarm action. We use the trigger that
+ * is passed to us instead of the one from the instance structure
+ * because this may be a snoozed alarm instead of an original
+ * occurrence.
+ */
+
+ alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
+ if (!alarm)
+ return;
+
+ e_cal_component_alarm_get_action (alarm, &action);
+ e_cal_component_alarm_free (alarm);
+
+ switch (action) {
+ case E_CAL_COMPONENT_ALARM_AUDIO:
+ audio_notification (trigger, cqa, alarm_id);
+ break;
+
+ case E_CAL_COMPONENT_ALARM_DISPLAY:
+#ifdef HAVE_LIBNOTIFY
+ popup_notification (trigger, cqa, alarm_id, TRUE);
+#endif
+ display_notification (trigger, cqa, alarm_id, TRUE);
+ break;
+
+ case E_CAL_COMPONENT_ALARM_EMAIL:
+ mail_notification (trigger, cqa, alarm_id);
+ break;
+
+ case E_CAL_COMPONENT_ALARM_PROCEDURE:
+ procedure_notification (trigger, cqa, alarm_id);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+ debug (("Notification sent: %d", action));
+}
+
+/* Adds the alarms in a ECalComponentAlarms structure to the alarms queued for a
+ * particular client. Also puts the triggers in the alarm timer queue.
+ */
+static void
+add_component_alarms (ClientAlarms *ca,
+ ECalComponentAlarms *alarms)
+{
+ ECalComponentId *id;
+ CompQueuedAlarms *cqa;
+ GSList *l;
+
+ /* No alarms? */
+ if (alarms == NULL || alarms->alarms == NULL) {
+ debug (("No alarms to add"));
+ if (alarms)
+ e_cal_component_alarms_free (alarms);
+ return;
+ }
+
+ cqa = g_new (CompQueuedAlarms, 1);
+ cqa->parent_client = ca;
+ cqa->alarms = alarms;
+ cqa->expecting_update = FALSE;
+
+ cqa->queued_alarms = NULL;
+ debug (("Creating CQA %p", cqa));
+
+ for (l = alarms->alarms; l; l = l->next) {
+ ECalComponentAlarmInstance *instance;
+ gpointer alarm_id;
+ QueuedAlarm *qa;
+
+ instance = l->data;
+
+ if (!has_known_notification (cqa->alarms->comp, instance->auid))
+ continue;
+
+ alarm_id = alarm_add (
+ instance->trigger, alarm_trigger_cb, cqa, NULL);
+ if (!alarm_id)
+ continue;
+
+ qa = g_new (QueuedAlarm, 1);
+ qa->alarm_id = alarm_id;
+ qa->instance = instance;
+ qa->orig_trigger = instance->trigger;
+ qa->snooze = FALSE;
+
+ cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
+ }
+
+ id = e_cal_component_get_id (alarms->comp);
+
+ /* If we failed to add all the alarms, then we should get rid of the cqa */
+ if (cqa->queued_alarms == NULL) {
+ e_cal_component_alarms_free (cqa->alarms);
+ cqa->alarms = NULL;
+ debug (("Failed to add all : %p", cqa));
+ g_free (cqa);
+ return;
+ }
+
+ cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
+ cqa->id = id;
+ debug (("Alarm added for %s", id->uid));
+ g_hash_table_insert (ca->uid_alarms_hash, cqa->id, cqa);
+}
+
+/* Loads the alarms of a client for a given range of time */
+static void
+load_alarms (ClientAlarms *ca,
+ time_t start,
+ time_t end)
+{
+ gchar *str_query, *iso_start, *iso_end;
+ GError *error = NULL;
+
+ debug (("..."));
+
+ iso_start = isodate_from_time_t (start);
+ if (!iso_start)
+ return;
+
+ iso_end = isodate_from_time_t (end);
+ if (!iso_end) {
+ g_free (iso_start);
+ return;
+ }
+
+ str_query = g_strdup_printf (
+ "(has-alarms-in-range? (make-time \"%s\") "
+ "(make-time \"%s\"))", iso_start, iso_end);
+ g_free (iso_start);
+ g_free (iso_end);
+
+ /* create the live query */
+ if (ca->view) {
+ debug (("Disconnecting old queries"));
+ g_signal_handlers_disconnect_matched (
+ ca->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, ca);
+ g_object_unref (ca->view);
+ ca->view = NULL;
+ }
+
+ e_cal_client_get_view_sync (
+ ca->cal_client, str_query, &ca->view, NULL, &error);
+
+ if (error != NULL) {
+ g_warning (
+ "%s: Could not get query for client: %s",
+ G_STRFUNC, error->message);
+ g_error_free (error);
+ } else {
+ debug (("Setting Call backs"));
+
+ g_signal_connect (
+ ca->view, "objects-added",
+ G_CALLBACK (query_objects_modified_cb), ca);
+ g_signal_connect (
+ ca->view, "objects-modified",
+ G_CALLBACK (query_objects_modified_cb), ca);
+ g_signal_connect (
+ ca->view, "objects-removed",
+ G_CALLBACK (query_objects_removed_cb), ca);
+
+ e_cal_client_view_start (ca->view, &error);
+
+ if (error != NULL) {
+ g_warning (
+ "%s: Failed to start view: %s",
+ G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+ }
+
+ g_free (str_query);
+}
+
+/* Loads today's remaining alarms for a client */
+static void
+load_alarms_for_today (ClientAlarms *ca)
+{
+ time_t now, from, day_end, day_start;
+ icaltimezone *zone;
+
+ now = time (NULL);
+ zone = config_data_get_timezone ();
+ day_start = time_day_begin_with_zone (now, zone);
+
+ /* Make sure we don't miss some events from the last notification.
+ * We add 1 to the saved notification time to make the time ranges
+ * half-open; we do not want to display the "last" displayed alarm
+ * twice, once when it occurs and once when the alarm daemon restarts.
+ */
+ from = config_data_get_last_notification_time (ca->cal_client) + 1;
+ if (from <= 0)
+ from = MAX (from, day_start);
+
+ day_end = time_day_end_with_zone (now, zone);
+ debug (("From %s to %s", e_ctime (&from), e_ctime (&day_end)));
+ load_alarms (ca, from, day_end);
+}
+
+/* Looks up a component's queued alarm structure in a client alarms structure */
+static CompQueuedAlarms *
+lookup_comp_queued_alarms (ClientAlarms *ca,
+ const ECalComponentId *id)
+{
+ return g_hash_table_lookup (ca->uid_alarms_hash, id);
+}
+
+static void
+remove_alarms (CompQueuedAlarms *cqa,
+ gboolean free_object)
+{
+ GSList *l;
+
+ debug (("Removing for %p", cqa));
+ for (l = cqa->queued_alarms; l;) {
+ QueuedAlarm *qa;
+
+ qa = l->data;
+
+ /* Get the next element here because the list element will go
+ * away in remove_queued_alarm(). The qa will be freed there as
+ * well.
+ */
+ l = l->next;
+
+ alarm_remove (qa->alarm_id);
+ remove_queued_alarm (cqa, qa->alarm_id, free_object, FALSE);
+ }
+}
+
+/* Removes a component an its alarms */
+static void
+remove_comp (ClientAlarms *ca,
+ ECalComponentId *id)
+{
+ CompQueuedAlarms *cqa;
+
+ debug (("Removing uid %s", id->uid));
+
+ if (id->rid && !(*(id->rid))) {
+ g_free (id->rid);
+ id->rid = NULL;
+ }
+
+ cqa = lookup_comp_queued_alarms (ca, id);
+ if (!cqa)
+ return;
+
+ /* If a component is present, then it means we must have alarms queued
+ * for it.
+ */
+ g_return_if_fail (cqa->queued_alarms != NULL);
+
+ debug (("Removing CQA %p", cqa));
+ remove_alarms (cqa, TRUE);
+}
+
+/* Called when a calendar component changes; we must reload its corresponding
+ * alarms.
+ */
+struct _query_msg {
+ Message header;
+ GSList *objects;
+ gpointer data;
+};
+
+static GSList *
+duplicate_ical (const GSList *in_list)
+{
+ const GSList *l;
+ GSList *out_list = NULL;
+ for (l = in_list; l; l = l->next) {
+ out_list = g_slist_prepend (
+ out_list, icalcomponent_new_clone (l->data));
+ }
+
+ return g_slist_reverse (out_list);
+}
+
+static GSList *
+duplicate_ecal (const GSList *in_list)
+{
+ const GSList *l;
+ GSList *out_list = NULL;
+ for (l = in_list; l; l = l->next) {
+ ECalComponentId *id, *old;
+ old = l->data;
+ id = g_new0 (ECalComponentId, 1);
+ id->uid = g_strdup (old->uid);
+ id->rid = g_strdup (old->rid);
+ out_list = g_slist_prepend (out_list, id);
+ }
+
+ return g_slist_reverse (out_list);
+}
+
+static gboolean
+get_alarms_for_object (ECalClient *cal_client,
+ const ECalComponentId *id,
+ time_t start,
+ time_t end,
+ ECalComponentAlarms **alarms)
+{
+ icalcomponent *icalcomp;
+ ECalComponent *comp;
+ ECalComponentAlarmAction omit[] = {-1};
+
+ g_return_val_if_fail (cal_client != NULL, FALSE);
+ g_return_val_if_fail (id != NULL, FALSE);
+ g_return_val_if_fail (alarms != NULL, FALSE);
+ g_return_val_if_fail (start >= 0 && end >= 0, FALSE);
+ g_return_val_if_fail (start <= end, FALSE);
+
+ if (!e_cal_client_get_object_sync (
+ cal_client, id->uid, id->rid, &icalcomp, NULL, NULL))
+ return FALSE;
+
+ if (!icalcomp)
+ return FALSE;
+
+ comp = e_cal_component_new ();
+ if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
+ icalcomponent_free (icalcomp);
+ g_object_unref (comp);
+ return FALSE;
+ }
+
+ *alarms = e_cal_util_generate_alarms_for_comp (
+ comp, start, end, omit, e_cal_client_resolve_tzid_cb,
+ cal_client, e_cal_client_get_default_timezone (cal_client));
+
+ g_object_unref (comp);
+
+ return TRUE;
+}
+
+static void
+query_objects_changed_async (struct _query_msg *msg)
+{
+ ClientAlarms *ca;
+ time_t from, day_end;
+ ECalComponentAlarms *alarms;
+ gboolean found;
+ icaltimezone *zone;
+ CompQueuedAlarms *cqa;
+ GSList *l;
+ GSList *objects;
+
+ ca = msg->data;
+ objects = msg->objects;
+
+ from = config_data_get_last_notification_time (ca->cal_client);
+ if (from == -1)
+ from = time (NULL);
+ else
+ from += 1; /* we add 1 to make sure the alarm is not displayed twice */
+
+ zone = config_data_get_timezone ();
+
+ day_end = time_day_end_with_zone (time (NULL), zone);
+
+ for (l = objects; l != NULL; l = l->next) {
+ ECalComponentId *id;
+ GSList *sl;
+ ECalComponent *comp = e_cal_component_new ();
+
+ e_cal_component_set_icalcomponent (comp, l->data);
+
+ id = e_cal_component_get_id (comp);
+ found = get_alarms_for_object (ca->cal_client, id, from, day_end, &alarms);
+
+ if (!found) {
+ debug (("No Alarm found for client %p", ca->cal_client));
+ tray_list_remove_cqa (lookup_comp_queued_alarms (ca, id));
+ remove_comp (ca, id);
+ g_hash_table_remove (ca->uid_alarms_hash, id);
+ e_cal_component_free_id (id);
+ g_object_unref (comp);
+ comp = NULL;
+ continue;
+ }
+
+ cqa = lookup_comp_queued_alarms (ca, id);
+ if (!cqa) {
+ debug (("No currently queued alarms for %s", id->uid));
+ add_component_alarms (ca, alarms);
+ g_object_unref (comp);
+ comp = NULL;
+ continue;
+ }
+
+ debug (("Alarm Already Exist for %s", id->uid));
+ /* If the alarms or the alarms list is empty,
+ * remove it after updating the cqa structure. */
+ if (alarms == NULL || alarms->alarms == NULL) {
+
+ /* Update the cqa and its queued alarms
+ * for changes in summary and alarm_uid. */
+ update_cqa (cqa, comp);
+
+ if (alarms)
+ e_cal_component_alarms_free (alarms);
+ continue;
+ }
+
+ /* if already in the list, just update it */
+ remove_alarms (cqa, FALSE);
+ cqa->alarms = alarms;
+ cqa->queued_alarms = NULL;
+
+ /* add the new alarms */
+ for (sl = cqa->alarms->alarms; sl; sl = sl->next) {
+ ECalComponentAlarmInstance *instance;
+ gpointer alarm_id;
+ QueuedAlarm *qa;
+
+ instance = sl->data;
+
+ if (!has_known_notification (cqa->alarms->comp, instance->auid))
+ continue;
+
+ alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, NULL);
+ if (!alarm_id)
+ continue;
+
+ qa = g_new (QueuedAlarm, 1);
+ qa->alarm_id = alarm_id;
+ qa->instance = instance;
+ qa->snooze = FALSE;
+ qa->orig_trigger = instance->trigger;
+ cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
+ debug (("Adding %p to queue", qa));
+ }
+
+ cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
+ g_object_unref (comp);
+ comp = NULL;
+ }
+ g_slist_free (objects);
+
+ g_slice_free (struct _query_msg, msg);
+}
+
+static void
+query_objects_modified_cb (ECalClientView *view,
+ const GSList *objects,
+ gpointer data)
+{
+ struct _query_msg *msg;
+
+ msg = g_slice_new0 (struct _query_msg);
+ msg->header.func = (MessageFunc) query_objects_changed_async;
+ msg->objects = duplicate_ical (objects);
+ msg->data = data;
+
+ message_push ((Message *) msg);
+}
+
+/* Called when a calendar component is removed; we must delete its corresponding
+ * alarms.
+ */
+static void
+query_objects_removed_async (struct _query_msg *msg)
+{
+ ClientAlarms *ca;
+ GSList *l;
+ GSList *objects;
+
+ ca = msg->data;
+ objects = msg->objects;
+
+ debug (("Removing %d objects", g_slist_length (objects)));
+
+ for (l = objects; l != NULL; l = l->next) {
+ /* If the alarm is already triggered remove it. */
+ tray_list_remove_cqa (lookup_comp_queued_alarms (ca, l->data));
+ remove_comp (ca, l->data);
+ g_hash_table_remove (ca->uid_alarms_hash, l->data);
+ e_cal_component_free_id (l->data);
+ }
+
+ g_slist_free (objects);
+
+ g_slice_free (struct _query_msg, msg);
+}
+
+static void
+query_objects_removed_cb (ECalClientView *view,
+ const GSList *uids,
+ gpointer data)
+{
+ struct _query_msg *msg;
+
+ msg = g_slice_new0 (struct _query_msg);
+ msg->header.func = (MessageFunc) query_objects_removed_async;
+ msg->objects = duplicate_ecal (uids);
+ msg->data = data;
+
+ message_push ((Message *) msg);
+}
+
+/* Notification functions */
+
+/* Creates a snooze alarm based on an existing one. The snooze offset is
+ * compued with respect to the current time.
+ */
+static void
+create_snooze (CompQueuedAlarms *cqa,
+ gpointer alarm_id,
+ gint snooze_mins)
+{
+ QueuedAlarm *orig_qa;
+ time_t t;
+ gpointer new_id;
+
+ orig_qa = lookup_queued_alarm (cqa, alarm_id);
+ if (!orig_qa)
+ return;
+
+ t = time (NULL);
+ t += snooze_mins * 60;
+
+ new_id = alarm_add (t, alarm_trigger_cb, cqa, NULL);
+ if (!new_id) {
+ debug (("Unable to schedule trigger for %s", e_ctime (&t)));
+ return;
+ }
+
+ orig_qa->instance->trigger = t;
+ orig_qa->alarm_id = new_id;
+ orig_qa->snooze = TRUE;
+ debug (("Adding a alarm at %s", e_ctime (&t)));
+}
+
+/* Launches a component editor for a component */
+static void
+edit_component (ECalClient *cal_client,
+ ECalComponent *comp)
+{
+ ESource *source;
+ gchar *command_line;
+ const gchar *scheme;
+ const gchar *comp_uid;
+ const gchar *source_uid;
+ GError *error = NULL;
+
+ /* XXX Don't we have a function to construct these URIs?
+ * How are other apps expected to know this stuff? */
+
+ source = e_client_get_source (E_CLIENT (cal_client));
+ source_uid = e_source_peek_uid (source);
+
+ e_cal_component_get_uid (comp, &comp_uid);
+
+ switch (e_cal_client_get_source_type (cal_client)) {
+ case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+ scheme = "calendar:";
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+ scheme = "task:";
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+ scheme = "memo:";
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ command_line = g_strdup_printf (
+ "%s %s///?source-uid=%s&comp-uid=%s",
+ PACKAGE, scheme, source_uid, comp_uid);
+
+ if (!g_spawn_command_line_async (command_line, &error)) {
+ g_critical ("%s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (command_line);
+}
+
+static void
+print_component (ECalClient *cal_client,
+ ECalComponent *comp)
+{
+ print_comp (comp,
+ cal_client,
+ config_data_get_timezone (),
+ config_data_get_24_hour_format (),
+ GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
+}
+
+typedef struct {
+ gchar *summary;
+ gchar *description;
+ gchar *location;
+ gboolean blink_state;
+ gboolean snooze_set;
+ gint blink_id;
+ time_t trigger;
+ CompQueuedAlarms *cqa;
+ gpointer alarm_id;
+ ECalComponent *comp;
+ ECalClient *cal_client;
+ ECalClientView *view;
+ GdkPixbuf *image;
+ GtkTreeIter iter;
+} TrayIconData;
+
+static void
+free_tray_icon_data (TrayIconData *tray_data)
+{
+ g_return_if_fail (tray_data != NULL);
+
+ if (tray_data->summary) {
+ g_free (tray_data->summary);
+ tray_data->summary = NULL;
+ }
+
+ if (tray_data->description) {
+ g_free (tray_data->description);
+ tray_data->description = NULL;
+ }
+
+ if (tray_data->location) {
+ g_free (tray_data->location);
+ tray_data->location = NULL;
+ }
+
+ g_object_unref (tray_data->cal_client);
+ tray_data->cal_client = NULL;
+
+ g_signal_handlers_disconnect_matched (
+ tray_data->view, G_SIGNAL_MATCH_FUNC,
+ 0, 0, NULL, on_dialog_objs_removed_cb, NULL);
+ g_object_unref (tray_data->view);
+ tray_data->view = NULL;
+
+ g_object_unref (tray_data->comp);
+ tray_data->comp = NULL;
+
+ tray_data->cqa = NULL;
+ tray_data->alarm_id = NULL;
+ tray_data->image = NULL;
+
+ g_free (tray_data);
+}
+
+static void
+on_dialog_objs_removed_async (struct _query_msg *msg)
+{
+ TrayIconData *tray_data;
+ GSList *l, *objects;
+ ECalComponentId *our_id;
+
+ debug (("..."));
+
+ tray_data = msg->data;
+ objects = msg->objects;
+
+ our_id = e_cal_component_get_id (tray_data->comp);
+ g_return_if_fail (our_id);
+
+ for (l = objects; l != NULL; l = l->next) {
+ ECalComponentId *id = l->data;
+
+ if (!id)
+ continue;
+
+ if (g_strcmp0 (id->uid, our_id->uid) == 0&& g_strcmp0 (id->rid, our_id->rid) == 0) {
+ tray_data->cqa = NULL;
+ tray_data->alarm_id = NULL;
+ tray_icons_list = g_list_remove (tray_icons_list, tray_data);
+ tray_data = NULL;
+ }
+
+ e_cal_component_free_id (id);
+ }
+
+ e_cal_component_free_id (our_id);
+ g_slist_free (objects);
+ g_slice_free (struct _query_msg, msg);
+}
+
+static void
+on_dialog_objs_removed_cb (ECalClientView *view,
+ const GSList *uids,
+ gpointer data)
+{
+ struct _query_msg *msg;
+
+ msg = g_slice_new0 (struct _query_msg);
+ msg->header.func = (MessageFunc) on_dialog_objs_removed_async;
+ msg->objects = duplicate_ecal (uids);
+ msg->data = data;
+
+ message_push ((Message *) msg);
+}
+
+struct _tray_cqa_msg {
+ Message header;
+ CompQueuedAlarms *cqa;
+};
+
+static void
+tray_list_remove_cqa_async (struct _tray_cqa_msg *msg)
+{
+ CompQueuedAlarms *cqa = msg->cqa;
+ GList *list = tray_icons_list;
+
+ debug (("Removing CQA %p from tray list", cqa));
+
+ while (list) {
+ TrayIconData *tray_data = list->data;
+ GList *tmp = list;
+ GtkTreeModel *model;
+
+ list = list->next;
+ if (tray_data->cqa == cqa) {
+ debug (("Found"));
+ tray_icons_list = g_list_delete_link (tray_icons_list, tmp);
+ if (alarm_notifications_dialog) {
+ model = gtk_tree_view_get_model (
+ GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
+ gtk_list_store_remove (GTK_LIST_STORE (model), &(tray_data->iter));
+ }
+ free_tray_icon_data (tray_data);
+ }
+ }
+
+ debug (("%d alarms left", g_list_length (tray_icons_list)));
+
+ if (alarm_notifications_dialog) {
+ if (!g_list_length (tray_icons_list)) {
+ gtk_widget_destroy (alarm_notifications_dialog->dialog);
+ g_free (alarm_notifications_dialog);
+ alarm_notifications_dialog = NULL;
+ } else {
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreeSelection *sel;
+
+ model = gtk_tree_view_get_model (
+ GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
+ gtk_tree_model_get_iter_first (model, &iter);
+ sel = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
+ gtk_tree_selection_select_iter (sel, &iter);
+ }
+ }
+
+ g_slice_free (struct _tray_cqa_msg, msg);
+}
+
+static void
+tray_list_remove_cqa (CompQueuedAlarms *cqa)
+{
+ struct _tray_cqa_msg *msg;
+
+ msg = g_slice_new0 (struct _tray_cqa_msg);
+ msg->header.func = (MessageFunc) tray_list_remove_cqa_async;
+ msg->cqa = cqa;
+
+ message_push ((Message *) msg);
+}
+
+/* Callback used from the alarm notify dialog */
+static void
+tray_list_remove_async (Message *msg)
+{
+ GList *list = tray_icons_list;
+
+ debug (("Removing %d alarms", g_list_length (list)));
+ while (list != NULL) {
+
+ TrayIconData *tray_data = list->data;
+
+ if (!tray_data->snooze_set) {
+ GList *temp = list->next;
+ gboolean status;
+
+ tray_icons_list = g_list_remove_link (tray_icons_list, list);
+ status = remove_queued_alarm (
+ tray_data->cqa,
+ tray_data->alarm_id, FALSE, TRUE);
+ if (status) {
+ g_hash_table_remove (
+ tray_data->cqa->parent_client->uid_alarms_hash,
+ tray_data->cqa->id);
+ e_cal_component_free_id (tray_data->cqa->id);
+ g_free (tray_data->cqa);
+ }
+ free_tray_icon_data (tray_data);
+ tray_data = NULL;
+ g_list_free_1 (list);
+ if (tray_icons_list != list) /* List head is modified */
+ list = tray_icons_list;
+ else
+ list = temp;
+ } else
+ list = list->next;
+ }
+
+ g_slice_free (Message, msg);
+}
+
+static void
+tray_list_remove_icons (void)
+{
+ Message *msg;
+
+ msg = g_slice_new0 (Message);
+ msg->func = tray_list_remove_async;
+
+ message_push (msg);
+}
+
+struct _tray_msg {
+ Message header;
+ TrayIconData *data;
+};
+
+static void
+tray_list_remove_data_async (struct _tray_msg *msg)
+{
+ TrayIconData *tray_data = msg->data;
+
+ debug (("Removing %p from tray list", tray_data));
+
+ tray_icons_list = g_list_remove_all (tray_icons_list, tray_data);
+ free_tray_icon_data (tray_data);
+ tray_data = NULL;
+
+ g_slice_free (struct _tray_msg, msg);
+}
+
+static void
+tray_list_remove_data (TrayIconData *data)
+{
+ struct _tray_msg *msg;
+
+ msg = g_slice_new0 (struct _tray_msg);
+ msg->header.func = (MessageFunc) tray_list_remove_data_async;
+ msg->data = data;
+
+ message_push ((Message *) msg);
+}
+
+static void
+notify_dialog_cb (AlarmNotifyResult result,
+ gint snooze_mins,
+ gpointer data)
+{
+ TrayIconData *tray_data = data;
+
+ debug (("Received from dialog"));
+
+ g_signal_handlers_disconnect_matched (
+ tray_data->view, G_SIGNAL_MATCH_FUNC,
+ 0, 0, NULL, on_dialog_objs_removed_cb, NULL);
+
+ switch (result) {
+ case ALARM_NOTIFY_SNOOZE:
+ debug (("Creating a snooze"));
+ create_snooze (tray_data->cqa, tray_data->alarm_id, snooze_mins);
+ tray_data->snooze_set = TRUE;
+ tray_list_remove_data (tray_data);
+ if (alarm_notifications_dialog) {
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
+ GtkTreeIter iter;
+ GtkTreeModel *model = NULL;
+
+ /* We can also use tray_data->iter */
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ if (!gtk_tree_model_get_iter_first (model, &iter)) {
+ /* We removed the last one */
+ gtk_widget_destroy (alarm_notifications_dialog->dialog);
+ g_free (alarm_notifications_dialog);
+ alarm_notifications_dialog = NULL;
+ } else {
+ /* Select the first */
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+ }
+
+ }
+
+ break;
+
+ case ALARM_NOTIFY_EDIT:
+ edit_component (tray_data->cal_client, tray_data->comp);
+
+ break;
+
+ case ALARM_NOTIFY_PRINT:
+ print_component (tray_data->cal_client, tray_data->comp);
+
+ break;
+
+ case ALARM_NOTIFY_DISMISS:
+ if (alarm_notifications_dialog) {
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (
+ GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
+ gtk_list_store_remove (GTK_LIST_STORE (model), &tray_data->iter);
+ }
+ break;
+
+ case ALARM_NOTIFY_CLOSE:
+ debug (("Dialog close"));
+ if (alarm_notifications_dialog) {
+ GtkTreeIter iter;
+ GtkTreeModel *model =
+ gtk_tree_view_get_model (
+ GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
+ gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ /* Maybe we should warn about this first? */
+ while (valid) {
+ valid = gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ }
+
+ gtk_widget_destroy (alarm_notifications_dialog->dialog);
+ g_free (alarm_notifications_dialog);
+ alarm_notifications_dialog = NULL;
+
+ /* Task to remove the tray icons */
+ tray_list_remove_icons ();
+ }
+
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ return;
+}
+
+static void
+remove_tray_icon (void)
+{
+ if (tray_blink_id > -1)
+ g_source_remove (tray_blink_id);
+ tray_blink_id = -1;
+
+ if (tray_icon) {
+ gtk_status_icon_set_visible (tray_icon, FALSE);
+ g_object_unref (tray_icon);
+ tray_icon = NULL;
+ }
+}
+
+/* Callbacks. */
+static gboolean
+open_alarm_dialog (TrayIconData *tray_data)
+{
+ QueuedAlarm *qa;
+
+ debug (("..."));
+ qa = lookup_queued_alarm (tray_data->cqa, tray_data->alarm_id);
+ if (qa) {
+ remove_tray_icon ();
+
+ if (!alarm_notifications_dialog)
+ alarm_notifications_dialog = notified_alarms_dialog_new ();
+
+ if (alarm_notifications_dialog) {
+
+ GtkTreeSelection *selection = NULL;
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (alarm_notifications_dialog->treeview));
+
+ tray_data->iter = add_alarm_to_notified_alarms_dialog (
+ alarm_notifications_dialog,
+ tray_data->trigger,
+ qa->instance->occur_start,
+ qa->instance->occur_end,
+ e_cal_component_get_vtype (tray_data->comp),
+ tray_data->summary,
+ tray_data->description,
+ tray_data->location,
+ notify_dialog_cb, tray_data);
+
+ gtk_tree_selection_select_iter (
+ selection, &tray_data->iter);
+
+ }
+
+ } else {
+ remove_tray_icon ();
+ }
+
+ return TRUE;
+}
+
+static gint
+tray_icon_clicked_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ if (event->type == GDK_BUTTON_PRESS) {
+ debug (("left click and %d alarms", g_list_length (tray_icons_list)));
+ if (event->button == 1 && g_list_length (tray_icons_list) > 0) {
+ GList *tmp;
+ for (tmp = tray_icons_list; tmp; tmp = tmp->next) {
+ open_alarm_dialog (tmp->data);
+ }
+
+ return TRUE;
+ } else if (event->button == 3) {
+ debug (("right click"));
+
+ remove_tray_icon ();
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+icon_activated (GtkStatusIcon *icon)
+{
+ GdkEventButton event;
+
+ event.type = GDK_BUTTON_PRESS;
+ event.button = 1;
+ event.time = gtk_get_current_event_time ();
+
+ tray_icon_clicked_cb (NULL, &event, NULL);
+}
+
+static void
+popup_menu (GtkStatusIcon *icon,
+ guint button,
+ guint activate_time)
+{
+ if (button == 3) {
+ /* right click */
+ GdkEventButton event;
+
+ event.type = GDK_BUTTON_PRESS;
+ event.button = 3;
+ event.time = gtk_get_current_event_time ();
+
+ tray_icon_clicked_cb (NULL, &event, NULL);
+ }
+}
+
+static gboolean
+tray_icon_blink_cb (gpointer data)
+{
+ static gboolean tray_blink_state = FALSE;
+ const gchar *icon_name;
+
+ tray_blink_countdown--;
+ tray_blink_state = !tray_blink_state;
+
+ if (tray_blink_state || tray_blink_countdown <= 0)
+ icon_name = "appointment-missed";
+ else
+ icon_name = "appointment-soon";
+
+ if (tray_icon)
+ gtk_status_icon_set_from_icon_name (tray_icon, icon_name);
+
+ if (tray_blink_countdown <= 0)
+ tray_blink_id = -1;
+
+ return tray_blink_countdown > 0;
+}
+
+/* Add a new data to tray list */
+
+static void
+tray_list_add_async (struct _tray_msg *msg)
+{
+ tray_icons_list = g_list_prepend (tray_icons_list, msg->data);
+
+ g_slice_free (struct _tray_msg, msg);
+}
+
+static void
+tray_list_add_new (TrayIconData *data)
+{
+ struct _tray_msg *msg;
+
+ msg = g_slice_new0 (struct _tray_msg);
+ msg->header.func = (MessageFunc) tray_list_add_async;
+ msg->data = data;
+
+ message_push ((Message *) msg);
+}
+
+/* Performs notification of a display alarm */
+static void
+display_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id,
+ gboolean use_description)
+{
+ QueuedAlarm *qa;
+ ECalComponent *comp;
+ const gchar *summary, *description, *location;
+ TrayIconData *tray_data;
+ ECalComponentText text;
+ GSList *text_list;
+ gchar *str, *start_str, *end_str, *alarm_str, *time_str;
+ icaltimezone *current_zone;
+ ECalComponentOrganizer organiser;
+
+ debug (("..."));
+
+ comp = cqa->alarms->comp;
+ qa = lookup_queued_alarm (cqa, alarm_id);
+ if (!qa)
+ return;
+
+ /* get a sensible description for the event */
+ e_cal_component_get_summary (comp, &text);
+ e_cal_component_get_organizer (comp, &organiser);
+
+ if (text.value)
+ summary = text.value;
+ else
+ summary = _("No summary available.");
+
+ e_cal_component_get_description_list (comp, &text_list);
+
+ if (text_list) {
+ text = *((ECalComponentText *) text_list->data);
+ if (text.value)
+ description = text.value;
+ else
+ description = _("No description available.");
+ } else {
+ description = _("No description available.");
+ }
+
+ e_cal_component_free_text_list (text_list);
+
+ e_cal_component_get_location (comp, &location);
+
+ if (!location)
+ location = _("No location information available.");
+
+ /* create the tray icon */
+ if (tray_icon == NULL) {
+ tray_icon = gtk_status_icon_new ();
+ gtk_status_icon_set_from_icon_name (
+ tray_icon, "appointment-soon");
+ g_signal_connect (
+ tray_icon, "activate",
+ G_CALLBACK (icon_activated), NULL);
+ g_signal_connect (
+ tray_icon, "popup-menu",
+ G_CALLBACK (popup_menu), NULL);
+ }
+
+ current_zone = config_data_get_timezone ();
+ alarm_str = timet_to_str_with_zone (trigger, current_zone);
+ start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone);
+ end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone);
+ time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end);
+
+ str = g_strdup_printf ("%s\n%s %s",
+ summary, start_str, time_str);
+
+ /* create the private structure */
+ tray_data = g_new0 (TrayIconData, 1);
+ tray_data->summary = g_strdup (summary);
+ tray_data->description = g_strdup (description);
+ tray_data->location = g_strdup (location);
+ tray_data->trigger = trigger;
+ tray_data->cqa = cqa;
+ tray_data->alarm_id = alarm_id;
+ tray_data->comp = g_object_ref (e_cal_component_clone (comp));
+ tray_data->cal_client = cqa->parent_client->cal_client;
+ tray_data->view = g_object_ref (cqa->parent_client->view);
+ tray_data->blink_state = FALSE;
+ tray_data->snooze_set = FALSE;
+ g_object_ref (tray_data->cal_client);
+
+ /* Task to add tray_data to the global tray_icon_list */
+ tray_list_add_new (tray_data);
+
+ if (g_list_length (tray_icons_list) > 1) {
+ gchar *tip;
+
+ tip = g_strdup_printf (ngettext (
+ "You have %d reminder", "You have %d reminders",
+ g_list_length (tray_icons_list)),
+ g_list_length (tray_icons_list));
+ gtk_status_icon_set_tooltip_text (tray_icon, tip);
+ }
+ else {
+ gtk_status_icon_set_tooltip_text (tray_icon, str);
+ }
+
+ g_free (start_str);
+ g_free (end_str);
+ g_free (alarm_str);
+ g_free (time_str);
+ g_free (str);
+
+ g_signal_connect (
+ tray_data->view, "objects_removed",
+ G_CALLBACK (on_dialog_objs_removed_cb), tray_data);
+
+ /* FIXME: We should remove this check */
+ if (!config_data_get_notify_with_tray ()) {
+ tray_blink_id = -1;
+ open_alarm_dialog (tray_data);
+ if (alarm_notifications_dialog)
+ gtk_window_stick (GTK_WINDOW (
+ alarm_notifications_dialog->dialog));
+ } else {
+ if (tray_blink_id == -1) {
+ tray_blink_countdown = 30;
+ tray_blink_id = g_timeout_add (500, tray_icon_blink_cb, tray_data);
+ }
+ }
+}
+
+#ifdef HAVE_LIBNOTIFY
+static void
+popup_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id,
+ gboolean use_description)
+{
+ QueuedAlarm *qa;
+ ECalComponent *comp;
+ const gchar *summary, *location;
+ ECalComponentText text;
+ gchar *str, *start_str, *end_str, *alarm_str, *time_str;
+ icaltimezone *current_zone;
+ ECalComponentOrganizer organiser;
+ NotifyNotification *n;
+ gchar *body;
+
+ debug (("..."));
+
+ comp = cqa->alarms->comp;
+ qa = lookup_queued_alarm (cqa, alarm_id);
+ if (!qa)
+ return;
+ if (!notify_is_initted ())
+ notify_init("Evolution Alarm Notify");
+
+ /* get a sensible description for the event */
+ e_cal_component_get_summary (comp, &text);
+ e_cal_component_get_organizer (comp, &organiser);
+
+ if (text.value)
+ summary = text.value;
+ else
+ summary = _("No summary available.");
+
+ e_cal_component_get_location (comp, &location);
+
+ /* create the tray icon */
+
+ current_zone = config_data_get_timezone ();
+ alarm_str = timet_to_str_with_zone (trigger, current_zone);
+ start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone);
+ end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone);
+ time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end);
+
+ str = g_strdup_printf ("%s %s",
+ start_str, time_str);
+
+ if (organiser.cn) {
+ if (location)
+ body = g_strdup_printf (
+ "<b>%s</b>\n%s %s\n%s %s",
+ organiser.cn, _("Location:"),
+ location, start_str, time_str);
+ else
+ body = g_strdup_printf (
+ "<b>%s</b>\n%s %s",
+ organiser.cn, start_str, time_str);
+ }
+ else {
+ if (location)
+ body = g_strdup_printf (
+ "%s %s\n%s %s", _("Location:"),
+ location, start_str, time_str);
+ else
+ body = g_strdup_printf (
+ "%s %s", start_str, time_str);
+ }
+
+#ifdef HAVE_LIBNOTIFY_07
+ n = notify_notification_new (summary, body, "appointment-soon");
+#else
+ n = notify_notification_new (summary, body, "appointment-soon", NULL);
+#endif /* HAVE_LIBNOTIFY_07 */
+ if (!notify_notification_show (n, NULL))
+ g_warning ("Could not send notification to daemon\n");
+
+ /* create the private structure */
+ g_free (start_str);
+ g_free (end_str);
+ g_free (alarm_str);
+ g_free (time_str);
+ g_free (str);
+
+}
+#endif
+
+/* Performs notification of an audio alarm */
+static void
+audio_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id)
+{
+ QueuedAlarm *qa;
+ ECalComponent *comp;
+ ECalComponentAlarm *alarm;
+ icalattach *attach;
+ gint flag = 0;
+
+ debug (("..."));
+
+ comp = cqa->alarms->comp;
+ qa = lookup_queued_alarm (cqa, alarm_id);
+ if (!qa)
+ return;
+
+ alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
+ g_return_if_fail (alarm != NULL);
+
+ e_cal_component_alarm_get_attach (alarm, &attach);
+ e_cal_component_alarm_free (alarm);
+
+ if (attach && icalattach_get_is_url (attach)) {
+ const gchar *url;
+
+ url = icalattach_get_url (attach);
+ if (url && *url) {
+ gchar *filename;
+ GError *error = NULL;
+
+ filename = g_filename_from_uri (url, NULL, &error);
+
+ if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ } else if (filename && g_file_test (filename, G_FILE_TEST_EXISTS)) {
+#ifdef HAVE_CANBERRA
+ flag = 1;
+ ca_context_play (
+ ca_gtk_context_get (), 0,
+ CA_PROP_MEDIA_FILENAME, filename, NULL);
+#endif
+ }
+
+ g_free (filename);
+ }
+ }
+
+ if (!flag)
+ gdk_beep ();
+
+ if (attach)
+ icalattach_unref (attach);
+
+}
+
+/* Performs notification of a mail alarm */
+static void
+mail_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id)
+{
+ GtkWidget *container;
+ GtkWidget *dialog;
+ GtkWidget *label;
+
+ /* FIXME */
+
+ debug (("..."));
+
+ if (!e_client_check_capability (
+ E_CLIENT (cqa->parent_client->cal_client),
+ CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS))
+ return;
+
+ dialog = gtk_dialog_new_with_buttons (
+ _("Warning"), NULL, 0,
+ GTK_STOCK_OK, GTK_RESPONSE_CANCEL,
+ NULL);
+ label = gtk_label_new (
+ _("Evolution does not support calendar reminders with\n"
+ "email notifications yet, but this reminder was\n"
+ "configured to send an email. Evolution will display\n"
+ "a normal reminder dialog box instead."));
+ gtk_widget_show (label);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+/* Performs notification of a procedure alarm */
+static gboolean
+procedure_notification_dialog (const gchar *cmd,
+ const gchar *url)
+{
+ GtkWidget *container;
+ GtkWidget *dialog;
+ GtkWidget *label;
+ GtkWidget *checkbox;
+ gchar *str;
+ gint btn;
+
+ debug (("..."));
+
+ if (config_data_is_blessed_program (url))
+ return TRUE;
+
+ dialog = gtk_dialog_new_with_buttons (
+ _("Warning"), NULL, 0,
+ GTK_STOCK_NO, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_YES, GTK_RESPONSE_OK,
+ NULL);
+
+ str = g_strdup_printf (
+ _("An Evolution Calendar reminder is about to trigger. "
+ "This reminder is configured to run the following program:\n\n"
+ " %s\n\n"
+ "Are you sure you want to run this program?"),
+ cmd);
+ label = gtk_label_new (str);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+ gtk_widget_show (label);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4);
+ g_free (str);
+
+ checkbox = gtk_check_button_new_with_label
+ (_("Do not ask me about this program again."));
+ gtk_widget_show (checkbox);
+ gtk_box_pack_start (GTK_BOX (container), checkbox, TRUE, TRUE, 4);
+
+ /* Run the dialog */
+ btn = gtk_dialog_run (GTK_DIALOG (dialog));
+ if (btn == GTK_RESPONSE_OK &&
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox)))
+ config_data_save_blessed_program (url);
+ gtk_widget_destroy (dialog);
+
+ return (btn == GTK_RESPONSE_OK);
+}
+
+static void
+procedure_notification (time_t trigger,
+ CompQueuedAlarms *cqa,
+ gpointer alarm_id)
+{
+ QueuedAlarm *qa;
+ ECalComponent *comp;
+ ECalComponentAlarm *alarm;
+ ECalComponentText description;
+ icalattach *attach;
+ const gchar *url;
+ gchar *cmd;
+ gboolean result = TRUE;
+
+ debug (("..."));
+
+ comp = cqa->alarms->comp;
+ qa = lookup_queued_alarm (cqa, alarm_id);
+ if (!qa)
+ return;
+
+ alarm = e_cal_component_get_alarm (comp, qa->instance->auid);
+ g_return_if_fail (alarm != NULL);
+
+ e_cal_component_alarm_get_attach (alarm, &attach);
+ e_cal_component_alarm_get_description (alarm, &description);
+ e_cal_component_alarm_free (alarm);
+
+ /* If the alarm has no attachment, simply display a notification dialog. */
+ if (!attach)
+ goto fallback;
+
+ if (!icalattach_get_is_url (attach)) {
+ icalattach_unref (attach);
+ goto fallback;
+ }
+
+ url = icalattach_get_url (attach);
+ g_return_if_fail (url != NULL);
+
+ /* Ask for confirmation before executing the stuff */
+ if (description.value)
+ cmd = g_strconcat (url, " ", description.value, NULL);
+ else
+ cmd = (gchar *) url;
+
+ if (procedure_notification_dialog (cmd, url))
+ result = g_spawn_command_line_async (cmd, NULL);
+
+ if (cmd != (gchar *) url)
+ g_free (cmd);
+
+ icalattach_unref (attach);
+
+ /* Fall back to display notification if we got an error */
+ if (result == FALSE)
+ goto fallback;
+
+ return;
+
+ fallback:
+
+ display_notification (trigger, cqa, alarm_id, FALSE);
+}
+
+static gboolean
+check_midnight_refresh (gpointer user_data)
+{
+ time_t new_midnight;
+ icaltimezone *zone;
+
+ debug (("..."));
+
+ zone = config_data_get_timezone ();
+ new_midnight = time_day_end_with_zone (time (NULL), zone);
+
+ if (new_midnight > midnight) {
+ struct _midnight_refresh_msg *msg;
+
+ msg = g_slice_new0 (struct _midnight_refresh_msg);
+ msg->header.func = (MessageFunc) midnight_refresh_async;
+ msg->remove = FALSE;
+
+ message_push ((Message *) msg);
+ }
+
+ return TRUE;
+}
+
+/**
+ * alarm_queue_init:
+ *
+ * Initializes the alarm queueing system. This should be called near the
+ * beginning of the program.
+ **/
+void
+alarm_queue_init (gpointer data)
+{
+ an = data;
+ g_return_if_fail (alarm_queue_inited == FALSE);
+
+ debug (("..."));
+
+ client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+ queue_midnight_refresh ();
+
+ if (config_data_get_last_notification_time (NULL) == -1) {
+ time_t tmval = time_day_begin (time (NULL));
+ debug (("Setting last notification time to %s", e_ctime (&tmval)));
+ config_data_set_last_notification_time (NULL, tmval);
+ }
+
+ /* install timeout handler (every 30 mins) for not missing the midnight refresh */
+ g_timeout_add_seconds (1800, (GSourceFunc) check_midnight_refresh, NULL);
+
+#ifdef HAVE_LIBNOTIFY
+ notify_init("Evolution Alarms");
+#endif
+
+ alarm_queue_inited = TRUE;
+}
+
+static gboolean
+free_client_alarms_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ ClientAlarms *ca = value;
+
+ debug (("ca=%p", ca));
+
+ if (ca) {
+ remove_client_alarms (ca);
+ if (ca->cal_client) {
+ debug (("Disconnecting Client"));
+
+ g_signal_handlers_disconnect_matched (ca->cal_client, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, ca);
+ g_object_unref (ca->cal_client);
+ }
+
+ if (ca->view) {
+ debug (("Disconnecting Query"));
+
+ g_signal_handlers_disconnect_matched (ca->view, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, ca);
+ g_object_unref (ca->view);
+ }
+
+ g_hash_table_destroy (ca->uid_alarms_hash);
+
+ g_free (ca);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * alarm_queue_done:
+ *
+ * Shuts down the alarm queueing system. This should be called near the end
+ * of the program. All the monitored calendar clients should already have been
+ * unregistered with alarm_queue_remove_client().
+ **/
+void
+alarm_queue_done (void)
+{
+ g_return_if_fail (alarm_queue_inited);
+
+ /* All clients must be unregistered by now */
+ g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0);
+
+ debug (("..."));
+
+ g_hash_table_foreach_remove (
+ client_alarms_hash, (GHRFunc) free_client_alarms_cb, NULL);
+ g_hash_table_destroy (client_alarms_hash);
+ client_alarms_hash = NULL;
+
+ if (midnight_refresh_id != NULL) {
+ alarm_remove (midnight_refresh_id);
+ midnight_refresh_id = NULL;
+ }
+
+ alarm_queue_inited = FALSE;
+}
+
+static gboolean
+compare_ids (gpointer a,
+ gpointer b)
+{
+ ECalComponentId *id, *id1;
+
+ id = a;
+ id1 = b;
+ if (id->uid != NULL && id1->uid != NULL) {
+ if (g_str_equal (id->uid, id1->uid)) {
+
+ if (id->rid && id1->rid)
+ return g_str_equal (id->rid, id1->rid);
+ else if (!(id->rid && id1->rid))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static guint
+hash_ids (gpointer a)
+{
+ ECalComponentId *id =a;
+
+ return g_str_hash (id->uid);
+}
+
+struct _alarm_client_msg {
+ Message header;
+ ECalClient *cal_client;
+};
+
+static void
+alarm_queue_add_async (struct _alarm_client_msg *msg)
+{
+ ClientAlarms *ca;
+ ECalClient *cal_client = msg->cal_client;
+
+ g_return_if_fail (alarm_queue_inited);
+ g_return_if_fail (cal_client != NULL);
+ g_return_if_fail (E_IS_CAL_CLIENT (cal_client));
+
+ ca = lookup_client (cal_client);
+ if (ca) {
+ /* We already have it. Unref the passed one*/
+ g_object_unref (cal_client);
+ return;
+ }
+
+ debug (("client=%p", cal_client));
+
+ ca = g_new (ClientAlarms, 1);
+
+ ca->cal_client = cal_client;
+ ca->view = NULL;
+
+ g_hash_table_insert (client_alarms_hash, cal_client, ca);
+
+ ca->uid_alarms_hash = g_hash_table_new (
+ (GHashFunc) hash_ids, (GEqualFunc) compare_ids);
+
+ load_alarms_for_today (ca);
+
+ g_slice_free (struct _alarm_client_msg, msg);
+}
+
+/**
+ * alarm_queue_add_client:
+ * @cal_client: A calendar client.
+ *
+ * Adds a calendar client to the alarm queueing system. Alarm trigger
+ * notifications will be presented at the appropriate times. The client should
+ * be removed with alarm_queue_remove_client() when receiving notifications
+ * from it is no longer desired.
+ *
+ * A client can be added any number of times to the alarm queueing system,
+ * but any single alarm trigger will only be presented once for a particular
+ * client. The client must still be removed the same number of times from the
+ * queueing system when it is no longer wanted.
+ **/
+void
+alarm_queue_add_client (ECalClient *cal_client)
+{
+ struct _alarm_client_msg *msg;
+
+ msg = g_slice_new0 (struct _alarm_client_msg);
+ msg->header.func = (MessageFunc) alarm_queue_add_async;
+ msg->cal_client = g_object_ref (cal_client);
+
+ message_push ((Message *) msg);
+}
+
+/* Removes a component an its alarms */
+static void
+remove_cqa (ClientAlarms *ca,
+ ECalComponentId *id,
+ CompQueuedAlarms *cqa)
+{
+
+ /* If a component is present, then it means we must have alarms queued
+ * for it.
+ */
+ g_return_if_fail (cqa->queued_alarms != NULL);
+
+ debug (("removing %d alarms", g_slist_length (cqa->queued_alarms)));
+ remove_alarms (cqa, TRUE);
+}
+
+static gboolean
+remove_comp_by_id (gpointer key,
+ gpointer value,
+ gpointer userdata)
+{
+
+ ClientAlarms *ca = (ClientAlarms *) userdata;
+
+ debug (("..."));
+
+/* if (!g_hash_table_size (ca->uid_alarms_hash)) */
+/* return; */
+
+ remove_cqa (ca, (ECalComponentId *) key, (CompQueuedAlarms *) value);
+
+ return TRUE;
+}
+
+/* Removes all the alarms queued for a particular calendar client */
+static void
+remove_client_alarms (ClientAlarms *ca)
+{
+ debug (("size %d", g_hash_table_size (ca->uid_alarms_hash)));
+
+ g_hash_table_foreach_remove (
+ ca->uid_alarms_hash, (GHRFunc) remove_comp_by_id, ca);
+
+ /* The hash table should be empty now */
+ g_return_if_fail (g_hash_table_size (ca->uid_alarms_hash) == 0);
+}
+
+/**
+ * alarm_queue_remove_client:
+ * @client: A calendar client.
+ *
+ * Removes a calendar client from the alarm queueing system.
+ **/
+static void
+alarm_queue_remove_async (struct _alarm_client_msg *msg)
+{
+ ClientAlarms *ca;
+ ECalClient *cal_client = msg->cal_client;
+
+ g_return_if_fail (alarm_queue_inited);
+ g_return_if_fail (cal_client != NULL);
+ g_return_if_fail (E_IS_CAL_CLIENT (cal_client));
+
+ ca = lookup_client (cal_client);
+ g_return_if_fail (ca != NULL);
+
+ debug (("..."));
+ remove_client_alarms (ca);
+
+ /* Clean up */
+ if (ca->cal_client) {
+ debug (("Disconnecting Client"));
+
+ g_signal_handlers_disconnect_matched (ca->cal_client, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, ca);
+ g_object_unref (ca->cal_client);
+ ca->cal_client = NULL;
+ }
+
+ if (ca->view) {
+ debug (("Disconnecting Query"));
+
+ g_signal_handlers_disconnect_matched (ca->view, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, ca);
+ g_object_unref (ca->view);
+ ca->view = NULL;
+ }
+
+ g_hash_table_destroy (ca->uid_alarms_hash);
+ ca->uid_alarms_hash = NULL;
+
+ g_free (ca);
+
+ g_hash_table_remove (client_alarms_hash, cal_client);
+
+ g_slice_free (struct _alarm_client_msg, msg);
+}
+
+/** alarm_queue_remove_client
+ *
+ * asynchronously remove client from alarm queue.
+ * @cal_client: Client to remove.
+ * @immediately: Indicates whether use thread or do it right now.
+ */
+
+void
+alarm_queue_remove_client (ECalClient *cal_client,
+ gboolean immediately)
+{
+ struct _alarm_client_msg *msg;
+
+ msg = g_slice_new0 (struct _alarm_client_msg);
+ msg->header.func = (MessageFunc) alarm_queue_remove_async;
+ msg->cal_client = cal_client;
+
+ if (immediately) {
+ alarm_queue_remove_async (msg);
+ } else
+ message_push ((Message *) msg);
+}
+
+/* Update non-time related variables for various structures on modification
+ * of an existing component to be called only from query_objects_changed_cb */
+static void
+update_cqa (CompQueuedAlarms *cqa,
+ ECalComponent *newcomp)
+{
+ ECalComponent *oldcomp;
+ ECalComponentAlarms *alarms = NULL;
+ GSList *qa_list; /* List of current QueuedAlarms corresponding to cqa */
+ time_t from, to;
+ icaltimezone *zone;
+ ECalComponentAlarmAction omit[] = {-1};
+
+ oldcomp = cqa->alarms->comp;
+
+ zone = config_data_get_timezone ();
+ from = time_day_begin_with_zone (time (NULL), zone);
+ to = time_day_end_with_zone (time (NULL), zone);
+
+ debug (("Generating alarms between %s and %s", e_ctime (&from), e_ctime (&to)));
+ alarms = e_cal_util_generate_alarms_for_comp (
+ newcomp, from, to, omit, e_cal_client_resolve_tzid_cb,
+ cqa->parent_client->cal_client, zone);
+
+ /* Update auids in Queued Alarms*/
+ for (qa_list = cqa->queued_alarms; qa_list; qa_list = qa_list->next) {
+ QueuedAlarm *qa = qa_list->data;
+ gchar *check_auid = (gchar *) qa->instance->auid;
+ ECalComponentAlarm *alarm;
+
+ alarm = e_cal_component_get_alarm (newcomp, check_auid);
+ if (alarm) {
+ e_cal_component_alarm_free (alarm);
+ continue;
+ } else {
+ alarm = e_cal_component_get_alarm (oldcomp, check_auid);
+ if (alarm) { /* Need to update QueuedAlarms */
+ e_cal_component_alarm_free (alarm);
+ if (alarms == NULL) {
+ debug (("No alarms found in the modified component"));
+ break;
+ }
+ update_qa (alarms, qa);
+ }
+ else
+ g_warning ("Failed in auid lookup for old component also\n");
+ }
+ }
+
+ /* Update the actual component stored in CompQueuedAlarms structure */
+ g_object_unref (cqa->alarms->comp);
+ cqa->alarms->comp = newcomp;
+ if (alarms != NULL )
+ e_cal_component_alarms_free (alarms);
+}
+
+static void
+update_qa (ECalComponentAlarms *alarms,
+ QueuedAlarm *qa)
+{
+ ECalComponentAlarmInstance *al_inst;
+ GSList *instance_list;
+
+ debug (("..."));
+ for (instance_list = alarms->alarms;
+ instance_list;
+ instance_list = instance_list->next) {
+ al_inst = instance_list->data;
+ /* FIXME If two or more alarm instances (audio, note)
+ * for same component have same trigger... */
+ if (al_inst->trigger == qa->orig_trigger) {
+ g_free ((gchar *) qa->instance->auid);
+ qa->instance->auid = g_strdup (al_inst->auid);
+ break;
+ }
+ }
+}