aboutsummaryrefslogtreecommitdiffstats
path: root/calendar/gui/alarm-notify.c
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/gui/alarm-notify.c')
-rw-r--r--calendar/gui/alarm-notify.c511
1 files changed, 511 insertions, 0 deletions
diff --git a/calendar/gui/alarm-notify.c b/calendar/gui/alarm-notify.c
new file mode 100644
index 0000000000..6f0e8e6759
--- /dev/null
+++ b/calendar/gui/alarm-notify.c
@@ -0,0 +1,511 @@
+/* Evolution calendar - Alarm notification engine
+ *
+ * Copyright (C) 2000 Helix Code, Inc.
+ *
+ * Authors: Federico Mena-Quintero <federico@helixcode.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtksignal.h>
+#include <cal-util/timeutil.h>
+#include "alarm.h"
+#include "alarm-notify.h"
+
+
+
+/* Whether the notification system has been initialized */
+static gboolean alarm_notify_inited;
+
+/* Clients we are monitoring for alarms */
+static GHashTable *client_alarms_hash = NULL;
+
+/* Structure that stores a client we are monitoring */
+typedef struct {
+ /* Monitored client */
+ CalClient *client;
+
+ /* Number of times this client has been registered */
+ int refcount;
+
+ /* 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 CalComponentAlarms and the mapping from queued alarm IDs to the
+ * actual alarm instance structures.
+ */
+typedef struct {
+ /* The parent client alarms structure */
+ ClientAlarms *parent_client;
+
+ /* The actual component and its alarm instances */
+ CalComponentAlarms *alarms;
+
+ /* List of QueuedAlarm structures */
+ GSList *queued_alarms;
+} 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 CompAlarms->alarms list */
+ CalAlarmInstance *instance;
+} QueuedAlarm;
+
+/* Alarm ID for the midnight refresh function */
+static gpointer midnight_refresh_id = NULL;
+
+
+
+static void load_alarms (ClientAlarms *ca);
+static void midnight_refresh_cb (gpointer alarm_id, time_t trigger, gpointer data);
+
+/* Queues an alarm trigger for midnight so that we can load the next day's worth
+ * of alarms.
+ */
+static void
+queue_midnight_refresh (void)
+{
+ time_t midnight;
+
+ g_assert (midnight_refresh_id == NULL);
+
+ midnight = time_day_end (time (NULL));
+
+ midnight_refresh_id = alarm_add (midnight, midnight_refresh_cb, NULL, NULL);
+ if (!midnight_refresh_id) {
+ g_message ("alarm_notify_init(): Could not set up 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;
+
+ ca = value;
+ load_alarms (ca);
+}
+
+/* Loads the alarms for the new day every midnight */
+static void
+midnight_refresh_cb (gpointer alarm_id, time_t trigger, gpointer data)
+{
+ /* Re-load the alarms for all clients */
+
+ g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL);
+
+ /* Re-schedule the midnight update */
+
+ midnight_refresh_id = NULL;
+ queue_midnight_refresh ();
+}
+
+/* Looks up a client in the client alarms hash table */
+static ClientAlarms *
+lookup_client (CalClient *client)
+{
+ return g_hash_table_lookup (client_alarms_hash, client);
+}
+
+/* Callback used when an alarm triggers */
+static void
+alarm_trigger_cb (gpointer alarm_id, time_t trigger, gpointer data)
+{
+ CompQueuedAlarms *cqa;
+
+ cqa = data;
+
+ /* FIXME */
+
+ g_message ("alarm_trigger_cb(): Triggered!");
+}
+
+/* Callback used when an alarm must be destroyed */
+static void
+alarm_destroy_cb (gpointer alarm_id, gpointer data)
+{
+ CompQueuedAlarms *cqa;
+ GSList *l;
+ QueuedAlarm *qa;
+ const char *uid;
+
+ cqa = data;
+
+ qa = NULL; /* Keep GCC happy */
+
+ /* Find the alarm in the queued alarms */
+
+ for (l = cqa->queued_alarms; l; l = l->next) {
+ qa = l->data;
+ if (qa->alarm_id == alarm_id)
+ break;
+ }
+
+ g_assert (l != NULL);
+
+ /* Remove it and free it */
+
+ cqa->queued_alarms = g_slist_remove_link (cqa->queued_alarms, l);
+ g_slist_free_1 (l);
+
+ g_free (qa);
+
+ /* If this was the last queued alarm for this component, remove the
+ * component itself.
+ */
+
+ if (cqa->queued_alarms != NULL)
+ return;
+
+ cal_component_get_uid (cqa->alarms->comp, &uid);
+ g_hash_table_remove (cqa->parent_client->uid_alarms_hash, uid);
+ cqa->parent_client = NULL;
+
+ cal_component_alarms_free (cqa->alarms);
+ cqa->alarms = NULL;
+
+ g_free (cqa);
+}
+
+/* Adds the alarms in a CalComponentAlarms 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, CalComponentAlarms *alarms)
+{
+ const char *uid;
+ CompQueuedAlarms *cqa;
+ GSList *l;
+
+ cqa = g_new (CompQueuedAlarms, 1);
+ cqa->parent_client = ca;
+ cqa->alarms = alarms;
+
+ cqa->queued_alarms = NULL;
+
+ for (l = alarms->alarms; l; l = l->next) {
+ CalAlarmInstance *instance;
+ gpointer alarm_id;
+ QueuedAlarm *qa;
+
+ instance = l->data;
+
+ alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, alarm_destroy_cb);
+ if (!alarm_id) {
+ g_message ("add_component_alarms(): Could not schedule a trigger for "
+ "%ld, discarding...", (long) instance->trigger);
+ continue;
+ }
+
+ qa = g_new (QueuedAlarm, 1);
+ qa->alarm_id = alarm_id;
+ qa->instance = instance;
+
+ cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa);
+ }
+
+ cal_component_get_uid (alarms->comp, &uid);
+
+ /* If we failed to add all the alarms, then we should get rid of the cqa */
+ if (cqa->queued_alarms == NULL) {
+ g_message ("add_component_alarms(): Could not add any of the alarms "
+ "for the component `%s'; discarding it...", uid);
+
+ cal_component_alarms_free (cqa->alarms);
+ cqa->alarms = NULL;
+
+ g_free (cqa);
+ return;
+ }
+
+ cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms);
+ g_hash_table_insert (ca->uid_alarms_hash, (char *) uid, cqa);
+}
+
+/* Loads today's remaining alarms for a client */
+static void
+load_alarms (ClientAlarms *ca)
+{
+ time_t now, day_end;
+ GSList *comp_alarms;
+ GSList *l;
+
+ now = time (NULL);
+ day_end = time_day_end (now);
+
+ comp_alarms = cal_client_get_alarms_in_range (ca->client, now, day_end);
+
+ /* All of the last day's alarms should have already triggered and should
+ * have been removed, so we should have no pending components.
+ */
+ g_assert (g_hash_table_size (ca->uid_alarms_hash) == 0);
+
+ for (l = comp_alarms; l; l = l->next) {
+ CalComponentAlarms *alarms;
+
+ alarms = l->data;
+ add_component_alarms (ca, alarms);
+ }
+
+ g_slist_free (comp_alarms);
+}
+
+/* Called when a calendar client finished loading; we load its alarms */
+static void
+cal_loaded_cb (CalClient *client, CalClientLoadStatus status, gpointer data)
+{
+ ClientAlarms *ca;
+
+ ca = data;
+
+ if (status != CAL_CLIENT_LOAD_SUCCESS)
+ return;
+
+ load_alarms (ca);
+}
+
+/* Looks up a component's queued alarm structure in a client alarms structure */
+static CompQueuedAlarms *
+lookup_comp_queued_alarms (ClientAlarms *ca, const char *uid)
+{
+ return g_hash_table_lookup (ca->uid_alarms_hash, uid);
+}
+
+/* Removes a component an its alarms */
+static void
+remove_comp (ClientAlarms *ca, const char *uid)
+{
+ CompQueuedAlarms *cqa;
+ GSList *l;
+
+ cqa = lookup_comp_queued_alarms (ca, uid);
+ if (!cqa)
+ return;
+
+ /* If a component is present, then it means we must have alarms queued
+ * for it.
+ */
+ g_assert (cqa->queued_alarms != NULL);
+
+ for (l = cqa->queued_alarms; l;) {
+ QueuedAlarm *qa;
+
+ qa = l->data;
+
+ /* Get the next element here because the list element will go
+ * away. Also, we do not free the qa here because it will be
+ * freed by the destroy notification function.
+ */
+ l = l->next;
+
+ alarm_remove (qa->alarm_id);
+ }
+
+ /* The list should be empty now, and thus the queued component alarms
+ * structure should have been freed and removed from the hash table.
+ */
+ g_assert (lookup_comp_queued_alarms (ca, uid) == NULL);
+}
+
+/* Called when a calendar component changes; we must reload its corresponding
+ * alarms.
+ */
+static void
+obj_updated_cb (CalClient *client, const char *uid, gpointer data)
+{
+ ClientAlarms *ca;
+ time_t now, day_end;
+ CalComponentAlarms *alarms;
+ gboolean found;
+
+ ca = data;
+
+ remove_comp (ca, uid);
+
+ now = time (NULL);
+ day_end = time_day_end (now);
+
+ found = cal_client_get_alarms_for_object (ca->client, uid, now, day_end, &alarms);
+
+ if (!found)
+ return;
+
+ add_component_alarms (ca, alarms);
+}
+
+/* Called when a calendar component is removed; we must delete its corresponding
+ * alarms.
+ */
+static void
+obj_removed_cb (CalClient *client, const char *uid, gpointer data)
+{
+ ClientAlarms *ca;
+
+ ca = data;
+
+ remove_comp (ca, uid);
+}
+
+
+
+/**
+ * alarm_notify_init:
+ *
+ * Initializes the alarm notification system. This should be called near the
+ * beginning of the program, after calling alarm_init().
+ **/
+void
+alarm_notify_init (void)
+{
+ g_return_if_fail (alarm_notify_inited == FALSE);
+
+ client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+ queue_midnight_refresh ();
+
+ alarm_notify_inited = TRUE;
+}
+
+/**
+ * alarm_notify_done:
+ *
+ * Shuts down the alarm notification system. This should be called near the end
+ * of the program. All the monitored calendar clients should already have been
+ * unregistered with alarm_notify_remove_client().
+ **/
+void
+alarm_notify_done (void)
+{
+ g_return_if_fail (alarm_notify_inited);
+
+ /* All clients must be unregistered by now */
+ g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0);
+
+ g_hash_table_destroy (client_alarms_hash);
+ client_alarms_hash = NULL;
+
+ g_assert (midnight_refresh_id != NULL);
+ alarm_remove (midnight_refresh_id);
+ midnight_refresh_id = NULL;
+
+ alarm_notify_inited = FALSE;
+}
+
+/**
+ * alarm_notify_add_client:
+ * @client: A calendar client.
+ *
+ * Adds a calendar client to the alarm notification system. Alarm trigger
+ * notifications will be presented at the appropriate times. The client should
+ * be removed with alarm_notify_remove_client() when receiving notifications
+ * from it is no longer desired.
+ *
+ * A client can be added any number of times to the alarm notification 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
+ * notification system when it is no longer wanted.
+ **/
+void
+alarm_notify_add_client (CalClient *client)
+{
+ ClientAlarms *ca;
+
+ g_return_if_fail (alarm_notify_inited);
+ g_return_if_fail (client != NULL);
+ g_return_if_fail (IS_CAL_CLIENT (client));
+
+ ca = lookup_client (client);
+ if (ca) {
+ ca->refcount++;
+ return;
+ }
+
+ ca = g_new (ClientAlarms, 1);
+
+ ca->client = client;
+ gtk_object_ref (GTK_OBJECT (ca->client));
+
+ ca->refcount = 1;
+ g_hash_table_insert (client_alarms_hash, client, ca);
+
+ ca->uid_alarms_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ if (!cal_client_is_loaded (client))
+ gtk_signal_connect (GTK_OBJECT (client), "cal_loaded",
+ GTK_SIGNAL_FUNC (cal_loaded_cb), ca);
+
+ gtk_signal_connect (GTK_OBJECT (client), "obj_updated",
+ GTK_SIGNAL_FUNC (obj_updated_cb), ca);
+ gtk_signal_connect (GTK_OBJECT (client), "obj_removed",
+ GTK_SIGNAL_FUNC (obj_removed_cb), ca);
+
+ if (cal_client_is_loaded (client))
+ load_alarms (ca);
+}
+
+/**
+ * alarm_notify_remove_client:
+ * @client: A calendar client.
+ *
+ * Removes a calendar client from the alarm notification system.
+ **/
+void
+alarm_notify_remove_client (CalClient *client)
+{
+ ClientAlarms *ca;
+
+ g_return_if_fail (alarm_notify_inited);
+ g_return_if_fail (client != NULL);
+ g_return_if_fail (IS_CAL_CLIENT (client));
+
+ ca = lookup_client (client);
+ g_return_if_fail (ca != NULL);
+
+ g_assert (ca->refcount > 0);
+ ca->refcount--;
+
+ if (ca->refcount > 0)
+ return;
+
+ /* FIXME: remove alarms */
+
+ /* Clean up */
+
+ gtk_signal_disconnect_by_data (GTK_OBJECT (ca->client), ca);
+
+ gtk_object_unref (GTK_OBJECT (ca->client));
+ ca->client = NULL;
+
+ g_hash_table_destroy (ca->uid_alarms_hash);
+ ca->uid_alarms_hash = NULL;
+
+ g_free (ca);
+
+ g_hash_table_remove (client_alarms_hash, client);
+}