added new arguments to the notifyObjectsSent method for backends to return
[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 g_object_ref (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         g_assert (CLASS (backend)->create_object != NULL);
812         (* CLASS (backend)->create_object) (backend, cal, calobj);
813 }
814
815 void
816 e_cal_backend_modify_object (ECalBackend *backend, EDataCal *cal, const char *calobj, CalObjModType mod)
817 {
818         g_return_if_fail (backend != NULL);
819         g_return_if_fail (E_IS_CAL_BACKEND (backend));
820         g_return_if_fail (calobj != NULL);
821
822         g_assert (CLASS (backend)->modify_object != NULL);
823         (* CLASS (backend)->modify_object) (backend, cal, calobj, mod);
824 }
825
826 /**
827  * e_cal_backend_remove_object:
828  * @backend: A calendar backend.
829  * @uid: Unique identifier of the object to remove.
830  * @rid: A recurrence ID.
831  * 
832  * Removes an object in a calendar backend.  The backend will notify all of its
833  * clients about the change.
834  * 
835  **/
836 void
837 e_cal_backend_remove_object (ECalBackend *backend, EDataCal *cal, const char *uid, const char *rid, CalObjModType mod)
838 {
839         g_return_if_fail (backend != NULL);
840         g_return_if_fail (E_IS_CAL_BACKEND (backend));
841         g_return_if_fail (uid != NULL);
842
843         g_assert (CLASS (backend)->remove_object != NULL);
844         (* CLASS (backend)->remove_object) (backend, cal, uid, rid, mod);
845 }
846
847 void
848 e_cal_backend_receive_objects (ECalBackend *backend, EDataCal *cal, const char *calobj)
849 {
850         g_return_if_fail (backend != NULL);
851         g_return_if_fail (E_IS_CAL_BACKEND (backend));
852         g_return_if_fail (calobj != NULL);
853
854         g_assert (CLASS (backend)->receive_objects != NULL);
855         (* CLASS (backend)->receive_objects) (backend, cal, calobj);
856 }
857
858 void
859 e_cal_backend_send_objects (ECalBackend *backend, EDataCal *cal, const char *calobj)
860 {
861         g_return_if_fail (backend != NULL);
862         g_return_if_fail (E_IS_CAL_BACKEND (backend));
863         g_return_if_fail (calobj != NULL);
864
865         g_assert (CLASS (backend)->send_objects != NULL);
866         (* CLASS (backend)->send_objects) (backend, cal, calobj);
867 }
868
869 /**
870  * e_cal_backend_get_timezone:
871  * @backend: A calendar backend.
872  * @tzid: Unique identifier of a VTIMEZONE object. Note that this must not be
873  * NULL.
874  * 
875  * Returns the icaltimezone* corresponding to the TZID, or NULL if the TZID
876  * can't be found.
877  * 
878  * Returns: The icaltimezone* corresponding to the given TZID, or NULL.
879  **/
880 void
881 e_cal_backend_get_timezone (ECalBackend *backend, EDataCal *cal, const char *tzid)
882 {
883         g_return_if_fail (backend != NULL);
884         g_return_if_fail (E_IS_CAL_BACKEND (backend));
885         g_return_if_fail (tzid != NULL);
886
887         g_assert (CLASS (backend)->get_timezone != NULL);
888         (* CLASS (backend)->get_timezone) (backend, cal, tzid);
889 }
890
891 /**
892  * e_cal_backend_set_default_timezone:
893  * @backend: A calendar backend.
894  * @tzid: The TZID identifying the timezone.
895  * 
896  * Sets the default timezone for the calendar, which is used to resolve
897  * DATE and floating DATE-TIME values.
898  * 
899  * Returns: TRUE if the VTIMEZONE data for the timezone was found, or FALSE if
900  * not.
901  **/
902 void
903 e_cal_backend_set_default_timezone (ECalBackend *backend, EDataCal *cal, const char *tzid)
904 {
905         g_return_if_fail (backend != NULL);
906         g_return_if_fail (E_IS_CAL_BACKEND (backend));
907         g_return_if_fail (tzid != NULL);
908
909         g_assert (CLASS (backend)->set_default_timezone != NULL);
910         (* CLASS (backend)->set_default_timezone) (backend, cal, tzid);
911 }
912
913 /**
914  * e_cal_backend_add_timezone
915  * @backend: A calendar backend.
916  * @tzobj: The timezone object, in a string.
917  *
918  * Add a timezone object to the given backend.
919  *
920  * Returns: TRUE if successful, or FALSE if not.
921  */
922 void
923 e_cal_backend_add_timezone (ECalBackend *backend, EDataCal *cal, const char *tzobj)
924 {
925         g_return_if_fail (E_IS_CAL_BACKEND (backend));
926         g_return_if_fail (tzobj != NULL);
927         g_return_if_fail (CLASS (backend)->add_timezone != NULL);
928
929         (* CLASS (backend)->add_timezone) (backend, cal, tzobj);
930 }
931
932 icaltimezone *
933 e_cal_backend_internal_get_default_timezone (ECalBackend *backend)
934 {
935         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
936         g_return_val_if_fail (CLASS (backend)->internal_get_default_timezone != NULL, NULL);
937
938         return (* CLASS (backend)->internal_get_default_timezone) (backend);
939 }
940
941 icaltimezone *
942 e_cal_backend_internal_get_timezone (ECalBackend *backend, const char *tzid)
943 {
944         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
945         g_return_val_if_fail (tzid != NULL, NULL);
946         g_return_val_if_fail (CLASS (backend)->internal_get_timezone != NULL, NULL);
947
948         return (* CLASS (backend)->internal_get_timezone) (backend, tzid);
949 }
950
951 void
952 e_cal_backend_set_notification_proxy (ECalBackend *backend, ECalBackend *proxy)
953 {
954         ECalBackendPrivate *priv;
955
956         g_return_if_fail (E_IS_CAL_BACKEND (backend));
957
958         priv = backend->priv;
959
960         priv->notification_proxy = proxy;
961 }
962
963 /**
964  * e_cal_backend_notify_object_created:
965  * @backend: A calendar backend.
966  * @calobj: iCalendar representation of new object
967  *
968  * Notifies each of the backend's listeners about a new object.
969  *
970  * cal_notify_object_created() calls this for you. You only need to
971  * call e_cal_backend_notify_object_created() yourself to report objects
972  * created by non-PCS clients.
973  **/
974 void
975 e_cal_backend_notify_object_created (ECalBackend *backend, const char *calobj)
976 {
977         ECalBackendPrivate *priv;
978         EList *queries;
979         EIterator *iter;
980         EDataCalView *query;
981
982         priv = backend->priv;
983
984         if (priv->notification_proxy) {
985                 e_cal_backend_notify_object_created (priv->notification_proxy, calobj);
986                 return;
987         }
988
989         queries = e_cal_backend_get_queries (backend);
990         iter = e_list_get_iterator (queries);
991
992         while (e_iterator_is_valid (iter)) {
993                 query = QUERY (e_iterator_get (iter));
994
995                 bonobo_object_ref (query);
996                 if (e_data_cal_view_object_matches (query, calobj))             
997                         e_data_cal_view_notify_objects_added_1 (query, calobj);
998                 bonobo_object_unref (query);
999
1000                 e_iterator_next (iter);
1001         }
1002         g_object_unref (iter);
1003         g_object_unref (queries);
1004 }
1005
1006 /**
1007  * e_cal_backend_notify_object_modified:
1008  * @backend: A calendar backend.
1009  * @old_object: iCalendar representation of the original form of the object
1010  * @object: iCalendar representation of the new form of the object
1011  *
1012  * Notifies each of the backend's listeners about a modified object.
1013  *
1014  * cal_notify_object_modified() calls this for you. You only need to
1015  * call e_cal_backend_notify_object_modified() yourself to report objects
1016  * modified by non-PCS clients.
1017  **/
1018 void
1019 e_cal_backend_notify_object_modified (ECalBackend *backend, 
1020                                     const char *old_object, const char *object)
1021 {
1022         ECalBackendPrivate *priv;
1023         EList *queries;
1024         EIterator *iter;
1025         EDataCalView *query;
1026         gboolean old_match, new_match;
1027
1028         priv = backend->priv;
1029
1030         if (priv->notification_proxy) {
1031                 e_cal_backend_notify_object_modified (priv->notification_proxy, old_object, object);
1032                 return;
1033         }
1034
1035         queries = e_cal_backend_get_queries (backend);
1036         iter = e_list_get_iterator (queries);
1037
1038         while (e_iterator_is_valid (iter)) {
1039                 query = QUERY (e_iterator_get (iter));
1040                 
1041                 bonobo_object_ref (query);
1042
1043                 old_match = e_data_cal_view_object_matches (query, old_object);
1044                 new_match = e_data_cal_view_object_matches (query, object);
1045                 if (old_match && new_match)
1046                         e_data_cal_view_notify_objects_modified_1 (query, object);
1047                 else if (new_match)
1048                         e_data_cal_view_notify_objects_added_1 (query, object);
1049                 else /* if (old_match) */ {
1050                         icalcomponent *comp;
1051
1052                         comp = icalcomponent_new_from_string ((char *)old_object);
1053                         e_data_cal_view_notify_objects_removed_1 (query, icalcomponent_get_uid (comp));
1054                         icalcomponent_free (comp);
1055                 }
1056
1057                 bonobo_object_unref (query);
1058
1059                 e_iterator_next (iter);
1060         }
1061         g_object_unref (iter);
1062         g_object_unref (queries);
1063 }
1064
1065 /**
1066  * e_cal_backend_notify_object_removed:
1067  * @backend: A calendar backend.
1068  * @uid: the UID of the removed object
1069  * @old_object: iCalendar representation of the removed object
1070  *
1071  * Notifies each of the backend's listeners about a removed object.
1072  *
1073  * cal_notify_object_removed() calls this for you. You only need to
1074  * call e_cal_backend_notify_object_removed() yourself to report objects
1075  * removed by non-PCS clients.
1076  **/
1077 void
1078 e_cal_backend_notify_object_removed (ECalBackend *backend, const char *uid,
1079                                    const char *old_object)
1080 {
1081         ECalBackendPrivate *priv;
1082         EList *queries;
1083         EIterator *iter;
1084         EDataCalView *query;
1085
1086         priv = backend->priv;
1087
1088         if (priv->notification_proxy) {
1089                 e_cal_backend_notify_object_removed (priv->notification_proxy, uid, old_object);
1090                 return;
1091         }
1092
1093         queries = e_cal_backend_get_queries (backend);
1094         iter = e_list_get_iterator (queries);
1095
1096         while (e_iterator_is_valid (iter)) {
1097                 query = QUERY (e_iterator_get (iter));
1098
1099                 bonobo_object_ref (query);
1100                 if (e_data_cal_view_object_matches (query, old_object))
1101                         e_data_cal_view_notify_objects_removed_1 (query, uid);
1102                 bonobo_object_unref (query);
1103
1104                 e_iterator_next (iter);
1105         }
1106         g_object_unref (iter);
1107         g_object_unref (queries);
1108 }
1109
1110 /**
1111  * e_cal_backend_notify_mode:
1112  * @backend: A calendar backend.
1113  * @status: Status of the mode set
1114  * @mode: the current mode
1115  *
1116  * Notifies each of the backend's listeners about the results of a
1117  * setMode call.
1118  **/
1119 void
1120 e_cal_backend_notify_mode (ECalBackend *backend,
1121                          GNOME_Evolution_Calendar_CalListener_SetModeStatus status, 
1122                          GNOME_Evolution_Calendar_CalMode mode)
1123 {
1124         ECalBackendPrivate *priv = backend->priv;
1125         GList *l;
1126
1127         if (priv->notification_proxy) {
1128                 e_cal_backend_notify_mode (priv->notification_proxy, status, mode);
1129                 return;
1130         }
1131
1132         for (l = priv->clients; l; l = l->next)
1133                 e_data_cal_notify_mode (l->data, status, mode);
1134 }
1135
1136 /**
1137  * e_cal_backend_notify_error:
1138  * @backend: A calendar backend.
1139  * @message: Error message
1140  *
1141  * Notifies each of the backend's listeners about an error
1142  **/
1143 void
1144 e_cal_backend_notify_error (ECalBackend *backend, const char *message)
1145 {
1146         ECalBackendPrivate *priv = backend->priv;
1147         GList *l;
1148
1149         if (priv->notification_proxy) {
1150                 e_cal_backend_notify_error (priv->notification_proxy, message);
1151                 return;
1152         }
1153
1154         for (l = priv->clients; l; l = l->next)
1155                 e_data_cal_notify_error (l->data, message);
1156 }
1157
1158 static void
1159 add_category_cb (gpointer name, gpointer category, gpointer data)
1160 {
1161         GNOME_Evolution_Calendar_StringSeq *seq = data;
1162
1163         seq->_buffer[seq->_length++] = CORBA_string_dup (name);
1164 }
1165
1166 static void
1167 notify_categories_changed (ECalBackend *backend)
1168 {
1169         ECalBackendPrivate *priv = backend->priv;
1170         GNOME_Evolution_Calendar_StringSeq *seq;
1171         GList *l;
1172
1173         /* Build the sequence of category names */
1174         seq = GNOME_Evolution_Calendar_StringSeq__alloc ();
1175         seq->_length = 0;
1176         seq->_maximum = g_hash_table_size (priv->categories);
1177         seq->_buffer = CORBA_sequence_CORBA_string_allocbuf (seq->_maximum);
1178         CORBA_sequence_set_release (seq, TRUE);
1179
1180         g_hash_table_foreach (priv->categories, add_category_cb, seq);
1181
1182         /* Notify the clients */
1183         for (l = priv->clients; l; l = l->next)
1184                 e_data_cal_notify_categories_changed (l->data, seq);
1185
1186         CORBA_free (seq);
1187 }
1188
1189 static gboolean
1190 idle_notify_categories_changed (gpointer data)
1191 {
1192         ECalBackend *backend = E_CAL_BACKEND (data);
1193         ECalBackendPrivate *priv = backend->priv;
1194
1195         if (g_hash_table_size (priv->changed_categories)) {
1196                 notify_categories_changed (backend);
1197                 g_hash_table_foreach_remove (priv->changed_categories, prune_changed_categories, NULL);
1198         }
1199
1200         priv->category_idle_id = 0;
1201         
1202         return FALSE;
1203 }
1204
1205 /**
1206  * e_cal_backend_ref_categories:
1207  * @backend: A calendar backend
1208  * @categories: a list of categories
1209  *
1210  * Adds 1 to the refcount of each of the named categories. If any of
1211  * the categories are new, clients will be notified of the updated
1212  * category list at idle time.
1213  **/
1214 void
1215 e_cal_backend_ref_categories (ECalBackend *backend, GSList *categories)
1216 {
1217         ECalBackendPrivate *priv;
1218         ECalBackendCategory *c;
1219         const char *name;
1220
1221         priv = backend->priv;
1222
1223         while (categories) {
1224                 name = categories->data;
1225                 c = g_hash_table_lookup (priv->categories, name);
1226
1227                 if (c)
1228                         c->refcount++;
1229                 else {
1230                         /* See if it was recently removed */
1231
1232                         c = g_hash_table_lookup (priv->changed_categories, name);
1233                         if (c && c->refcount == 0) {
1234                                 /* Move it back to the set of live categories */
1235                                 g_hash_table_remove (priv->changed_categories, c->name);
1236
1237                                 c->refcount = 1;
1238                                 g_hash_table_insert (priv->categories, c->name, c);
1239                         } else {
1240                                 /* Create a new category */
1241                                 c = g_new (ECalBackendCategory, 1);
1242                                 c->name = g_strdup (name);
1243                                 c->refcount = 1;
1244                                 g_hash_table_insert (priv->categories, c->name, c);
1245                                 g_hash_table_insert (priv->changed_categories, c->name, c);
1246                         }
1247                 }
1248
1249                 categories = categories->next;
1250         }
1251
1252         if (g_hash_table_size (priv->changed_categories) &&
1253             !priv->category_idle_id)
1254                 priv->category_idle_id = g_idle_add (idle_notify_categories_changed, backend);
1255 }
1256
1257 /**
1258  * e_cal_backend_unref_categories:
1259  * @backend: A calendar backend
1260  * @categories: a list of categories
1261  *
1262  * Subtracts 1 from the refcount of each of the named categories. If
1263  * any of the refcounts go down to 0, clients will be notified of the
1264  * updated category list at idle time.
1265  **/
1266 void
1267 e_cal_backend_unref_categories (ECalBackend *backend, GSList *categories)
1268 {
1269         ECalBackendPrivate *priv;
1270         ECalBackendCategory *c;
1271         const char *name;
1272
1273         priv = backend->priv;
1274
1275         while (categories) {
1276                 name = categories->data;
1277                 c = g_hash_table_lookup (priv->categories, name);
1278
1279                 if (c) {
1280                         g_assert (c != NULL);
1281                         g_assert (c->refcount > 0);
1282
1283                         c->refcount--;
1284
1285                         if (c->refcount == 0) {
1286                                 g_hash_table_remove (priv->categories, c->name);
1287                                 g_hash_table_insert (priv->changed_categories, c->name, c);
1288                         }
1289                 }
1290
1291                 categories = categories->next;
1292         }
1293
1294         if (g_hash_table_size (priv->changed_categories) &&
1295             !priv->category_idle_id)
1296                 priv->category_idle_id = g_idle_add (idle_notify_categories_changed, backend);
1297 }