aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/pcs/cal-backend-file.c
blob: 1313b22f12b1b2612d73145fd0aeb88fa802f496 (plain) (tree)
1
2
3
4
5
6

                                              
                                  
                                  
  
                                                       

















                                                                            
                            
                               
                              
                             
                             


 








                                                                              


























                                                                               
                                                                                       
                               
                                       
 










                                                                     

                                                                                         
                                                                 


                                                                                 
                                                                                                  
                                                                                          
                                                                               

                                                                                          
                                                                                            

                                                                               







                                                                                             
                                                                                          
                                                                                      
 

                                                                                           

                                                               


















































                                                                                                   
                                                    
                                                              

                                                                      
                                                                                    
                                                                                  
                                                            
                                                                                    
                                                                      
                                                                  

                                                                                      
                                                                        
                                                                      

                                                                    

 





                                                               







                                                         

                         




                                   

                                                                      
                                                                              


                                                                  
















                                                                


                                      
                   

                  



                                          
                                                         












                                                                           

         




















                                                                                   

 










                                                                  





                                            
                       






                                                        

                                                
















                                                

                                                                






                                                           



                              



                                                                        



                                                                                





















                                                                           
 
























                                                              




































                                                                            
                                                                 








                                                         





























































                                                                                                
























                                                                                      

















                                                                                                










                                                                                         
                                                                                           






                                                        




                                                                                
                                                                                    



                                    
 






















                                                                          

                                           
                                                                     
 
                                             










                                                                       



                                                         


                                                                           

                                                                             




                                                             
                                




                            








                                                                  

                                                       
        
























                                                 



                                                          







                                                                            
                          




                                               



                                                                                       


                                        

                                                      











                                                                      
                                                    




                                           
                                              






                                     


                                                                                      
 


                                

                                               
 

                                                          
 

                        
 





                                                                 
 
                            
 
                                     
 


                                                      
 

                                              

                      



                                                                

           

                                                                       
                                              
         





                                                                         


                                
                                        

 

                                                     
 
                                    
 

                            
                                                 
                                                   

                                      

                                                                         
                                


                            











































                                                                                      

 












                                                


















































                                                                     
                                             

















                                                                            
































                                                                            










































                                                                













                                                                 




                                                                              
 
                             
                        







                                                                             


                                           


                                                      
 

                                                           

 

                                                                             
   

                                                                                          
 
                 
 

                                              
                                                         
 
                                               







                                                                                                                      
         

 


                                                                                  
 




                        
 

                                  
 
                                                 

 
                                                       
              

                                                                            


                                    
                          
                             








                                                              
                                                              
 

                                                                            
 








                                                                              
 


                          
                                                
             

                                                                              



























                                                                              
                                              


                                             




                                                                            








                                                                              









                                                                                















                                                                     
 








































































                                                                                                               

                                                                      




























































                                                                                                          
                                                      
                                                       



                                                                                    




                                                            








                                                              




                                                                   





                                                                                       
















                                                                                            
                                                                                                    






                                                   


                                                        
                                                    
                                                                             
                                                                                         


                                    



                                                                  



                                            
                                                            
 



                                                              
 




                                              
 







                                                                             
                                                                                                    
                     
                                                                                                  
                                                   
              
                                                                                        

                            










                                                             

                                                            
                                                                 















                                                             

                                                            
                                                                 






                                             

























                                                                           
                                                  
               
                                                                         


                                    
                                                        



                                
                                               





                                                             



                                                                           
                                                                            




                                                             

                                                           

                                            















                                                                                 


                                                                       















                                                                                        











                                                                
                                                    


                                                
                                        



                                                     






                                                                               

                                  
                                                       
 
                     

                                                    












                                                                               
 

                                                                

                            
                                         





                                                                






















                                                                     




                                                                               
                                        
 

                            

                                    

                                                                
                                                   
         
 

                    
 

















                                                                         
/* 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);
}