Merge from recurrences-work-branch
[platform/upstream/evolution-data-server.git] / calendar / libedata-cal / e-cal-backend.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* Evolution calendar - generic backend class
3  *
4  * Copyright (C) 2000 Ximian, Inc.
5  * Copyright (C) 2000 Ximian, Inc.
6  *
7  * Authors: Federico Mena-Quintero <federico@ximian.com>
8  *          JP Rosevear <jpr@ximian.com>
9  *          Rodrigo Moya <rodrigo@ximian.com>    
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of version 2 of the GNU General Public
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
23  */
24
25 #include <config.h>
26 #include <libxml/parser.h>
27 #include <libxml/parserInternals.h>
28 #include <libxml/xmlmemory.h>
29
30 #include "e-cal-backend.h"
31
32 \f
33
34 /* A category that exists in some of the objects of the calendar */
35 typedef struct {
36         /* Category name, also used as the key in the categories hash table */
37         char *name;
38
39         /* Number of objects that have this category */
40         int refcount;
41 } ECalBackendCategory;
42
43 /* Private part of the CalBackend structure */
44 struct _ECalBackendPrivate {
45         /* The source for this backend */
46         ESource *source;
47
48         /* URI, from source. This is cached, since we return const. */
49         char *uri;
50
51         /* The kind of components for this backend */
52         icalcomponent_kind kind;
53         
54         /* List of Cal objects */
55         GMutex *clients_mutex;
56         GList *clients;
57
58         GMutex *queries_mutex;
59         EList *queries;
60         
61         /* Hash table of live categories, temporary hash of
62          * added/removed categories, and idle handler for sending
63          * category_changed.
64          */
65         GHashTable *categories;
66         GHashTable *changed_categories;
67         guint category_idle_id;
68
69         /* ECalBackend to pass notifications on to */
70         ECalBackend *notification_proxy;
71 };
72
73 /* Property IDs */
74 enum props {
75         PROP_0,
76         PROP_SOURCE,
77         PROP_URI,
78         PROP_KIND
79 };
80
81 /* Signal IDs */
82 enum {
83         LAST_CLIENT_GONE,
84         OPENED,
85         REMOVED,
86         LAST_SIGNAL
87 };
88 static guint e_cal_backend_signals[LAST_SIGNAL];
89
90 static void e_cal_backend_class_init (ECalBackendClass *class);
91 static void e_cal_backend_init (ECalBackend *backend);
92 static void e_cal_backend_finalize (GObject *object);
93
94 static void notify_categories_changed (ECalBackend *backend);
95
96 #define CLASS(backend) (E_CAL_BACKEND_CLASS (G_OBJECT_GET_CLASS (backend)))
97
98 static GObjectClass *parent_class;
99
100 \f
101
102 /**
103  * e_cal_backend_get_type:
104  * @void:
105  *
106  * Registers the #ECalBackend class if necessary, and returns the type ID
107  * associated to it.
108  *
109  * Return value: The type ID of the #ECalBackend class.
110  **/
111 GType
112 e_cal_backend_get_type (void)
113 {
114         static GType e_cal_backend_type = 0;
115
116         if (!e_cal_backend_type) {
117                 static GTypeInfo info = {
118                         sizeof (ECalBackendClass),
119                         (GBaseInitFunc) NULL,
120                         (GBaseFinalizeFunc) NULL,
121                         (GClassInitFunc) e_cal_backend_class_init,
122                         NULL, NULL,
123                         sizeof (ECalBackend),
124                         0,
125                         (GInstanceInitFunc) e_cal_backend_init,
126                 };
127                 e_cal_backend_type = g_type_register_static (G_TYPE_OBJECT, "ECalBackend", &info, 0);
128         }
129
130         return e_cal_backend_type;
131 }
132
133 static void
134 e_cal_backend_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
135 {
136         ECalBackend *backend;
137         ECalBackendPrivate *priv;
138         
139         backend = E_CAL_BACKEND (object);
140         priv = backend->priv;
141         
142         switch (property_id) {
143         case PROP_SOURCE:
144                 {
145                         ESource *new_source;
146
147                         new_source = g_value_get_object (value);
148                         if (new_source)
149                                 g_object_ref (new_source);
150
151                         if (priv->source)
152                                 g_object_unref (priv->source);
153
154                         priv->source = new_source;
155
156                         /* Cache the URI */
157                         if (new_source) {
158                                 g_free (priv->uri);
159                                 priv->uri = e_source_get_uri (priv->source);
160                         }
161                 }
162                 break;
163         case PROP_URI:
164                 if (!priv->source) {
165                         g_free (priv->uri);
166                         priv->uri = g_value_dup_string (value);
167                 }
168                 break;
169         case PROP_KIND:
170                 priv->kind = g_value_get_ulong (value);
171                 break;
172         default:
173                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
174                 break;
175         }
176 }
177
178 static void
179 e_cal_backend_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
180 {
181         ECalBackend *backend;
182         ECalBackendPrivate *priv;
183         
184         backend = E_CAL_BACKEND (object);
185         priv = backend->priv;
186
187         switch (property_id) {
188         case PROP_SOURCE:
189                 g_value_set_object (value, e_cal_backend_get_source (backend));
190                 break;
191         case PROP_URI:
192                 g_value_set_string (value, e_cal_backend_get_uri (backend));
193                 break;
194         case PROP_KIND:
195                 g_value_set_ulong (value, e_cal_backend_get_kind (backend));
196                 break;
197         default:
198                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
199                 break;
200         }
201 }
202
203 /* Class initialization function for the calendar backend */
204 static void
205 e_cal_backend_class_init (ECalBackendClass *class)
206 {
207         GObjectClass *object_class;
208
209         parent_class = (GObjectClass *) g_type_class_peek_parent (class);
210
211         object_class = (GObjectClass *) class;
212
213         object_class->set_property = e_cal_backend_set_property;
214         object_class->get_property = e_cal_backend_get_property;
215         object_class->finalize = e_cal_backend_finalize;
216
217         g_object_class_install_property (object_class, PROP_SOURCE, 
218                                          g_param_spec_object ("source", NULL, NULL, E_TYPE_SOURCE,
219                                                               G_PARAM_READABLE | G_PARAM_WRITABLE
220                                                               | G_PARAM_CONSTRUCT_ONLY));
221
222         g_object_class_install_property (object_class, PROP_URI, 
223                                          g_param_spec_string ("uri", NULL, NULL, "",
224                                                               G_PARAM_READABLE | G_PARAM_WRITABLE
225                                                               | G_PARAM_CONSTRUCT_ONLY));
226
227         g_object_class_install_property (object_class, PROP_KIND, 
228                                          g_param_spec_ulong ("kind", NULL, NULL, 
229                                                              ICAL_NO_COMPONENT, ICAL_XLICMIMEPART_COMPONENT, 
230                                                              ICAL_NO_COMPONENT,
231                                                              G_PARAM_READABLE | G_PARAM_WRITABLE
232                                                              | G_PARAM_CONSTRUCT_ONLY));        
233         e_cal_backend_signals[LAST_CLIENT_GONE] =
234                 g_signal_new ("last_client_gone",
235                               G_TYPE_FROM_CLASS (class),
236                               G_SIGNAL_RUN_FIRST,
237                               G_STRUCT_OFFSET (ECalBackendClass, last_client_gone),
238                               NULL, NULL,
239                               g_cclosure_marshal_VOID__VOID,
240                               G_TYPE_NONE, 0);
241         e_cal_backend_signals[OPENED] =
242                 g_signal_new ("opened",
243                               G_TYPE_FROM_CLASS (class),
244                               G_SIGNAL_RUN_FIRST,
245                               G_STRUCT_OFFSET (ECalBackendClass, opened),
246                               NULL, NULL,
247                               g_cclosure_marshal_VOID__ENUM,
248                               G_TYPE_NONE, 1,
249                               G_TYPE_INT);
250         e_cal_backend_signals[REMOVED] =
251                 g_signal_new ("removed",
252                               G_TYPE_FROM_CLASS (class),
253                               G_SIGNAL_RUN_FIRST,
254                               G_STRUCT_OFFSET (ECalBackendClass, removed),
255                               NULL, NULL,
256                               g_cclosure_marshal_VOID__ENUM,
257                               G_TYPE_NONE, 1,
258                               G_TYPE_INT);
259
260         class->last_client_gone = NULL;
261         class->opened = NULL;
262         class->obj_updated = NULL;
263
264         class->get_cal_address = NULL;
265         class->get_alarm_email_address = NULL;
266         class->get_static_capabilities = NULL;
267         class->open = NULL;
268         class->is_loaded = NULL;
269         class->is_read_only = NULL;
270         class->start_query = NULL;
271         class->get_mode = NULL;
272         class->set_mode = NULL; 
273         class->get_object = NULL;
274         class->get_default_object = NULL;
275         class->get_object_list = NULL;
276         class->get_free_busy = NULL;
277         class->get_changes = NULL;
278         class->discard_alarm = NULL;
279         class->create_object = NULL;
280         class->modify_object = NULL;
281         class->remove_object = NULL;
282         class->receive_objects = NULL;
283         class->send_objects = NULL;
284         class->get_timezone = NULL;
285         class->add_timezone = NULL;
286         class->set_default_timezone = NULL;
287 }
288
289 /* Object initialization func for the calendar backend */
290 void
291 e_cal_backend_init (ECalBackend *backend)
292 {
293         ECalBackendPrivate *priv;
294
295         priv = g_new0 (ECalBackendPrivate, 1);
296         backend->priv = priv;
297
298         priv->clients = NULL;
299         priv->clients_mutex = g_mutex_new ();
300
301         /* FIXME bonobo_object_ref/unref? */
302         priv->queries = e_list_new((EListCopyFunc) g_object_ref, (EListFreeFunc) g_object_unref, NULL);
303         priv->queries_mutex = g_mutex_new ();
304         
305         priv->categories = g_hash_table_new (g_str_hash, g_str_equal);
306         priv->changed_categories = g_hash_table_new (g_str_hash, g_str_equal);
307 }
308
309 /* Used from g_hash_table_foreach(), frees a ECalBackendCategory structure */
310 static void
311 free_category_cb (gpointer key, gpointer value, gpointer data)
312 {
313         ECalBackendCategory *c = value;
314
315         g_free (c->name);
316         g_free (c);
317 }
318
319 static gboolean
320 prune_changed_categories (gpointer key, gpointer value, gpointer data)
321 {
322         ECalBackendCategory *c = value;
323
324         if (!c->refcount)
325                 free_category_cb (key, value, data);
326         return TRUE;
327 }
328
329 void
330 e_cal_backend_finalize (GObject *object)
331 {
332         ECalBackend *backend = (ECalBackend *)object;
333         ECalBackendPrivate *priv;
334
335         priv = backend->priv;
336
337         g_assert (priv->clients == NULL);
338
339         g_object_unref (priv->queries);
340
341         g_hash_table_foreach_remove (priv->changed_categories, prune_changed_categories, NULL);
342         g_hash_table_destroy (priv->changed_categories);
343
344         g_hash_table_foreach (priv->categories, free_category_cb, NULL);
345         g_hash_table_destroy (priv->categories);
346
347         g_mutex_free (priv->clients_mutex);
348         g_mutex_free (priv->queries_mutex);
349
350         if (priv->category_idle_id)
351                 g_source_remove (priv->category_idle_id);
352
353         g_free (priv);
354
355         G_OBJECT_CLASS (parent_class)->finalize (object);
356 }
357
358 \f
359
360 ESource *
361 e_cal_backend_get_source (ECalBackend *backend)
362 {
363         ECalBackendPrivate *priv;
364         
365         g_return_val_if_fail (backend != NULL, NULL);
366         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
367
368         priv = backend->priv;
369         
370         return priv->source;
371 }
372
373 /**
374  * e_cal_backend_get_uri:
375  * @backend: A calendar backend.
376  *
377  * Queries the URI of a calendar backend, which must already have an open
378  * calendar.
379  *
380  * Return value: The URI where the calendar is stored.
381  **/
382 const char *
383 e_cal_backend_get_uri (ECalBackend *backend)
384 {
385         ECalBackendPrivate *priv;
386         
387         g_return_val_if_fail (backend != NULL, NULL);
388         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
389
390         priv = backend->priv;
391         
392         return priv->uri;
393 }
394
395 icalcomponent_kind
396 e_cal_backend_get_kind (ECalBackend *backend)
397 {
398         ECalBackendPrivate *priv;
399         
400         g_return_val_if_fail (backend != NULL, ICAL_NO_COMPONENT);
401         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), ICAL_NO_COMPONENT);
402
403         priv = backend->priv;
404         
405         return priv->kind;
406 }
407
408 static void
409 cal_destroy_cb (gpointer data, GObject *where_cal_was)
410 {
411         ECalBackend *backend = E_CAL_BACKEND (data);
412
413         e_cal_backend_remove_client (backend, (EDataCal *) where_cal_was);
414 }
415
416 static void
417 listener_died_cb (gpointer cnx, gpointer data)
418 {
419         EDataCal *cal = E_DATA_CAL (data);
420
421         e_cal_backend_remove_client (e_data_cal_get_backend (cal), cal);
422 }
423
424 static void
425 last_client_gone (ECalBackend *backend)
426 {
427         g_signal_emit (backend, e_cal_backend_signals[LAST_CLIENT_GONE], 0);
428 }
429
430 void
431 e_cal_backend_add_client (ECalBackend *backend, EDataCal *cal)
432 {
433         ECalBackendPrivate *priv;
434         
435         g_return_if_fail (backend != NULL);
436         g_return_if_fail (E_IS_CAL_BACKEND (backend));
437         g_return_if_fail (cal != NULL);
438         g_return_if_fail (E_IS_DATA_CAL (cal));
439
440         priv = backend->priv;
441         
442         bonobo_object_set_immortal (BONOBO_OBJECT (cal), TRUE);
443
444         g_object_weak_ref (G_OBJECT (cal), cal_destroy_cb, backend);
445
446         ORBit_small_listen_for_broken (e_data_cal_get_listener (cal), G_CALLBACK (listener_died_cb), cal);
447
448         g_mutex_lock (priv->clients_mutex);
449         priv->clients = g_list_append (priv->clients, cal);
450         g_mutex_unlock (priv->clients_mutex);
451
452         /* Tell the new client about the list of categories.
453          * (Ends up telling all the other clients too, but *shrug*.)
454          */
455         /* FIXME This doesn't seem right at all */
456         notify_categories_changed (backend);
457 }
458
459 void
460 e_cal_backend_remove_client (ECalBackend *backend, EDataCal *cal)
461 {
462         ECalBackendPrivate *priv;
463         
464         /* XXX this needs a bit more thinking wrt the mutex - we
465            should be holding it when we check to see if clients is
466            NULL */
467         g_return_if_fail (backend != NULL);
468         g_return_if_fail (E_IS_CAL_BACKEND (backend));
469         g_return_if_fail (cal != NULL);
470         g_return_if_fail (E_IS_DATA_CAL (cal));
471
472         priv = backend->priv;
473
474         /* Disconnect */
475         g_mutex_lock (priv->clients_mutex);
476         priv->clients = g_list_remove (priv->clients, cal);
477         g_mutex_unlock (priv->clients_mutex);
478
479         /* When all clients go away, notify the parent factory about it so that
480          * it may decide whether to kill the backend or not.
481          */
482         if (!priv->clients)
483                 last_client_gone (backend);
484 }
485
486 void
487 e_cal_backend_add_query (ECalBackend *backend, EDataCalView *query)
488 {
489         g_return_if_fail (backend != NULL);
490         g_return_if_fail (E_IS_CAL_BACKEND (backend));
491
492         g_mutex_lock (backend->priv->queries_mutex);
493
494         e_list_append (backend->priv->queries, query);
495         
496         g_mutex_unlock (backend->priv->queries_mutex);
497 }
498
499 EList *
500 e_cal_backend_get_queries (ECalBackend *backend)
501 {
502         g_return_val_if_fail (backend != NULL, NULL);
503         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
504
505         return backend->priv->queries;
506 }
507
508
509 /**
510  * e_cal_backend_get_cal_address:
511  * @backend: A calendar backend.
512  *
513  * Queries the cal address associated with a calendar backend, which
514  * must already have an open calendar.
515  *
516  * Return value: The cal address associated with the calendar.
517  **/
518 void
519 e_cal_backend_get_cal_address (ECalBackend *backend, EDataCal *cal)
520 {
521         g_return_if_fail (backend != NULL);
522         g_return_if_fail (E_IS_CAL_BACKEND (backend));
523
524         g_assert (CLASS (backend)->get_cal_address != NULL);
525         (* CLASS (backend)->get_cal_address) (backend, cal);
526 }
527
528 void
529 e_cal_backend_get_alarm_email_address (ECalBackend *backend, EDataCal *cal)
530 {
531         g_return_if_fail (backend != NULL);
532         g_return_if_fail (E_IS_CAL_BACKEND (backend));
533
534         g_assert (CLASS (backend)->get_alarm_email_address != NULL);
535         (* CLASS (backend)->get_alarm_email_address) (backend, cal);
536 }
537
538 void
539 e_cal_backend_get_ldap_attribute (ECalBackend *backend, EDataCal *cal)
540 {
541         g_return_if_fail (backend != NULL);
542         g_return_if_fail (E_IS_CAL_BACKEND (backend));
543
544         g_assert (CLASS (backend)->get_ldap_attribute != NULL);
545         (* CLASS (backend)->get_ldap_attribute) (backend, cal);
546 }
547
548 void
549 e_cal_backend_get_static_capabilities (ECalBackend *backend, EDataCal *cal)
550 {
551         g_return_if_fail (backend != NULL);
552         g_return_if_fail (E_IS_CAL_BACKEND (backend));
553
554         g_assert (CLASS (backend)->get_static_capabilities != NULL);
555         (* CLASS (backend)->get_static_capabilities) (backend, cal);
556 }
557
558 /**
559  * e_cal_backend_open:
560  * @backend: A calendar backend.
561  * @uristr: URI that contains the calendar data.
562  * @only_if_exists: Whether the calendar should be opened only if it already
563  * exists.  If FALSE, a new calendar will be created when the specified @uri
564  * does not exist.
565  * @username: User name to use for authentication (if needed).
566  * @password: Password for @username.
567  *
568  * Opens a calendar backend with data from a calendar stored at the specified
569  * URI.
570  *
571  * Return value: An operation status code.
572  **/
573 void
574 e_cal_backend_open (ECalBackend *backend, EDataCal *cal, gboolean only_if_exists,
575                     const char *username, const char *password)
576 {
577         g_return_if_fail (backend != NULL);
578         g_return_if_fail (E_IS_CAL_BACKEND (backend));
579
580         g_assert (CLASS (backend)->open != NULL);
581         (* CLASS (backend)->open) (backend, cal, only_if_exists, username, password);
582 }
583
584 void
585 e_cal_backend_remove (ECalBackend *backend, EDataCal *cal)
586 {
587         g_return_if_fail (backend != NULL);
588         g_return_if_fail (E_IS_CAL_BACKEND (backend));
589
590         g_assert (CLASS (backend)->remove != NULL);
591         (* CLASS (backend)->remove) (backend, cal);
592 }
593
594 /**
595  * e_cal_backend_is_loaded:
596  * @backend: A calendar backend.
597  * 
598  * Queries whether a calendar backend has been loaded yet.
599  * 
600  * Return value: TRUE if the backend has been loaded with data, FALSE
601  * otherwise.
602  **/
603 gboolean
604 e_cal_backend_is_loaded (ECalBackend *backend)
605 {
606         gboolean result;
607
608         g_return_val_if_fail (backend != NULL, FALSE);
609         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
610
611         g_assert (CLASS (backend)->is_loaded != NULL);
612         result = (* CLASS (backend)->is_loaded) (backend);
613
614         return result;
615 }
616
617 /**
618  * e_cal_backend_is_read_only
619  * @backend: A calendar backend.
620  *
621  * Queries whether a calendar backend is read only or not.
622  *
623  * Return value: TRUE if the calendar is read only, FALSE otherwise.
624  */
625 void
626 e_cal_backend_is_read_only (ECalBackend *backend, EDataCal *cal)
627 {
628         g_return_if_fail (backend != NULL);
629         g_return_if_fail (E_IS_CAL_BACKEND (backend));
630
631         g_assert (CLASS (backend)->is_read_only != NULL);
632         (* CLASS (backend)->is_read_only) (backend, cal);
633 }
634
635 void 
636 e_cal_backend_start_query (ECalBackend *backend, EDataCalView *query)
637 {
638         g_return_if_fail (backend != NULL);
639         g_return_if_fail (E_IS_CAL_BACKEND (backend));
640
641         g_assert (CLASS (backend)->start_query != NULL);
642         (* CLASS (backend)->start_query) (backend, query);
643 }
644
645 /**
646  * e_cal_backend_get_mode:
647  * @backend: A calendar backend. 
648  * 
649  * Queries whether a calendar backend is connected remotely.
650  * 
651  * Return value: The current mode the calendar is in
652  **/
653 CalMode
654 e_cal_backend_get_mode (ECalBackend *backend)
655 {
656         CalMode result;
657
658         g_return_val_if_fail (backend != NULL, FALSE);
659         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
660
661         g_assert (CLASS (backend)->get_mode != NULL);
662         result = (* CLASS (backend)->get_mode) (backend);
663
664         return result;
665 }
666
667
668 /**
669  * e_cal_backend_set_mode:
670  * @backend: A calendar backend
671  * @mode: Mode to change to
672  * 
673  * Sets the mode of the calendar
674  * 
675  **/
676 void
677 e_cal_backend_set_mode (ECalBackend *backend, CalMode mode)
678 {
679         g_return_if_fail (backend != NULL);
680         g_return_if_fail (E_IS_CAL_BACKEND (backend));
681
682         g_assert (CLASS (backend)->set_mode != NULL);
683         (* CLASS (backend)->set_mode) (backend, mode);
684 }
685
686 void
687 e_cal_backend_get_default_object (ECalBackend *backend, EDataCal *cal)
688 {
689         g_return_if_fail (backend != NULL);
690         g_return_if_fail (E_IS_CAL_BACKEND (backend));
691
692         g_assert (CLASS (backend)->get_default_object != NULL);
693         (* CLASS (backend)->get_default_object) (backend, cal);
694 }
695
696 /**
697  * e_cal_backend_get_object:
698  * @backend: A calendar backend.
699  * @uid: Unique identifier for a calendar object.
700  * @rid: ID for the object's recurrence to get.
701  *
702  * Queries a calendar backend for a calendar object based on its unique
703  * identifier and its recurrence ID (if a recurrent appointment).
704  *
705  * Return value: The string representation of a complete calendar wrapping the
706  * the sought object, or NULL if no object had the specified UID.
707  **/
708 void
709 e_cal_backend_get_object (ECalBackend *backend, EDataCal *cal, const char *uid, const char *rid)
710 {
711         g_return_if_fail (backend != NULL);
712         g_return_if_fail (E_IS_CAL_BACKEND (backend));
713         g_return_if_fail (uid != NULL);
714
715         g_assert (CLASS (backend)->get_object != NULL);
716         (* CLASS (backend)->get_object) (backend, cal, uid, rid);
717 }
718
719 /**
720  * e_cal_backend_get_object_list:
721  * @backend: 
722  * @type: 
723  * 
724  * 
725  * 
726  * Return value: 
727  **/
728 void
729 e_cal_backend_get_object_list (ECalBackend *backend, EDataCal *cal, const char *sexp)
730 {
731         g_return_if_fail (backend != NULL);
732         g_return_if_fail (E_IS_CAL_BACKEND (backend));
733
734         g_assert (CLASS (backend)->get_object_list != NULL);
735         return (* CLASS (backend)->get_object_list) (backend, cal, sexp);
736 }
737
738 /**
739  * e_cal_backend_get_free_busy:
740  * @backend: A calendar backend.
741  * @users: List of users to get free/busy information for.
742  * @start: Start time for query.
743  * @end: End time for query.
744  * 
745  * Gets a free/busy object for the given time interval
746  * 
747  * Return value: a list of CalObj's
748  **/
749 void
750 e_cal_backend_get_free_busy (ECalBackend *backend, EDataCal *cal, GList *users, time_t start, time_t end)
751 {
752         g_return_if_fail (backend != NULL);
753         g_return_if_fail (E_IS_CAL_BACKEND (backend));
754         g_return_if_fail (start != -1 && end != -1);
755         g_return_if_fail (start <= end);
756
757         g_assert (CLASS (backend)->get_free_busy != NULL);
758         (* CLASS (backend)->get_free_busy) (backend, cal, users, start, end);
759 }
760
761 /**
762  * e_cal_backend_get_changes:
763  * @backend: A calendar backend
764  * @change_id: A unique uid for the callers change list
765  * 
766  * Builds a sequence of objects and the type of change that occurred on them since
767  * the last time the give change_id was seen
768  * 
769  * Return value: A list of the objects that changed and the type of change
770  **/
771 void
772 e_cal_backend_get_changes (ECalBackend *backend, EDataCal *cal, const char *change_id) 
773 {
774         g_return_if_fail (backend != NULL);
775         g_return_if_fail (E_IS_CAL_BACKEND (backend));
776         g_return_if_fail (change_id != NULL);
777
778         g_assert (CLASS (backend)->get_changes != NULL);
779         (* CLASS (backend)->get_changes) (backend, cal, change_id);
780 }
781
782 /**
783  * e_cal_backend_discard_alarm
784  * @backend: A calendar backend.
785  * @uid: UID of the component to discard the alarm from.
786  * @auid: Alarm ID.
787  *
788  * Discards an alarm from the given component. This allows the specific backend
789  * to do whatever is needed to really discard the alarm.
790  *
791  **/
792 void
793 e_cal_backend_discard_alarm (ECalBackend *backend, EDataCal *cal, const char *uid, const char *auid)
794 {
795         g_return_if_fail (backend != NULL);
796         g_return_if_fail (E_IS_CAL_BACKEND (backend));
797         g_return_if_fail (uid != NULL);
798         g_return_if_fail (auid != NULL);
799
800         g_assert (CLASS (backend)->discard_alarm != NULL);
801         (* CLASS (backend)->discard_alarm) (backend, cal, uid, auid);
802 }
803
804 void
805 e_cal_backend_create_object (ECalBackend *backend, EDataCal *cal, const char *calobj)
806 {
807         g_return_if_fail (backend != NULL);
808         g_return_if_fail (E_IS_CAL_BACKEND (backend));
809         g_return_if_fail (calobj != NULL);
810
811         if (CLASS (backend)->create_object)
812                 (* CLASS (backend)->create_object) (backend, cal, calobj);
813         else
814                 e_data_cal_notify_object_created (cal, GNOME_Evolution_Calendar_PermissionDenied, NULL, NULL);
815 }
816
817 void
818 e_cal_backend_modify_object (ECalBackend *backend, EDataCal *cal, const char *calobj, CalObjModType mod)
819 {
820         g_return_if_fail (backend != NULL);
821         g_return_if_fail (E_IS_CAL_BACKEND (backend));
822         g_return_if_fail (calobj != NULL);
823
824         if (CLASS (backend)->modify_object)
825                 (* CLASS (backend)->modify_object) (backend, cal, calobj, mod);
826         else
827                 e_data_cal_notify_object_removed (cal, GNOME_Evolution_Calendar_PermissionDenied, NULL, NULL, NULL);
828 }
829
830 /**
831  * e_cal_backend_remove_object:
832  * @backend: A calendar backend.
833  * @uid: Unique identifier of the object to remove.
834  * @rid: A recurrence ID.
835  * 
836  * Removes an object in a calendar backend.  The backend will notify all of its
837  * clients about the change.
838  * 
839  **/
840 void
841 e_cal_backend_remove_object (ECalBackend *backend, EDataCal *cal, const char *uid, const char *rid, CalObjModType mod)
842 {
843         g_return_if_fail (backend != NULL);
844         g_return_if_fail (E_IS_CAL_BACKEND (backend));
845         g_return_if_fail (uid != NULL);
846
847         g_assert (CLASS (backend)->remove_object != NULL);
848         (* CLASS (backend)->remove_object) (backend, cal, uid, rid, mod);
849 }
850
851 void
852 e_cal_backend_receive_objects (ECalBackend *backend, EDataCal *cal, const char *calobj)
853 {
854         g_return_if_fail (backend != NULL);
855         g_return_if_fail (E_IS_CAL_BACKEND (backend));
856         g_return_if_fail (calobj != NULL);
857
858         g_assert (CLASS (backend)->receive_objects != NULL);
859         (* CLASS (backend)->receive_objects) (backend, cal, calobj);
860 }
861
862 void
863 e_cal_backend_send_objects (ECalBackend *backend, EDataCal *cal, const char *calobj)
864 {
865         g_return_if_fail (backend != NULL);
866         g_return_if_fail (E_IS_CAL_BACKEND (backend));
867         g_return_if_fail (calobj != NULL);
868
869         g_assert (CLASS (backend)->send_objects != NULL);
870         (* CLASS (backend)->send_objects) (backend, cal, calobj);
871 }
872
873 /**
874  * e_cal_backend_get_timezone:
875  * @backend: A calendar backend.
876  * @tzid: Unique identifier of a VTIMEZONE object. Note that this must not be
877  * NULL.
878  * 
879  * Returns the icaltimezone* corresponding to the TZID, or NULL if the TZID
880  * can't be found.
881  * 
882  * Returns: The icaltimezone* corresponding to the given TZID, or NULL.
883  **/
884 void
885 e_cal_backend_get_timezone (ECalBackend *backend, EDataCal *cal, const char *tzid)
886 {
887         g_return_if_fail (backend != NULL);
888         g_return_if_fail (E_IS_CAL_BACKEND (backend));
889         g_return_if_fail (tzid != NULL);
890
891         g_assert (CLASS (backend)->get_timezone != NULL);
892         (* CLASS (backend)->get_timezone) (backend, cal, tzid);
893 }
894
895 /**
896  * e_cal_backend_set_default_timezone:
897  * @backend: A calendar backend.
898  * @tzid: The TZID identifying the timezone.
899  * 
900  * Sets the default timezone for the calendar, which is used to resolve
901  * DATE and floating DATE-TIME values.
902  * 
903  * Returns: TRUE if the VTIMEZONE data for the timezone was found, or FALSE if
904  * not.
905  **/
906 void
907 e_cal_backend_set_default_timezone (ECalBackend *backend, EDataCal *cal, const char *tzid)
908 {
909         g_return_if_fail (backend != NULL);
910         g_return_if_fail (E_IS_CAL_BACKEND (backend));
911         g_return_if_fail (tzid != NULL);
912
913         g_assert (CLASS (backend)->set_default_timezone != NULL);
914         (* CLASS (backend)->set_default_timezone) (backend, cal, tzid);
915 }
916
917 /**
918  * e_cal_backend_add_timezone
919  * @backend: A calendar backend.
920  * @tzobj: The timezone object, in a string.
921  *
922  * Add a timezone object to the given backend.
923  *
924  * Returns: TRUE if successful, or FALSE if not.
925  */
926 void
927 e_cal_backend_add_timezone (ECalBackend *backend, EDataCal *cal, const char *tzobj)
928 {
929         g_return_if_fail (E_IS_CAL_BACKEND (backend));
930         g_return_if_fail (tzobj != NULL);
931         g_return_if_fail (CLASS (backend)->add_timezone != NULL);
932
933         (* CLASS (backend)->add_timezone) (backend, cal, tzobj);
934 }
935
936 icaltimezone *
937 e_cal_backend_internal_get_default_timezone (ECalBackend *backend)
938 {
939         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
940         g_return_val_if_fail (CLASS (backend)->internal_get_default_timezone != NULL, NULL);
941
942         return (* CLASS (backend)->internal_get_default_timezone) (backend);
943 }
944
945 icaltimezone *
946 e_cal_backend_internal_get_timezone (ECalBackend *backend, const char *tzid)
947 {
948         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
949         g_return_val_if_fail (tzid != NULL, NULL);
950         g_return_val_if_fail (CLASS (backend)->internal_get_timezone != NULL, NULL);
951
952         return (* CLASS (backend)->internal_get_timezone) (backend, tzid);
953 }
954
955 void
956 e_cal_backend_set_notification_proxy (ECalBackend *backend, ECalBackend *proxy)
957 {
958         ECalBackendPrivate *priv;
959
960         g_return_if_fail (E_IS_CAL_BACKEND (backend));
961
962         priv = backend->priv;
963
964         priv->notification_proxy = proxy;
965 }
966
967 /**
968  * e_cal_backend_notify_object_created:
969  * @backend: A calendar backend.
970  * @calobj: iCalendar representation of new object
971  *
972  * Notifies each of the backend's listeners about a new object.
973  *
974  * cal_notify_object_created() calls this for you. You only need to
975  * call e_cal_backend_notify_object_created() yourself to report objects
976  * created by non-PCS clients.
977  **/
978 void
979 e_cal_backend_notify_object_created (ECalBackend *backend, const char *calobj)
980 {
981         ECalBackendPrivate *priv;
982         EList *queries;
983         EIterator *iter;
984         EDataCalView *query;
985
986         priv = backend->priv;
987
988         if (priv->notification_proxy) {
989                 e_cal_backend_notify_object_created (priv->notification_proxy, calobj);
990                 return;
991         }
992
993         queries = e_cal_backend_get_queries (backend);
994         iter = e_list_get_iterator (queries);
995
996         while (e_iterator_is_valid (iter)) {
997                 query = QUERY (e_iterator_get (iter));
998
999                 bonobo_object_ref (query);
1000                 if (e_data_cal_view_object_matches (query, calobj))             
1001                         e_data_cal_view_notify_objects_added_1 (query, calobj);
1002                 bonobo_object_unref (query);
1003
1004                 e_iterator_next (iter);
1005         }
1006         g_object_unref (iter);
1007 }
1008
1009 static void
1010 match_query_and_notify (EDataCalView *query, const char *old_object, const char *object)
1011 {
1012         gboolean old_match, new_match;
1013
1014         old_match = e_data_cal_view_object_matches (query, old_object);
1015         new_match = e_data_cal_view_object_matches (query, object);
1016         if (old_match && new_match)
1017                 e_data_cal_view_notify_objects_modified_1 (query, object);
1018         else if (new_match)
1019                 e_data_cal_view_notify_objects_added_1 (query, object);
1020         else if (old_match) {
1021                 icalcomponent *icalcomp;
1022         
1023                 icalcomp = icalcomponent_new_from_string ((char *) old_object);
1024                 if (icalcomp) {
1025                         e_data_cal_view_notify_objects_removed_1 (query, icalcomponent_get_uid (icalcomp));
1026                         icalcomponent_free (icalcomp);
1027                 }
1028         }
1029 }
1030
1031 /**
1032  * e_cal_backend_notify_object_modified:
1033  * @backend: A calendar backend.
1034  * @old_object: iCalendar representation of the original form of the object
1035  * @object: iCalendar representation of the new form of the object
1036  *
1037  * Notifies each of the backend's listeners about a modified object.
1038  *
1039  * cal_notify_object_modified() calls this for you. You only need to
1040  * call e_cal_backend_notify_object_modified() yourself to report objects
1041  * modified by non-PCS clients.
1042  **/
1043 void
1044 e_cal_backend_notify_object_modified (ECalBackend *backend, 
1045                                       const char *old_object, const char *object)
1046 {
1047         ECalBackendPrivate *priv;
1048         EList *queries;
1049         EIterator *iter;
1050         EDataCalView *query;
1051
1052         priv = backend->priv;
1053
1054         if (priv->notification_proxy) {
1055                 e_cal_backend_notify_object_modified (priv->notification_proxy, old_object, object);
1056                 return;
1057         }
1058
1059         queries = e_cal_backend_get_queries (backend);
1060         iter = e_list_get_iterator (queries);
1061
1062         while (e_iterator_is_valid (iter)) {
1063                 query = QUERY (e_iterator_get (iter));
1064
1065                 bonobo_object_ref (query);
1066                 match_query_and_notify (query, old_object, object);
1067                 bonobo_object_unref (query);
1068
1069                 e_iterator_next (iter);
1070         }
1071         g_object_unref (iter);
1072 }
1073
1074 /**
1075  * e_cal_backend_notify_object_removed:
1076  * @backend: A calendar backend.
1077  * @uid: the UID of the removed object
1078  * @old_object: iCalendar representation of the removed object
1079  * @new_object: iCalendar representation of the object after the removal. This
1080  * only applies to recurrent appointments that had an instance removed. In that
1081  * case, this function notifies a modification instead of a removal.
1082  *
1083  * Notifies each of the backend's listeners about a removed object.
1084  *
1085  * cal_notify_object_removed() calls this for you. You only need to
1086  * call e_cal_backend_notify_object_removed() yourself to report objects
1087  * removed by non-PCS clients.
1088  **/
1089 void
1090 e_cal_backend_notify_object_removed (ECalBackend *backend, const char *uid,
1091                                      const char *old_object, const char *object)
1092 {
1093         ECalBackendPrivate *priv;
1094         EList *queries;
1095         EIterator *iter;
1096         EDataCalView *query;
1097
1098         priv = backend->priv;
1099
1100         if (priv->notification_proxy) {
1101                 e_cal_backend_notify_object_removed (priv->notification_proxy, uid, old_object, object);
1102                 return;
1103         }
1104
1105         queries = e_cal_backend_get_queries (backend);
1106         iter = e_list_get_iterator (queries);
1107
1108         while (e_iterator_is_valid (iter)) {
1109                 query = QUERY (e_iterator_get (iter));
1110
1111                 bonobo_object_ref (query);
1112
1113                 if (object == NULL) {
1114                         /* if object == NULL, it means the object has been completely
1115                            removed from the backend */
1116                         if (e_data_cal_view_object_matches (query, old_object))
1117                                 e_data_cal_view_notify_objects_removed_1 (query, uid);
1118                 } else
1119                         match_query_and_notify (query, old_object, object);
1120
1121                 bonobo_object_unref (query);
1122
1123                 e_iterator_next (iter);
1124         }
1125         g_object_unref (iter);
1126 }
1127
1128 /**
1129  * e_cal_backend_notify_mode:
1130  * @backend: A calendar backend.
1131  * @status: Status of the mode set
1132  * @mode: the current mode
1133  *
1134  * Notifies each of the backend's listeners about the results of a
1135  * setMode call.
1136  **/
1137 void
1138 e_cal_backend_notify_mode (ECalBackend *backend,
1139                          GNOME_Evolution_Calendar_CalListener_SetModeStatus status, 
1140                          GNOME_Evolution_Calendar_CalMode mode)
1141 {
1142         ECalBackendPrivate *priv = backend->priv;
1143         GList *l;
1144
1145         if (priv->notification_proxy) {
1146                 e_cal_backend_notify_mode (priv->notification_proxy, status, mode);
1147                 return;
1148         }
1149
1150         for (l = priv->clients; l; l = l->next)
1151                 e_data_cal_notify_mode (l->data, status, mode);
1152 }
1153
1154 /**
1155  * e_cal_backend_notify_error:
1156  * @backend: A calendar backend.
1157  * @message: Error message
1158  *
1159  * Notifies each of the backend's listeners about an error
1160  **/
1161 void
1162 e_cal_backend_notify_error (ECalBackend *backend, const char *message)
1163 {
1164         ECalBackendPrivate *priv = backend->priv;
1165         GList *l;
1166
1167         if (priv->notification_proxy) {
1168                 e_cal_backend_notify_error (priv->notification_proxy, message);
1169                 return;
1170         }
1171
1172         for (l = priv->clients; l; l = l->next)
1173                 e_data_cal_notify_error (l->data, message);
1174 }
1175
1176 static void
1177 add_category_cb (gpointer name, gpointer category, gpointer data)
1178 {
1179         GNOME_Evolution_Calendar_StringSeq *seq = data;
1180
1181         seq->_buffer[seq->_length++] = CORBA_string_dup (name);
1182 }
1183
1184 static void
1185 notify_categories_changed (ECalBackend *backend)
1186 {
1187         ECalBackendPrivate *priv = backend->priv;
1188         GNOME_Evolution_Calendar_StringSeq *seq;
1189         GList *l;
1190
1191         /* Build the sequence of category names */
1192         seq = GNOME_Evolution_Calendar_StringSeq__alloc ();
1193         seq->_length = 0;
1194         seq->_maximum = g_hash_table_size (priv->categories);
1195         seq->_buffer = CORBA_sequence_CORBA_string_allocbuf (seq->_maximum);
1196         CORBA_sequence_set_release (seq, TRUE);
1197
1198         g_hash_table_foreach (priv->categories, add_category_cb, seq);
1199
1200         /* Notify the clients */
1201         for (l = priv->clients; l; l = l->next)
1202                 e_data_cal_notify_categories_changed (l->data, seq);
1203
1204         CORBA_free (seq);
1205 }
1206
1207 static gboolean
1208 idle_notify_categories_changed (gpointer data)
1209 {
1210         ECalBackend *backend = E_CAL_BACKEND (data);
1211         ECalBackendPrivate *priv = backend->priv;
1212
1213         if (g_hash_table_size (priv->changed_categories)) {
1214                 notify_categories_changed (backend);
1215                 g_hash_table_foreach_remove (priv->changed_categories, prune_changed_categories, NULL);
1216         }
1217
1218         priv->category_idle_id = 0;
1219         
1220         return FALSE;
1221 }
1222
1223 /**
1224  * e_cal_backend_ref_categories:
1225  * @backend: A calendar backend
1226  * @categories: a list of categories
1227  *
1228  * Adds 1 to the refcount of each of the named categories. If any of
1229  * the categories are new, clients will be notified of the updated
1230  * category list at idle time.
1231  **/
1232 void
1233 e_cal_backend_ref_categories (ECalBackend *backend, GSList *categories)
1234 {
1235         ECalBackendPrivate *priv;
1236         ECalBackendCategory *c;
1237         const char *name;
1238
1239         priv = backend->priv;
1240
1241         while (categories) {
1242                 name = categories->data;
1243                 c = g_hash_table_lookup (priv->categories, name);
1244
1245                 if (c)
1246                         c->refcount++;
1247                 else {
1248                         /* See if it was recently removed */
1249
1250                         c = g_hash_table_lookup (priv->changed_categories, name);
1251                         if (c && c->refcount == 0) {
1252                                 /* Move it back to the set of live categories */
1253                                 g_hash_table_remove (priv->changed_categories, c->name);
1254
1255                                 c->refcount = 1;
1256                                 g_hash_table_insert (priv->categories, c->name, c);
1257                         } else {
1258                                 /* Create a new category */
1259                                 c = g_new (ECalBackendCategory, 1);
1260                                 c->name = g_strdup (name);
1261                                 c->refcount = 1;
1262                                 g_hash_table_insert (priv->categories, c->name, c);
1263                                 g_hash_table_insert (priv->changed_categories, c->name, c);
1264                         }
1265                 }
1266
1267                 categories = categories->next;
1268         }
1269
1270         if (g_hash_table_size (priv->changed_categories) &&
1271             !priv->category_idle_id)
1272                 priv->category_idle_id = g_idle_add (idle_notify_categories_changed, backend);
1273 }
1274
1275 /**
1276  * e_cal_backend_unref_categories:
1277  * @backend: A calendar backend
1278  * @categories: a list of categories
1279  *
1280  * Subtracts 1 from the refcount of each of the named categories. If
1281  * any of the refcounts go down to 0, clients will be notified of the
1282  * updated category list at idle time.
1283  **/
1284 void
1285 e_cal_backend_unref_categories (ECalBackend *backend, GSList *categories)
1286 {
1287         ECalBackendPrivate *priv;
1288         ECalBackendCategory *c;
1289         const char *name;
1290
1291         priv = backend->priv;
1292
1293         while (categories) {
1294                 name = categories->data;
1295                 c = g_hash_table_lookup (priv->categories, name);
1296
1297                 if (c) {
1298                         g_assert (c != NULL);
1299                         g_assert (c->refcount > 0);
1300
1301                         c->refcount--;
1302
1303                         if (c->refcount == 0) {
1304                                 g_hash_table_remove (priv->categories, c->name);
1305                                 g_hash_table_insert (priv->changed_categories, c->name, c);
1306                         }
1307                 }
1308
1309                 categories = categories->next;
1310         }
1311
1312         if (g_hash_table_size (priv->changed_categories) &&
1313             !priv->category_idle_id)
1314                 priv->category_idle_id = g_idle_add (idle_notify_categories_changed, backend);
1315 }