/* Evolution calendar - iCalendar file backend
*
* Copyright (C) 2000 Ximian, Inc.
* Copyright (C) 2000 Ximian, Inc.
*
* Author: Federico Mena-Quintero <federico@ximian.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.
*/
#include <config.h>
#include <gtk/gtksignal.h>
#include "e-util/e-dbhash.h"
#include "cal-util/cal-recur.h"
#include "cal-util/cal-util.h"
#include "cal-backend-file.h"
#include "cal-backend-util.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, and a temporary hash of removed categories */
GHashTable *categories;
GHashTable *removed_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 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 CalComponent *cal_backend_file_get_object_component (CalBackend *backend, const char *uid);
static char *cal_backend_file_get_timezone_object (CalBackend *backend, const char *tzid);
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 void notify_categories_changed (CalBackendFile *cbfile);
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->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_object_component = cal_backend_file_get_object_component;
backend_class->get_timezone_object = cal_backend_file_get_timezone_object;
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;
}
static void
cal_added_cb (CalBackend *backend, gpointer user_data)
{
notify_categories_changed (CAL_BACKEND_FILE (backend));
}
/* 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->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);
priv->removed_categories = g_hash_table_new (g_str_hash, g_str_equal);
gtk_signal_connect (GTK_OBJECT (cbfile), "cal_added",
GTK_SIGNAL_FUNC (cal_added_cb), NULL);
}
/* 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;
GList *clients;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_CAL_BACKEND_FILE (object));
cbfile = CAL_BACKEND_FILE (object);
priv = cbfile->priv;
clients = CAL_BACKEND (cbfile)->clients;
g_assert (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;
g_hash_table_foreach (priv->removed_categories, free_category_cb, NULL);
g_hash_table_destroy (priv->removed_categories);
priv->removed_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;
}
/* 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 = CAL_BACKEND (cbfile)->clients; l; l = l->next) {
Cal *cal;
cal = CAL (l->data);
cal_notify_categories_changed (cal, seq);
}
CORBA_free (seq);
}
/* 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 {
/* See if it was in the removed categories */
c = g_hash_table_lookup (priv->removed_categories, name);
if (c) {
/* Move it to the set of live categories */
g_assert (c->refcount == 0);
g_hash_table_remove (priv->removed_categories, c->name);
c->refcount = 1;
g_hash_table_insert (priv->categories, c->name, c);
} else {
/* Create a new category */
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_hash_table_insert (priv->removed_categories, c->name, 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 CalComponent *
cal_backend_file_get_object_component (CalBackend *backend, const char *uid)
{
CalBackendFile *cbfile;
CalBackendFilePrivate *priv;
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);
return lookup_component (cbfile, uid);
}
/* 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_return_val_if_fail (tzid != NULL, NULL);
g_return_val_if_fail (priv->icalcomp != NULL, NULL);
g_assert (priv->comp_uid_hash != NULL);
icaltz = icalcomponent_get_timezone (priv->icalcomp, tzid);
if (!icaltz)
return NULL;
icalcomp = icaltimezone_get_component (icaltz);
if (!icalcomp)
return NULL;
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;
}
/* 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;
}
/* function to resolve timezones */
static icaltimezone *
resolve_tzid (const char *tzid, gpointer user_data)
{
icalcomponent *vcalendar_comp = user_data;
if (!tzid || !tzid[0])
return NULL;
else if (!strcmp (tzid, "UTC"))
return icaltimezone_get_utc_timezone ();
return icalcomponent_get_timezone (vcalendar_comp, tzid);
}
/* 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;
}
/* 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, *prop;
struct icalperiodtype ipt;
char *uid = (char *) l->data;
/* FIXME: This looks quite inefficient. It is converting the
component to a string and then parsing it again. It would
be better to use lookup_component(). It needs to handle
timezones as well, so it is probably easier to use the
CalComponent wrapper functions. - Damon. */
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;
/* If the event is TRANSPARENT, skip it. */
prop = icalcomponent_get_first_property (icalcomp,
ICAL_TRANSP_PROPERTY);
if (prop) {
const char *transp_val = icalproperty_get_transp (prop);
if (transp_val
&& !strcasecmp (transp_val, "TRANSPARENT"))
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);
}
/* 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 += cal_util_generate_alarms_for_list (priv->events, start, end,
&comp_alarms, resolve_tzid,
priv->icalcomp);
n_comp_alarms += cal_util_generate_alarms_for_list (priv->todos, start, end,
&comp_alarms, resolve_tzid,
priv->icalcomp);
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);
cal_backend_util_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 = cal_util_generate_alarms_for_comp (comp, start, end, resolve_tzid, priv->icalcomp);
if (alarms) {
cal_backend_util_fill_alarm_instances_seq (&corba_alarms->alarms, alarms->alarms);
cal_component_alarms_free (alarms);
} else
cal_backend_util_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 = CAL_BACKEND (cbfile)->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 = CAL_BACKEND (cbfile)->clients; l; l = l->next) {
Cal *cal;
cal = CAL (l->data);
cal_notify_remove (cal, uid);
}
}
/* Used from g_hash_table_foreach_remove(); removes and frees a category */
static gboolean
remove_category_cb (gpointer key, gpointer value, gpointer data)
{
Category *c;
c = value;
g_free (c->name);
g_free (c);
return TRUE;
}
/* Clears the table of removed categories */
static void
clean_removed_categories (CalBackendFile *cbfile)
{
CalBackendFilePrivate *priv;
priv = cbfile->priv;
g_hash_table_foreach_remove (priv->removed_categories,
remove_category_cb,
NULL);
}
/* 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;
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 (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;
}
/* The list of removed categories must be empty because we are about to
* start a new scanning process.
*/
g_assert (g_hash_table_size (priv->removed_categories) == 0);
old_n_categories = g_hash_table_size (priv->categories);
/* 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);
}
new_n_categories = g_hash_table_size (priv->categories);
mark_dirty (cbfile);
notify_update (cbfile, comp_uid);
if (old_n_categories != new_n_categories ||
g_hash_table_size (priv->removed_categories) != 0) {
clean_removed_categories (cbfile);
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;
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;
/* The list of removed categories must be empty because we are about to
* start a new scanning process.
*/
g_assert (g_hash_table_size (priv->removed_categories) == 0);
remove_component (cbfile, comp);
mark_dirty (cbfile);
notify_remove (cbfile, uid);
if (g_hash_table_size (priv->removed_categories) != 0) {
clean_removed_categories (cbfile);
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);
}