diff options
Diffstat (limited to 'calendar/gui/alarm-notify.c')
-rw-r--r-- | calendar/gui/alarm-notify.c | 511 |
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); +} |