/* Evolution calendar - iCalendar file backend * * Copyright (C) 2000 Ximian, Inc. * Copyright (C) 2000 Ximian, Inc. * * Author: Federico Mena-Quintero * * 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. */ #include #include #include "e-util/e-dbhash.h" #include "cal-util/cal-recur.h" #include "cal-util/cal-util.h" #include "cal-backend-file.h" /* A category that exists in some of the objects of the calendar */ typedef struct { /* Category name, also used as the key in the categories hash table */ char *name; /* Number of objects that have this category */ int refcount; } Category; /* Private part of the CalBackendFile structure */ struct _CalBackendFilePrivate { /* URI where the calendar data is stored */ GnomeVFSURI *uri; /* List of Cal objects with their listeners */ GList *clients; /* Toplevel VCALENDAR component */ icalcomponent *icalcomp; /* All the CalComponent objects in the calendar, hashed by UID. The * hash key *is* the uid returned by cal_component_get_uid(); it is not * copied, so don't free it when you remove an object from the hash * table. */ GHashTable *comp_uid_hash; /* All event, to-do, and journal components in the calendar; they are * here just for easy access (i.e. so that you don't have to iterate * over the comp_uid_hash). If you need *all* the components in the * calendar, iterate over the hash instead. */ GList *events; GList *todos; GList *journals; /* Hash table of live categories */ GHashTable *categories; /* Idle handler for saving the calendar when it is dirty */ guint idle_id; }; static void cal_backend_file_class_init (CalBackendFileClass *class); static void cal_backend_file_init (CalBackendFile *cbfile); static void cal_backend_file_destroy (GtkObject *object); static GnomeVFSURI *cal_backend_file_get_uri (CalBackend *backend); static void cal_backend_file_add_cal (CalBackend *backend, Cal *cal); static CalBackendOpenStatus cal_backend_file_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exists); static gboolean cal_backend_file_is_loaded (CalBackend *backend); static int cal_backend_file_get_n_objects (CalBackend *backend, CalObjType type); static char *cal_backend_file_get_object (CalBackend *backend, const char *uid); static char *cal_backend_file_get_timezone_object (CalBackend *backend, const char *tzid); static CalObjType cal_backend_file_get_type_by_uid (CalBackend *backend, const char *uid); static GList *cal_backend_file_get_uids (CalBackend *backend, CalObjType type); static GList *cal_backend_file_get_objects_in_range (CalBackend *backend, CalObjType type, time_t start, time_t end); static char *cal_backend_file_get_free_busy (CalBackend *backend, time_t start, time_t end); static GNOME_Evolution_Calendar_CalObjChangeSeq *cal_backend_file_get_changes ( CalBackend *backend, CalObjType type, const char *change_id); static GNOME_Evolution_Calendar_CalComponentAlarmsSeq *cal_backend_file_get_alarms_in_range ( CalBackend *backend, time_t start, time_t end); static GNOME_Evolution_Calendar_CalComponentAlarms *cal_backend_file_get_alarms_for_object ( CalBackend *backend, const char *uid, time_t start, time_t end, gboolean *object_found); static gboolean cal_backend_file_update_objects (CalBackend *backend, const char *calobj); static gboolean cal_backend_file_remove_object (CalBackend *backend, const char *uid); static icaltimezone* cal_backend_file_get_timezone (CalBackend *backend, const char *tzid); static CalBackendClass *parent_class; /** * cal_backend_file_get_type: * @void: * * Registers the #CalBackendFile class if necessary, and returns the type ID * associated to it. * * Return value: The type ID of the #CalBackendFile class. **/ GtkType cal_backend_file_get_type (void) { static GtkType cal_backend_file_type = 0; if (!cal_backend_file_type) { static const GtkTypeInfo cal_backend_file_info = { "CalBackendFile", sizeof (CalBackendFile), sizeof (CalBackendFileClass), (GtkClassInitFunc) cal_backend_file_class_init, (GtkObjectInitFunc) cal_backend_file_init, NULL, /* reserved_1 */ NULL, /* reserved_2 */ (GtkClassInitFunc) NULL }; cal_backend_file_type = gtk_type_unique (CAL_BACKEND_TYPE, &cal_backend_file_info); } return cal_backend_file_type; } /* Class initialization function for the file backend */ static void cal_backend_file_class_init (CalBackendFileClass *class) { GtkObjectClass *object_class; CalBackendClass *backend_class; object_class = (GtkObjectClass *) class; backend_class = (CalBackendClass *) class; parent_class = gtk_type_class (CAL_BACKEND_TYPE); object_class->destroy = cal_backend_file_destroy; backend_class->get_uri = cal_backend_file_get_uri; backend_class->add_cal = cal_backend_file_add_cal; backend_class->open = cal_backend_file_open; backend_class->is_loaded = cal_backend_file_is_loaded; backend_class->get_n_objects = cal_backend_file_get_n_objects; backend_class->get_object = cal_backend_file_get_object; backend_class->get_timezone_object = cal_backend_file_get_timezone_object; backend_class->get_type_by_uid = cal_backend_file_get_type_by_uid; backend_class->get_uids = cal_backend_file_get_uids; backend_class->get_objects_in_range = cal_backend_file_get_objects_in_range; backend_class->get_free_busy = cal_backend_file_get_free_busy; backend_class->get_changes = cal_backend_file_get_changes; backend_class->get_alarms_in_range = cal_backend_file_get_alarms_in_range; backend_class->get_alarms_for_object = cal_backend_file_get_alarms_for_object; backend_class->update_objects = cal_backend_file_update_objects; backend_class->remove_object = cal_backend_file_remove_object; backend_class->get_timezone = cal_backend_file_get_timezone; } /* Object initialization function for the file backend */ static void cal_backend_file_init (CalBackendFile *cbfile) { CalBackendFilePrivate *priv; priv = g_new0 (CalBackendFilePrivate, 1); cbfile->priv = priv; priv->uri = NULL; priv->clients = NULL; priv->icalcomp = NULL; priv->comp_uid_hash = NULL; priv->events = NULL; priv->todos = NULL; priv->journals = NULL; priv->categories = g_hash_table_new (g_str_hash, g_str_equal); } /* g_hash_table_foreach() callback to destroy a CalComponent */ static void free_cal_component (gpointer key, gpointer value, gpointer data) { CalComponent *comp; comp = CAL_COMPONENT (value); gtk_object_unref (GTK_OBJECT (comp)); } /* Saves the calendar data */ static void save (CalBackendFile *cbfile) { CalBackendFilePrivate *priv; GnomeVFSHandle *handle = NULL; GnomeVFSResult result; GnomeVFSFileSize out; gchar *tmp; char *buf; priv = cbfile->priv; g_assert (priv->uri != NULL); g_assert (priv->icalcomp != NULL); /* Make a backup copy of the file if it exists */ tmp = gnome_vfs_uri_to_string (priv->uri, GNOME_VFS_URI_HIDE_NONE); if (tmp) { GnomeVFSURI *backup_uri; gchar *backup_uristr; backup_uristr = g_strconcat (tmp, "~", NULL); backup_uri = gnome_vfs_uri_new (backup_uristr); result = gnome_vfs_move_uri (priv->uri, backup_uri, TRUE); gnome_vfs_uri_unref (backup_uri); g_free (tmp); g_free (backup_uristr); } /* Now write the new file out */ result = gnome_vfs_create_uri (&handle, priv->uri, GNOME_VFS_OPEN_WRITE, FALSE, 0666); if (result != GNOME_VFS_OK) goto error; buf = icalcomponent_as_ical_string (priv->icalcomp); result = gnome_vfs_write (handle, buf, strlen (buf) * sizeof (char), &out); if (result != GNOME_VFS_OK) goto error; gnome_vfs_close (handle); return; error: g_warning ("Error writing calendar file."); return; } /* Used from g_hash_table_foreach(), frees a Category structure */ static void free_category_cb (gpointer key, gpointer value, gpointer data) { Category *c; c = value; g_free (c->name); g_free (c); } /* Destroy handler for the file backend */ static void cal_backend_file_destroy (GtkObject *object) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; g_return_if_fail (object != NULL); g_return_if_fail (IS_CAL_BACKEND_FILE (object)); cbfile = CAL_BACKEND_FILE (object); priv = cbfile->priv; g_assert (priv->clients == NULL); /* Save if necessary */ if (priv->idle_id != 0) { save (cbfile); g_source_remove (priv->idle_id); priv->idle_id = 0; } /* Clean up */ if (priv->uri) { gnome_vfs_uri_unref (priv->uri); priv->uri = NULL; } if (priv->comp_uid_hash) { g_hash_table_foreach (priv->comp_uid_hash, free_cal_component, NULL); g_hash_table_destroy (priv->comp_uid_hash); priv->comp_uid_hash = NULL; } g_list_free (priv->events); g_list_free (priv->todos); g_list_free (priv->journals); priv->events = NULL; priv->todos = NULL; priv->journals = NULL; g_hash_table_foreach (priv->categories, free_category_cb, NULL); g_hash_table_destroy (priv->categories); priv->categories = NULL; if (priv->icalcomp) { icalcomponent_free (priv->icalcomp); priv->icalcomp = NULL; } g_free (priv); cbfile->priv = NULL; if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } /* Looks up a component by its UID on the backend's component hash table */ static CalComponent * lookup_component (CalBackendFile *cbfile, const char *uid) { CalBackendFilePrivate *priv; CalComponent *comp; priv = cbfile->priv; comp = g_hash_table_lookup (priv->comp_uid_hash, uid); return comp; } /* Calendar backend methods */ /* Get_uri handler for the file backend */ static GnomeVFSURI * cal_backend_file_get_uri (CalBackend *backend) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_assert (priv->uri != NULL); return priv->uri; } /* Callback used when a Cal is destroyed */ static void cal_destroy_cb (GtkObject *object, gpointer data) { Cal *cal; Cal *lcal; CalBackendFile *cbfile; CalBackendFilePrivate *priv; GList *l; cal = CAL (object); cbfile = CAL_BACKEND_FILE (data); priv = cbfile->priv; /* Find the cal in the list of clients */ for (l = priv->clients; l; l = l->next) { lcal = CAL (l->data); if (lcal == cal) break; } g_assert (l != NULL); /* Disconnect */ priv->clients = g_list_remove_link (priv->clients, l); g_list_free_1 (l); /* When all clients go away, notify the parent factory about it so that * it may decide whether to kill the backend or not. */ if (!priv->clients) cal_backend_last_client_gone (CAL_BACKEND (cbfile)); } /* Used from g_hash_table_foreach(), adds a category name to the sequence */ static void add_category_cb (gpointer key, gpointer value, gpointer data) { Category *c; GNOME_Evolution_Calendar_StringSeq *seq; c = value; seq = data; seq->_buffer[seq->_length] = CORBA_string_dup (c->name); seq->_length++; } /* Notifies the clients with the current list of categories */ static void notify_categories_changed (CalBackendFile *cbfile) { CalBackendFilePrivate *priv; GNOME_Evolution_Calendar_StringSeq *seq; GList *l; priv = cbfile->priv; /* Build the sequence of category names */ seq = GNOME_Evolution_Calendar_StringSeq__alloc (); seq->_length = 0; seq->_maximum = g_hash_table_size (priv->categories); seq->_buffer = CORBA_sequence_CORBA_string_allocbuf (seq->_maximum); CORBA_sequence_set_release (seq, TRUE); g_hash_table_foreach (priv->categories, add_category_cb, seq); g_assert (seq->_length == seq->_maximum); /* Notify the clients */ for (l = priv->clients; l; l = l->next) { Cal *cal; cal = CAL (l->data); cal_notify_categories_changed (cal, seq); } CORBA_free (seq); } /* Add_cal handler for the file backend */ static void cal_backend_file_add_cal (CalBackend *backend, Cal *cal) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_if_fail (priv->icalcomp != NULL); g_return_if_fail (cal != NULL); g_return_if_fail (IS_CAL (cal)); /* We do not keep a reference to the Cal since the calendar user agent * owns it. */ gtk_signal_connect (GTK_OBJECT (cal), "destroy", GTK_SIGNAL_FUNC (cal_destroy_cb), backend); priv->clients = g_list_prepend (priv->clients, cal); /* Notify the client about changed categories so that it can populate * its lists. */ notify_categories_changed (cbfile); } /* Idle handler; we save the calendar since it is dirty */ static gboolean save_idle (gpointer data) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; cbfile = CAL_BACKEND_FILE (data); priv = cbfile->priv; g_assert (priv->icalcomp != NULL); save (cbfile); priv->idle_id = 0; return FALSE; } /* Marks the file backend as dirty and queues a save operation */ static void mark_dirty (CalBackendFile *cbfile) { CalBackendFilePrivate *priv; priv = cbfile->priv; if (priv->idle_id != 0) return; priv->idle_id = g_idle_add (save_idle, cbfile); } /* Checks if the specified component has a duplicated UID and if so changes it */ static void check_dup_uid (CalBackendFile *cbfile, CalComponent *comp) { CalBackendFilePrivate *priv; CalComponent *old_comp; const char *uid; char *new_uid; priv = cbfile->priv; cal_component_get_uid (comp, &uid); old_comp = g_hash_table_lookup (priv->comp_uid_hash, uid); if (!old_comp) return; /* Everything is fine */ g_message ("check_dup_uid(): Got object with duplicated UID `%s', changing it...", uid); new_uid = cal_component_gen_uid (); cal_component_set_uid (comp, new_uid); g_free (new_uid); /* FIXME: I think we need to reset the SEQUENCE property and reset the * CREATED/DTSTAMP/LAST-MODIFIED. */ mark_dirty (cbfile); } /* Updates the hash table of categories by adding or removing those in the * component. */ static void update_categories_from_comp (CalBackendFile *cbfile, CalComponent *comp, gboolean add) { CalBackendFilePrivate *priv; GSList *categories, *l; priv = cbfile->priv; cal_component_get_categories_list (comp, &categories); for (l = categories; l; l = l->next) { const char *name; Category *c; name = l->data; c = g_hash_table_lookup (priv->categories, name); if (add) { /* Add the category to the set */ if (c) c->refcount++; else { c = g_new (Category, 1); c->name = g_strdup (name); c->refcount = 1; g_hash_table_insert (priv->categories, c->name, c); } } else { /* Remove the category from the set --- it *must* have existed */ g_assert (c != NULL); g_assert (c->refcount > 0); c->refcount--; if (c->refcount == 0) { g_hash_table_remove (priv->categories, c->name); g_free (c->name); g_free (c); } } } cal_component_free_categories_list (categories); } /* Tries to add an icalcomponent to the file backend. We only store the objects * of the types we support; all others just remain in the toplevel component so * that we don't lose them. */ static void add_component (CalBackendFile *cbfile, CalComponent *comp, gboolean add_to_toplevel) { CalBackendFilePrivate *priv; GList **list; const char *uid; priv = cbfile->priv; switch (cal_component_get_vtype (comp)) { case CAL_COMPONENT_EVENT: list = &priv->events; break; case CAL_COMPONENT_TODO: list = &priv->todos; break; case CAL_COMPONENT_JOURNAL: list = &priv->journals; break; default: g_assert_not_reached (); return; } /* Ensure that the UID is unique; some broken implementations spit * components with duplicated UIDs. */ check_dup_uid (cbfile, comp); cal_component_get_uid (comp, &uid); g_hash_table_insert (priv->comp_uid_hash, (char *)uid, comp); *list = g_list_prepend (*list, comp); /* Put the object in the toplevel component if required */ if (add_to_toplevel) { icalcomponent *icalcomp; icalcomp = cal_component_get_icalcomponent (comp); g_assert (icalcomp != NULL); icalcomponent_add_component (priv->icalcomp, icalcomp); } /* Update the set of categories */ update_categories_from_comp (cbfile, comp, TRUE); } /* Removes a component from the backend's hash and lists. Does not perform * notification on the clients. Also removes the component from the toplevel * icalcomponent. */ static void remove_component (CalBackendFile *cbfile, CalComponent *comp) { CalBackendFilePrivate *priv; icalcomponent *icalcomp; const char *uid; GList **list, *l; priv = cbfile->priv; /* Remove the icalcomp from the toplevel */ icalcomp = cal_component_get_icalcomponent (comp); g_assert (icalcomp != NULL); icalcomponent_remove_component (priv->icalcomp, icalcomp); /* Remove it from our mapping */ cal_component_get_uid (comp, &uid); g_hash_table_remove (priv->comp_uid_hash, uid); switch (cal_component_get_vtype (comp)) { case CAL_COMPONENT_EVENT: list = &priv->events; break; case CAL_COMPONENT_TODO: list = &priv->todos; break; case CAL_COMPONENT_JOURNAL: list = &priv->journals; break; default: /* Make the compiler shut up. */ list = NULL; g_assert_not_reached (); } l = g_list_find (*list, comp); g_assert (l != NULL); *list = g_list_remove_link (*list, l); g_list_free_1 (l); /* Update the set of categories */ update_categories_from_comp (cbfile, comp, FALSE); gtk_object_unref (GTK_OBJECT (comp)); } /* Scans the toplevel VCALENDAR component and stores the objects it finds */ static void scan_vcalendar (CalBackendFile *cbfile) { CalBackendFilePrivate *priv; icalcompiter iter; priv = cbfile->priv; g_assert (priv->icalcomp != NULL); g_assert (priv->comp_uid_hash != NULL); for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_ANY_COMPONENT); icalcompiter_deref (&iter) != NULL; icalcompiter_next (&iter)) { icalcomponent *icalcomp; icalcomponent_kind kind; CalComponent *comp; icalcomp = icalcompiter_deref (&iter); kind = icalcomponent_isa (icalcomp); if (!(kind == ICAL_VEVENT_COMPONENT || kind == ICAL_VTODO_COMPONENT || kind == ICAL_VJOURNAL_COMPONENT)) continue; comp = cal_component_new (); if (!cal_component_set_icalcomponent (comp, icalcomp)) continue; add_component (cbfile, comp, FALSE); } } /* Callback used from icalparser_parse() */ static char * get_line_fn (char *s, size_t size, void *data) { FILE *file; file = data; return fgets (s, size, file); } /* Parses an open iCalendar file and returns a toplevel component with the contents */ static icalcomponent * parse_file (FILE *file) { icalparser *parser; icalcomponent *icalcomp; parser = icalparser_new (); icalparser_set_gen_data (parser, file); icalcomp = icalparser_parse (parser, get_line_fn); icalparser_free (parser); return icalcomp; } /* Parses an open iCalendar file and loads it into the backend */ static CalBackendOpenStatus open_cal (CalBackendFile *cbfile, GnomeVFSURI *uri, FILE *file) { CalBackendFilePrivate *priv; icalcomponent *icalcomp; priv = cbfile->priv; icalcomp = parse_file (file); if (fclose (file) != 0) { if (icalcomp) icalcomponent_free (icalcomp); return CAL_BACKEND_OPEN_ERROR; } if (!icalcomp) return CAL_BACKEND_OPEN_ERROR; /* FIXME: should we try to demangle XROOT components and * individual components as well? */ if (icalcomponent_isa (icalcomp) != ICAL_VCALENDAR_COMPONENT) { icalcomponent_free (icalcomp); return CAL_BACKEND_OPEN_ERROR; } priv->icalcomp = icalcomp; priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal); scan_vcalendar (cbfile); gnome_vfs_uri_ref (uri); priv->uri = uri; return CAL_BACKEND_OPEN_SUCCESS; } static CalBackendOpenStatus create_cal (CalBackendFile *cbfile, GnomeVFSURI *uri) { CalBackendFilePrivate *priv; priv = cbfile->priv; /* Create the new calendar information */ priv->icalcomp = cal_util_new_top_level (); /* Create our internal data */ priv->comp_uid_hash = g_hash_table_new (g_str_hash, g_str_equal); gnome_vfs_uri_ref (uri); priv->uri = uri; mark_dirty (cbfile); return CAL_BACKEND_OPEN_SUCCESS; } /* Open handler for the file backend */ static CalBackendOpenStatus cal_backend_file_open (CalBackend *backend, GnomeVFSURI *uri, gboolean only_if_exists) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; char *str_uri; FILE *file; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp == NULL, CAL_BACKEND_OPEN_ERROR); g_return_val_if_fail (uri != NULL, CAL_BACKEND_OPEN_ERROR); g_assert (priv->uri == NULL); g_assert (priv->comp_uid_hash == NULL); if (!gnome_vfs_uri_is_local (uri)) return CAL_BACKEND_OPEN_ERROR; str_uri = gnome_vfs_uri_to_string (uri, (GNOME_VFS_URI_HIDE_USER_NAME | GNOME_VFS_URI_HIDE_PASSWORD | GNOME_VFS_URI_HIDE_HOST_NAME | GNOME_VFS_URI_HIDE_HOST_PORT | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD)); /* Load! */ file = fopen (str_uri, "r"); g_free (str_uri); if (file) return open_cal (cbfile, uri, file); else { if (only_if_exists) return CAL_BACKEND_OPEN_NOT_FOUND; return create_cal (cbfile, uri); } } /* is_loaded handler for the file backend */ static gboolean cal_backend_file_is_loaded (CalBackend *backend) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; return (priv->icalcomp != NULL); } /* Get_n_objects handler for the file backend */ static int cal_backend_file_get_n_objects (CalBackend *backend, CalObjType type) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; int n; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, -1); n = 0; if (type & CALOBJ_TYPE_EVENT) n += g_list_length (priv->events); if (type & CALOBJ_TYPE_TODO) n += g_list_length (priv->todos); if (type & CALOBJ_TYPE_JOURNAL) n += g_list_length (priv->journals); return n; } /* Get_object handler for the file backend */ static char * cal_backend_file_get_object (CalBackend *backend, const char *uid) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; CalComponent *comp; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_assert (priv->comp_uid_hash != NULL); comp = lookup_component (cbfile, uid); if (!comp) return NULL; return cal_component_get_as_string (comp); } /* Get_object handler for the file backend */ static char * cal_backend_file_get_timezone_object (CalBackend *backend, const char *tzid) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; icaltimezone *icaltz; icalcomponent *icalcomp; char *ical_string; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_print ("In cal_backend_file_get_timezone_object: %s\n", tzid); g_return_val_if_fail (tzid != NULL, NULL); g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_assert (priv->comp_uid_hash != NULL); g_print (" getting icaltz\n"); icaltz = icalcomponent_get_timezone (priv->icalcomp, tzid); if (!icaltz) return NULL; g_print (" getting icalcomp\n"); icalcomp = icaltimezone_get_component (icaltz); if (!icalcomp) return NULL; g_print (" getting ical_string\n"); ical_string = icalcomponent_as_ical_string (icalcomp); /* We dup the string; libical owns that memory. */ if (ical_string) return g_strdup (ical_string); else return NULL; } static CalObjType cal_backend_file_get_type_by_uid (CalBackend *backend, const char *uid) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; CalComponent *comp; CalComponentVType type; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; comp = lookup_component (cbfile, uid); if (!comp) return CAL_COMPONENT_NO_TYPE; type = cal_component_get_vtype (comp); switch (type) { case CAL_COMPONENT_EVENT: return CALOBJ_TYPE_EVENT; case CAL_COMPONENT_TODO: return CALOBJ_TYPE_TODO; case CAL_COMPONENT_JOURNAL: return CALOBJ_TYPE_JOURNAL; default: return CAL_COMPONENT_NO_TYPE; } } /* Builds a list of UIDs from a list of CalComponent objects */ static void build_uids_list (GList **list, GList *components) { GList *l; for (l = components; l; l = l->next) { CalComponent *comp; const char *uid; comp = CAL_COMPONENT (l->data); cal_component_get_uid (comp, &uid); *list = g_list_prepend (*list, g_strdup (uid)); } } /* Get_uids handler for the file backend */ static GList * cal_backend_file_get_uids (CalBackend *backend, CalObjType type) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; GList *list; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, NULL); list = NULL; if (type & CALOBJ_TYPE_EVENT) build_uids_list (&list, priv->events); if (type & CALOBJ_TYPE_TODO) build_uids_list (&list, priv->todos); if (type & CALOBJ_TYPE_JOURNAL) build_uids_list (&list, priv->journals); return list; } /* Callback used from cal_recur_generate_instances(); adds the component's UID * to our hash table. */ static gboolean add_instance (CalComponent *comp, time_t start, time_t end, gpointer data) { GHashTable *uid_hash; const char *uid; const char *old_uid; uid_hash = data; /* We only care that the component's UID is listed in the hash table; * that's why we only allow generation of one instance (i.e. return * FALSE every time). */ cal_component_get_uid (comp, &uid); old_uid = g_hash_table_lookup (uid_hash, uid); if (old_uid) return FALSE; g_hash_table_insert (uid_hash, (char *) uid, NULL); return FALSE; } static icaltimezone* resolve_tzid (const char *tzid, gpointer data) { icalcomponent *vcalendar_comp = data; if (!tzid || !tzid[0]) return NULL; else if (!strcmp (tzid, "UTC")) return icaltimezone_get_utc_timezone (); else return icalcomponent_get_timezone (vcalendar_comp, tzid); } /* Populates a hash table with the UIDs of the components that occur or recur * within a specific time range. */ static void get_instances_in_range (GHashTable *uid_hash, GList *components, time_t start, time_t end) { GList *l; for (l = components; l; l = l->next) { CalComponent *comp; icalcomponent *icalcomp, *vcalendar_comp; comp = CAL_COMPONENT (l->data); /* Get the parent VCALENDAR component, so we can resolve TZIDs. */ icalcomp = cal_component_get_icalcomponent (comp); vcalendar_comp = icalcomponent_get_parent (icalcomp); g_assert (vcalendar_comp != NULL); cal_recur_generate_instances (comp, start, end, add_instance, uid_hash, resolve_tzid, vcalendar_comp); } } /* Used from g_hash_table_foreach(), adds a UID from the hash table to our list */ static void add_uid_to_list (gpointer key, gpointer value, gpointer data) { GList **list; const char *uid; char *uid_copy; list = data; uid = key; uid_copy = g_strdup (uid); *list = g_list_prepend (*list, uid_copy); } /* Get_objects_in_range handler for the file backend */ static GList * cal_backend_file_get_objects_in_range (CalBackend *backend, CalObjType type, time_t start, time_t end) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; GList *event_list; GHashTable *uid_hash; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_return_val_if_fail (start != -1 && end != -1, NULL); g_return_val_if_fail (start <= end, NULL); uid_hash = g_hash_table_new (g_str_hash, g_str_equal); if (type & CALOBJ_TYPE_EVENT) get_instances_in_range (uid_hash, priv->events, start, end); if (type & CALOBJ_TYPE_TODO) get_instances_in_range (uid_hash, priv->todos, start, end); if (type & CALOBJ_TYPE_JOURNAL) get_instances_in_range (uid_hash, priv->journals, start, end); event_list = NULL; g_hash_table_foreach (uid_hash, add_uid_to_list, &event_list); g_hash_table_destroy (uid_hash); return event_list; } /* Get_free_busy handler for the file backend */ static char * cal_backend_file_get_free_busy (CalBackend *backend, time_t start, time_t end) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; icalcomponent *vfb; char *calobj; struct icaltimetype itime; GList *uids; GList *l; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_return_val_if_fail (start != -1 && end != -1, NULL); g_return_val_if_fail (start <= end, NULL); /* create the iCal VFREEBUSY component */ vfb = icalcomponent_new_vfreebusy (); itime = icaltime_from_timet (start, 1); icalcomponent_set_dtstart (vfb, itime); itime = icaltime_from_timet (end, 1); icalcomponent_set_dtend (vfb, itime); /* add all objects in the given interval */ uids = cal_backend_get_objects_in_range (CAL_BACKEND (cbfile), CALOBJ_TYPE_ANY, start, end); for (l = uids; l != NULL; l = l->next) { char *comp_str; icalcomponent *icalcomp; icalproperty *icalprop; struct icalperiodtype ipt; char *uid = (char *) l->data; comp_str = cal_backend_get_object (CAL_BACKEND (cbfile), uid); if (!comp_str) continue; icalcomp = icalparser_parse_string (comp_str); g_free (comp_str); if (!icalcomp) continue; ipt.start = icalcomponent_get_dtstart (icalcomp); ipt.end = icalcomponent_get_dtend (icalcomp); ipt.duration = icalcomponent_get_duration (icalcomp); /* add busy information to the vfb component */ icalprop = icalproperty_new (ICAL_FREEBUSY_PROPERTY); icalproperty_set_freebusy (icalprop, ipt); icalcomponent_add_property (vfb, icalprop); } calobj = g_strdup (icalcomponent_as_ical_string (vfb)); icalcomponent_free (vfb); cal_obj_uid_list_free (uids); return calobj; } typedef struct { CalBackend *backend; GList *changes; GList *change_ids; } CalBackendFileComputeChangesData; static void cal_backend_file_compute_changes_foreach_key (const char *key, gpointer data) { CalBackendFileComputeChangesData *be_data = data; char *calobj = cal_backend_get_object (be_data->backend, key); if (calobj == NULL) { CalComponent *comp; GNOME_Evolution_Calendar_CalObjChange *coc; comp = cal_component_new (); cal_component_set_new_vtype (comp, CAL_COMPONENT_TODO); cal_component_set_uid (comp, key); coc = GNOME_Evolution_Calendar_CalObjChange__alloc (); coc->calobj = CORBA_string_dup (cal_component_get_as_string (comp)); coc->type = GNOME_Evolution_Calendar_DELETED; be_data->changes = g_list_prepend (be_data->changes, coc); be_data->change_ids = g_list_prepend (be_data->change_ids, (gpointer) key); } } static GNOME_Evolution_Calendar_CalObjChangeSeq * cal_backend_file_compute_changes (CalBackend *backend, CalObjType type, const char *change_id) { char *filename; EDbHash *ehash; CalBackendFileComputeChangesData be_data; GNOME_Evolution_Calendar_CalObjChangeSeq *seq; GList *uids, *changes = NULL, *change_ids = NULL; GList *i, *j; int n; /* Find the changed ids - FIX ME, path should not be hard coded */ if (type == GNOME_Evolution_Calendar_TYPE_TODO) filename = g_strdup_printf ("%s/evolution/local/Tasks/%s.db", g_get_home_dir (), change_id); else filename = g_strdup_printf ("%s/evolution/local/Calendar/%s.db", g_get_home_dir (), change_id); ehash = e_dbhash_new (filename); g_free (filename); uids = cal_backend_get_uids (backend, type); /* Calculate adds and modifies */ for (i = uids; i != NULL; i = i->next) { GNOME_Evolution_Calendar_CalObjChange *coc; char *uid = i->data; char *calobj = cal_backend_get_object (backend, uid); g_assert (calobj != NULL); /* check what type of change has occurred, if any */ switch (e_dbhash_compare (ehash, uid, calobj)) { case E_DBHASH_STATUS_SAME: break; case E_DBHASH_STATUS_NOT_FOUND: coc = GNOME_Evolution_Calendar_CalObjChange__alloc (); coc->calobj = CORBA_string_dup (calobj); coc->type = GNOME_Evolution_Calendar_ADDED; changes = g_list_prepend (changes, coc); change_ids = g_list_prepend (change_ids, uid); break; case E_DBHASH_STATUS_DIFFERENT: coc = GNOME_Evolution_Calendar_CalObjChange__alloc (); coc->calobj = CORBA_string_dup (calobj); coc->type = GNOME_Evolution_Calendar_MODIFIED; changes = g_list_prepend (changes, coc); change_ids = g_list_prepend (change_ids, uid); break; } } /* Calculate deletions */ be_data.backend = backend; be_data.changes = changes; be_data.change_ids = change_ids; e_dbhash_foreach_key (ehash, (EDbHashFunc)cal_backend_file_compute_changes_foreach_key, &be_data); changes = be_data.changes; change_ids = be_data.change_ids; /* Build the sequence and update the hash */ n = g_list_length (changes); seq = GNOME_Evolution_Calendar_CalObjChangeSeq__alloc (); seq->_length = n; seq->_buffer = CORBA_sequence_GNOME_Evolution_Calendar_CalObjChange_allocbuf (n); CORBA_sequence_set_release (seq, TRUE); for (i = changes, j = change_ids, n = 0; i != NULL; i = i->next, j = j->next, n++) { GNOME_Evolution_Calendar_CalObjChange *coc = i->data; GNOME_Evolution_Calendar_CalObjChange *seq_coc; char *uid = j->data; /* sequence building */ seq_coc = &seq->_buffer[n]; seq_coc->calobj = CORBA_string_dup (coc->calobj); seq_coc->type = coc->type; /* hash updating */ if (coc->type == GNOME_Evolution_Calendar_ADDED || coc->type == GNOME_Evolution_Calendar_MODIFIED) { e_dbhash_add (ehash, uid, coc->calobj); } else { e_dbhash_remove (ehash, uid); } CORBA_free (coc); } e_dbhash_write (ehash); e_dbhash_destroy (ehash); cal_obj_uid_list_free (uids); g_list_free (change_ids); g_list_free (changes); return seq; } /* Get_changes handler for the file backend */ static GNOME_Evolution_Calendar_CalObjChangeSeq * cal_backend_file_get_changes (CalBackend *backend, CalObjType type, const char *change_id) { g_return_val_if_fail (backend != NULL, NULL); g_return_val_if_fail (IS_CAL_BACKEND (backend), NULL); return cal_backend_file_compute_changes (backend, type, change_id); } /* Computes the range of time in which recurrences should be generated for a * component in order to compute alarm trigger times. */ static void compute_alarm_range (CalComponent *comp, GList *alarm_uids, time_t start, time_t end, time_t *alarm_start, time_t *alarm_end) { GList *l; *alarm_start = start; *alarm_end = end; for (l = alarm_uids; l; l = l->next) { const char *auid; CalComponentAlarm *alarm; CalAlarmTrigger trigger; struct icaldurationtype *dur; time_t dur_time; auid = l->data; alarm = cal_component_get_alarm (comp, auid); g_assert (alarm != NULL); cal_component_alarm_get_trigger (alarm, &trigger); cal_component_alarm_free (alarm); switch (trigger.type) { case CAL_ALARM_TRIGGER_NONE: case CAL_ALARM_TRIGGER_ABSOLUTE: continue; case CAL_ALARM_TRIGGER_RELATIVE_START: case CAL_ALARM_TRIGGER_RELATIVE_END: dur = &trigger.u.rel_duration; dur_time = icaldurationtype_as_int (*dur); if (dur->is_neg) /* If the duration is negative then dur_time * will be negative as well; that is why we * subtract to expand the range. */ *alarm_end = MAX (*alarm_end, end - dur_time); else *alarm_start = MIN (*alarm_start, start - dur_time); break; default: g_assert_not_reached (); } } g_assert (*alarm_start <= *alarm_end); } /* Closure data to generate alarm occurrences */ struct alarm_occurrence_data { /* These are the info we have */ GList *alarm_uids; time_t start; time_t end; /* This is what we compute */ GSList *triggers; int n_triggers; }; /* Callback used from cal_recur_generate_instances(); generates triggers for all * of a component's RELATIVE alarms. */ static gboolean add_alarm_occurrences_cb (CalComponent *comp, time_t start, time_t end, gpointer data) { struct alarm_occurrence_data *aod; GList *l; aod = data; for (l = aod->alarm_uids; l; l = l->next) { const char *auid; CalComponentAlarm *alarm; CalAlarmTrigger trigger; struct icaldurationtype *dur; time_t dur_time; time_t occur_time, trigger_time; CalAlarmInstance *instance; auid = l->data; alarm = cal_component_get_alarm (comp, auid); g_assert (alarm != NULL); cal_component_alarm_get_trigger (alarm, &trigger); cal_component_alarm_free (alarm); if (trigger.type != CAL_ALARM_TRIGGER_RELATIVE_START && trigger.type != CAL_ALARM_TRIGGER_RELATIVE_END) continue; dur = &trigger.u.rel_duration; dur_time = icaldurationtype_as_int (*dur); if (trigger.type == CAL_ALARM_TRIGGER_RELATIVE_START) occur_time = start; else occur_time = end; /* If dur->is_neg is true then dur_time will already be * negative. So we do not need to test for dur->is_neg here; we * can simply add the dur_time value to the occur_time and get * the correct result. */ trigger_time = occur_time + dur_time; if (trigger_time < aod->start || trigger_time >= aod->end) continue; instance = g_new (CalAlarmInstance, 1); instance->auid = auid; instance->trigger = trigger_time; instance->occur_start = start; instance->occur_end = end; aod->triggers = g_slist_prepend (aod->triggers, instance); aod->n_triggers++; } return TRUE; } /* Generates the absolute triggers for a component */ static void generate_absolute_triggers (CalComponent *comp, struct alarm_occurrence_data *aod) { GList *l; CalComponentDateTime dt_start, dt_end; cal_component_get_dtstart (comp, &dt_start); cal_component_get_dtend (comp, &dt_end); for (l = aod->alarm_uids; l; l = l->next) { const char *auid; CalComponentAlarm *alarm; CalAlarmTrigger trigger; time_t abs_time; CalAlarmInstance *instance; auid = l->data; alarm = cal_component_get_alarm (comp, auid); g_assert (alarm != NULL); cal_component_alarm_get_trigger (alarm, &trigger); cal_component_alarm_free (alarm); if (trigger.type != CAL_ALARM_TRIGGER_ABSOLUTE) continue; abs_time = icaltime_as_timet (trigger.u.abs_time); if (abs_time < aod->start || abs_time >= aod->end) continue; instance = g_new (CalAlarmInstance, 1); instance->auid = auid; instance->trigger = abs_time; /* No particular occurrence, so just use the times from the component */ if (dt_start.value) instance->occur_start = icaltime_as_timet (*dt_start.value); else instance->occur_start = -1; if (dt_end.value) instance->occur_end = icaltime_as_timet (*dt_end.value); else instance->occur_end = -1; aod->triggers = g_slist_prepend (aod->triggers, instance); aod->n_triggers++; } cal_component_free_datetime (&dt_start); cal_component_free_datetime (&dt_end); } /* Compares two alarm instances; called from g_slist_sort() */ static gint compare_alarm_instance (gconstpointer a, gconstpointer b) { const CalAlarmInstance *aia, *aib; aia = a; aib = b; if (aia->trigger < aib->trigger) return -1; else if (aia->trigger > aib->trigger) return 1; else return 0; } /* Generates alarm instances for a calendar component. Returns the instances * structure, or NULL if no alarm instances occurred in the specified time * range. */ static CalComponentAlarms * generate_alarms_for_comp (CalComponent *comp, time_t start, time_t end) { GList *alarm_uids; time_t alarm_start, alarm_end; struct alarm_occurrence_data aod; CalComponentAlarms *alarms; icalcomponent *icalcomp, *vcalendar_comp; if (!cal_component_has_alarms (comp)) return NULL; alarm_uids = cal_component_get_alarm_uids (comp); compute_alarm_range (comp, alarm_uids, start, end, &alarm_start, &alarm_end); aod.alarm_uids = alarm_uids; aod.start = start; aod.end = end; aod.triggers = NULL; aod.n_triggers = 0; /* Get the parent VCALENDAR component, so we can resolve TZIDs. */ icalcomp = cal_component_get_icalcomponent (comp); vcalendar_comp = icalcomponent_get_parent (icalcomp); g_assert (vcalendar_comp != NULL); cal_recur_generate_instances (comp, alarm_start, alarm_end, add_alarm_occurrences_cb, &aod, resolve_tzid, vcalendar_comp); /* We add the ABSOLUTE triggers separately */ generate_absolute_triggers (comp, &aod); if (aod.n_triggers == 0) return NULL; /* Create the component alarm instances structure */ alarms = g_new (CalComponentAlarms, 1); alarms->comp = comp; gtk_object_ref (GTK_OBJECT (alarms->comp)); alarms->alarms = g_slist_sort (aod.triggers, compare_alarm_instance); return alarms; } /* Iterates through all the components in the comps list and generates alarm * instances for them; putting them in the comp_alarms list. Returns the number * of elements it added to that list. */ static int generate_alarms_for_list (GList *comps, time_t start, time_t end, GSList **comp_alarms) { GList *l; int n; n = 0; for (l = comps; l; l = l->next) { CalComponent *comp; CalComponentAlarms *alarms; comp = CAL_COMPONENT (l->data); alarms = generate_alarms_for_comp (comp, start, end); if (alarms) { *comp_alarms = g_slist_prepend (*comp_alarms, alarms); n++; } } return n; } /* Fills a CORBA sequence of alarm instances */ static void fill_alarm_instances_seq (GNOME_Evolution_Calendar_CalAlarmInstanceSeq *seq, GSList *alarms) { int n_alarms; GSList *l; int i; n_alarms = g_slist_length (alarms); CORBA_sequence_set_release (seq, TRUE); seq->_length = n_alarms; seq->_buffer = CORBA_sequence_GNOME_Evolution_Calendar_CalAlarmInstance_allocbuf (n_alarms); for (l = alarms, i = 0; l; l = l->next, i++) { CalAlarmInstance *instance; GNOME_Evolution_Calendar_CalAlarmInstance *corba_instance; instance = l->data; corba_instance = seq->_buffer + i; corba_instance->auid = CORBA_string_dup (instance->auid); corba_instance->trigger = (long) instance->trigger; corba_instance->occur_start = (long) instance->occur_start; corba_instance->occur_end = (long) instance->occur_end; } } /* Get_alarms_in_range handler for the file backend */ static GNOME_Evolution_Calendar_CalComponentAlarmsSeq * cal_backend_file_get_alarms_in_range (CalBackend *backend, time_t start, time_t end) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; int n_comp_alarms; GSList *comp_alarms; GSList *l; int i; GNOME_Evolution_Calendar_CalComponentAlarmsSeq *seq; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_return_val_if_fail (start != -1 && end != -1, NULL); g_return_val_if_fail (start <= end, NULL); /* Per RFC 2445, only VEVENTs and VTODOs can have alarms */ n_comp_alarms = 0; comp_alarms = NULL; n_comp_alarms += generate_alarms_for_list (priv->events, start, end, &comp_alarms); n_comp_alarms += generate_alarms_for_list (priv->todos, start, end, &comp_alarms); seq = GNOME_Evolution_Calendar_CalComponentAlarmsSeq__alloc (); CORBA_sequence_set_release (seq, TRUE); seq->_length = n_comp_alarms; seq->_buffer = CORBA_sequence_GNOME_Evolution_Calendar_CalComponentAlarms_allocbuf ( n_comp_alarms); for (l = comp_alarms, i = 0; l; l = l->next, i++) { CalComponentAlarms *alarms; char *comp_str; alarms = l->data; comp_str = cal_component_get_as_string (alarms->comp); seq->_buffer[i].calobj = CORBA_string_dup (comp_str); g_free (comp_str); fill_alarm_instances_seq (&seq->_buffer[i].alarms, alarms->alarms); cal_component_alarms_free (alarms); } g_slist_free (comp_alarms); return seq; } /* Get_alarms_for_object handler for the file backend */ static GNOME_Evolution_Calendar_CalComponentAlarms * cal_backend_file_get_alarms_for_object (CalBackend *backend, const char *uid, time_t start, time_t end, gboolean *object_found) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; CalComponent *comp; char *comp_str; GNOME_Evolution_Calendar_CalComponentAlarms *corba_alarms; CalComponentAlarms *alarms; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, NULL); g_return_val_if_fail (uid != NULL, NULL); g_return_val_if_fail (start != -1 && end != -1, NULL); g_return_val_if_fail (start <= end, NULL); g_return_val_if_fail (object_found != NULL, NULL); comp = lookup_component (cbfile, uid); if (!comp) { *object_found = FALSE; return NULL; } *object_found = TRUE; comp_str = cal_component_get_as_string (comp); corba_alarms = GNOME_Evolution_Calendar_CalComponentAlarms__alloc (); corba_alarms->calobj = CORBA_string_dup (comp_str); g_free (comp_str); alarms = generate_alarms_for_comp (comp, start, end); if (alarms) { fill_alarm_instances_seq (&corba_alarms->alarms, alarms->alarms); cal_component_alarms_free (alarms); } else fill_alarm_instances_seq (&corba_alarms->alarms, NULL); return corba_alarms; } /* Notifies a backend's clients that an object was updated */ static void notify_update (CalBackendFile *cbfile, const char *uid) { CalBackendFilePrivate *priv; GList *l; priv = cbfile->priv; cal_backend_obj_updated (CAL_BACKEND (cbfile), uid); for (l = priv->clients; l; l = l->next) { Cal *cal; cal = CAL (l->data); cal_notify_update (cal, uid); } } /* Notifies a backend's clients that an object was removed */ static void notify_remove (CalBackendFile *cbfile, const char *uid) { CalBackendFilePrivate *priv; GList *l; priv = cbfile->priv; cal_backend_obj_removed (CAL_BACKEND (cbfile), uid); for (l = priv->clients; l; l = l->next) { Cal *cal; cal = CAL (l->data); cal_notify_remove (cal, uid); } } /* Update_objects handler for the file backend. */ static gboolean cal_backend_file_update_objects (CalBackend *backend, const char *calobj) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; icalcomponent *icalcomp, *vcalendar_comp = NULL; icalcomponent_kind kind; CalComponent *old_comp; CalComponent *comp; const char *comp_uid; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, FALSE); g_return_val_if_fail (calobj != NULL, FALSE); /* Pull the component from the string and ensure that it is sane */ fprintf (stderr, "cal_backend_file: Parsing string:\n%s\n", calobj); icalcomp = icalparser_parse_string ((char *) calobj); if (!icalcomp) return FALSE; fprintf (stderr, "cal_backend_file: Parsed OK.\n"); kind = icalcomponent_isa (icalcomp); if (kind == ICAL_VCALENDAR_COMPONENT) { int num_found = 0; icalcomponent_kind child_kind; icalcomponent *subcomp; fprintf (stderr, "cal_backend_file: VCALENDAR found\n"); /* We have a VCALENDAR containing the VEVENT/VTODO and the related timezone data, so we have to step through it to find the actual VEVENT/VTODO component. */ vcalendar_comp = icalcomp; subcomp = icalcomponent_get_first_component (vcalendar_comp, ICAL_ANY_COMPONENT); while (subcomp) { child_kind = icalcomponent_isa (subcomp); if (child_kind == ICAL_VEVENT_COMPONENT || child_kind == ICAL_VTODO_COMPONENT || child_kind == ICAL_VJOURNAL_COMPONENT) { icalcomp = subcomp; num_found++; } subcomp = icalcomponent_get_next_component (vcalendar_comp, ICAL_ANY_COMPONENT); } /* If we didn't find exactly 1 VEVENT/VTODO it is an error. */ if (num_found != 1) { icalcomponent_free (icalcomp); return FALSE; } } else if (!(kind == ICAL_VEVENT_COMPONENT || kind == ICAL_VTODO_COMPONENT || kind == ICAL_VJOURNAL_COMPONENT)) { /* We don't support this type of component */ icalcomponent_free (icalcomp); return FALSE; } comp = cal_component_new (); if (!cal_component_set_icalcomponent (comp, icalcomp)) { gtk_object_unref (GTK_OBJECT (comp)); icalcomponent_free (icalcomp); return FALSE; } /* Get the UID, and check it isn't empty. */ cal_component_get_uid (comp, &comp_uid); if (!comp_uid || !comp_uid[0]) { gtk_object_unref (GTK_OBJECT (comp)); return FALSE; } /* Update the component */ old_comp = lookup_component (cbfile, comp_uid); if (old_comp) remove_component (cbfile, old_comp); if (kind == ICAL_VCALENDAR_COMPONENT) { /* If we have a VCALENDAR component with child VTIMEZONEs and the VEVENT/VTODO, we have to merge it into the existing VCALENDAR, resolving any conflicting TZIDs. */ icalcomponent_merge_component (priv->icalcomp, vcalendar_comp); /* Now we add the component to our local cache, but we pass FALSE as the last argument, since we have already added the libical component when merging above.*/ add_component (cbfile, comp, FALSE); } else { add_component (cbfile, comp, TRUE); } mark_dirty (cbfile); /* FIXME: do the notification asynchronously */ notify_update (cbfile, comp_uid); notify_categories_changed (cbfile); return TRUE; } /* Remove_object handler for the file backend */ static gboolean cal_backend_file_remove_object (CalBackend *backend, const char *uid) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; CalComponent *comp; int old_n_categories, new_n_categories; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, FALSE); g_return_val_if_fail (uid != NULL, FALSE); comp = lookup_component (cbfile, uid); if (!comp) return FALSE; old_n_categories = g_hash_table_size (priv->categories); remove_component (cbfile, comp); new_n_categories = g_hash_table_size (priv->categories); mark_dirty (cbfile); /* FIXME: do the notification asynchronously */ notify_remove (cbfile, uid); if (old_n_categories != new_n_categories) notify_categories_changed (cbfile); return TRUE; } static icaltimezone* cal_backend_file_get_timezone (CalBackend *backend, const char *tzid) { CalBackendFile *cbfile; CalBackendFilePrivate *priv; cbfile = CAL_BACKEND_FILE (backend); priv = cbfile->priv; g_return_val_if_fail (priv->icalcomp != NULL, NULL); if (!strcmp (tzid, "UTC")) return icaltimezone_get_utc_timezone (); else return icalcomponent_get_timezone (priv->icalcomp, tzid); }