Bug #655499 - Don't crash when getting contacts before backend is opened
[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) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * Authors: Federico Mena-Quintero <federico@ximian.com>
7  *          JP Rosevear <jpr@ximian.com>
8  *          Rodrigo Moya <rodrigo@ximian.com>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of version 2 of the GNU Lesser General Public
12  * License as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  */
23
24 #include <config.h>
25
26 #include <glib/gi18n-lib.h>
27
28 #include <libedataserver/e-data-server-util.h>
29
30 #include "e-cal-backend.h"
31 #include "e-cal-backend-cache.h"
32
33 #define EDC_ERROR(_code)        e_data_cal_create_error (_code, NULL)
34 #define EDC_OPENING_ERROR       e_data_cal_create_error (Busy, _("Cannot process, calendar backend is opening"))
35 #define EDC_NOT_OPENED_ERROR    e_data_cal_create_error (NotOpened, NULL)
36
37 /* Private part of the CalBackend structure */
38 struct _ECalBackendPrivate {
39         /* The source for this backend */
40         ESource *source;
41         /* The kind of components for this backend */
42         icalcomponent_kind kind;
43
44         gboolean opening, opened, readonly, removed, online;
45
46         /* URI, from source. This is cached, since we return const. */
47         gchar *uri;
48
49         gchar *cache_dir;
50
51         /* List of Cal objects */
52         GMutex *clients_mutex;
53         GSList *clients;
54
55         GMutex *views_mutex;
56         GSList *views;
57
58         /* ECalBackend to pass notifications on to */
59         ECalBackend *notification_proxy;
60
61 };
62
63 /* Property IDs */
64 enum {
65         PROP_0,
66         PROP_CACHE_DIR,
67         PROP_KIND,
68         PROP_SOURCE,
69         PROP_URI
70 };
71
72 /* Signal IDs */
73 enum {
74         LAST_CLIENT_GONE,
75         LAST_SIGNAL
76 };
77
78 static guint signals[LAST_SIGNAL];
79
80 static void e_cal_backend_remove_client_private (ECalBackend *backend, EDataCal *cal, gboolean weak_unref);
81
82 G_DEFINE_TYPE (ECalBackend, e_cal_backend, G_TYPE_OBJECT);
83
84 static void
85 source_changed_cb (ESource *source, ECalBackend *backend)
86 {
87         ECalBackendPrivate *priv;
88         gchar *suri;
89
90         g_return_if_fail (source != NULL);
91         g_return_if_fail (backend != NULL);
92         g_return_if_fail (E_IS_CAL_BACKEND (backend));
93
94         priv = backend->priv;
95         g_return_if_fail (priv != NULL);
96         g_return_if_fail (priv->source == source);
97
98         suri = e_source_get_uri (priv->source);
99         if (!priv->uri || (suri && !g_str_equal (priv->uri, suri))) {
100                 g_free (priv->uri);
101                 priv->uri = suri;
102         } else {
103                 g_free (suri);
104         }
105 }
106
107 static void
108 cal_backend_set_default_cache_dir (ECalBackend *backend)
109 {
110         ESource *source;
111         icalcomponent_kind kind;
112         const gchar *component_type;
113         const gchar *user_cache_dir;
114         gchar *mangled_uri;
115         gchar *filename;
116
117         user_cache_dir = e_get_user_cache_dir ();
118
119         kind = e_cal_backend_get_kind (backend);
120         source = e_cal_backend_get_source (backend);
121
122         switch (kind) {
123                 case ICAL_VEVENT_COMPONENT:
124                         component_type = "calendar";
125                         break;
126                 case ICAL_VTODO_COMPONENT:
127                         component_type = "tasks";
128                         break;
129                 case ICAL_VJOURNAL_COMPONENT:
130                         component_type = "memos";
131                         break;
132                 default:
133                         g_return_if_reached ();
134         }
135
136         /* Mangle the URI to not contain invalid characters. */
137         mangled_uri = g_strdelimit (e_source_get_uri (source), ":/", '_');
138
139         filename = g_build_filename (
140                 user_cache_dir, component_type, mangled_uri, NULL);
141         e_cal_backend_set_cache_dir (backend, filename);
142         g_free (filename);
143
144         g_free (mangled_uri);
145 }
146
147 static void
148 cal_backend_set_source (ECalBackend *backend,
149                         ESource *source)
150 {
151         if (backend->priv->source != NULL) {
152                 g_signal_handlers_disconnect_matched (backend->priv->source, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, source_changed_cb, backend);
153                 g_object_unref (backend->priv->source);
154         }
155
156         if (source != NULL)
157                 g_signal_connect (
158                         g_object_ref (source), "changed",
159                         G_CALLBACK (source_changed_cb), backend);
160
161         backend->priv->source = source;
162
163         /* Cache the URI */
164         if (source != NULL) {
165                 g_free (backend->priv->uri);
166                 backend->priv->uri = e_source_get_uri (source);
167         }
168 }
169
170 static void
171 cal_backend_set_uri (ECalBackend *backend,
172                      const gchar *uri)
173 {
174         /* ESource's URI gets priority. */
175         if (backend->priv->source == NULL) {
176                 g_free (backend->priv->uri);
177                 backend->priv->uri = g_strdup (uri);
178         }
179 }
180
181 static void
182 cal_backend_set_kind (ECalBackend *backend,
183                       icalcomponent_kind kind)
184 {
185         backend->priv->kind = kind;
186 }
187
188 static void
189 cal_backend_get_backend_property (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *prop_name)
190 {
191         g_return_if_fail (backend != NULL);
192         g_return_if_fail (E_IS_CAL_BACKEND (backend));
193         g_return_if_fail (cal != NULL);
194         g_return_if_fail (prop_name != NULL);
195
196         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_OPENED)) {
197                 e_data_cal_respond_get_backend_property (cal, opid, NULL, e_cal_backend_is_opened (backend) ? "TRUE" : "FALSE");
198         } else if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_OPENING)) {
199                 e_data_cal_respond_get_backend_property (cal, opid, NULL, e_cal_backend_is_opening (backend) ? "TRUE" : "FALSE");
200         } else if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_ONLINE)) {
201                 e_data_cal_respond_get_backend_property (cal, opid, NULL, e_cal_backend_is_online (backend) ? "TRUE" : "FALSE");
202         } else if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_READONLY)) {
203                 e_data_cal_respond_get_backend_property (cal, opid, NULL, e_cal_backend_is_readonly (backend) ? "TRUE" : "FALSE");
204         } else if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CACHE_DIR)) {
205                 e_data_cal_respond_get_backend_property (cal, opid, NULL, e_cal_backend_get_cache_dir (backend));
206         } else {
207                 e_data_cal_respond_get_backend_property (cal, opid, e_data_cal_create_error_fmt (NotSupported, _("Unknown calendar property '%s'"), prop_name), NULL);
208         }
209 }
210
211 static void
212 cal_backend_set_backend_property (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *prop_name, const gchar *prop_value)
213 {
214         g_return_if_fail (backend != NULL);
215         g_return_if_fail (E_IS_CAL_BACKEND (backend));
216         g_return_if_fail (cal != NULL);
217         g_return_if_fail (prop_name != NULL);
218
219         e_data_cal_respond_set_backend_property (cal, opid, e_data_cal_create_error_fmt (NotSupported, _("Cannot change value of calendar property '%s'"), prop_name));
220 }
221
222 static void
223 cal_backend_set_property (GObject *object,
224                           guint property_id,
225                           const GValue *value,
226                           GParamSpec *pspec)
227 {
228         switch (property_id) {
229                 case PROP_CACHE_DIR:
230                         e_cal_backend_set_cache_dir (
231                                 E_CAL_BACKEND (object),
232                                 g_value_get_string (value));
233                         return;
234                 case PROP_KIND:
235                         cal_backend_set_kind (
236                                 E_CAL_BACKEND (object),
237                                 g_value_get_ulong (value));
238                         return;
239                 case PROP_SOURCE:
240                         cal_backend_set_source (
241                                 E_CAL_BACKEND (object),
242                                 g_value_get_object (value));
243                         return;
244                 case PROP_URI:
245                         cal_backend_set_uri (
246                                 E_CAL_BACKEND (object),
247                                 g_value_get_string (value));
248                         return;
249         }
250
251         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
252 }
253
254 static void
255 cal_backend_get_property (GObject *object,
256                           guint property_id,
257                           GValue *value,
258                           GParamSpec *pspec)
259 {
260         switch (property_id) {
261                 case PROP_CACHE_DIR:
262                         g_value_set_string (
263                                 value, e_cal_backend_get_cache_dir (
264                                 E_CAL_BACKEND (object)));
265                         return;
266                 case PROP_KIND:
267                         g_value_set_ulong (
268                                 value, e_cal_backend_get_kind (
269                                 E_CAL_BACKEND (object)));
270                         return;
271                 case PROP_SOURCE:
272                         g_value_set_object (
273                                 value, e_cal_backend_get_source (
274                                 E_CAL_BACKEND (object)));
275                         return;
276                 case PROP_URI:
277                         g_value_set_string (
278                                 value, e_cal_backend_get_uri (
279                                 E_CAL_BACKEND (object)));
280                         return;
281         }
282
283         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
284 }
285
286 static void
287 cal_backend_finalize (GObject *object)
288 {
289         ECalBackendPrivate *priv;
290
291         priv = E_CAL_BACKEND (object)->priv;
292
293         g_assert (priv->clients == NULL);
294
295         g_slist_free (priv->views);
296         /* should be NULL, anyway */
297         g_slist_free (priv->clients);
298
299         g_mutex_free (priv->clients_mutex);
300         g_mutex_free (priv->views_mutex);
301
302         g_free (priv->uri);
303         g_free (priv->cache_dir);
304
305         if (priv->source) {
306                 g_signal_handlers_disconnect_matched (priv->source, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
307                 g_object_unref (priv->source);
308         }
309
310         /* Chain up to parent's finalize() method. */
311         G_OBJECT_CLASS (e_cal_backend_parent_class)->finalize (object);
312 }
313
314 static void
315 cal_backend_constructed (GObject *object)
316 {
317         cal_backend_set_default_cache_dir (E_CAL_BACKEND (object));
318
319         G_OBJECT_CLASS (e_cal_backend_parent_class)->constructed (object);
320 }
321
322 static void
323 e_cal_backend_class_init (ECalBackendClass *klass)
324 {
325         GObjectClass *object_class;
326
327         g_type_class_add_private (klass, sizeof (ECalBackendPrivate));
328
329         object_class = G_OBJECT_CLASS (klass);
330         object_class->set_property = cal_backend_set_property;
331         object_class->get_property = cal_backend_get_property;
332         object_class->finalize = cal_backend_finalize;
333         object_class->constructed = cal_backend_constructed;
334
335         klass->get_backend_property = cal_backend_get_backend_property;
336         klass->set_backend_property = cal_backend_set_backend_property;
337
338         g_object_class_install_property (
339                 object_class,
340                 PROP_CACHE_DIR,
341                 g_param_spec_string (
342                         "cache-dir",
343                         NULL,
344                         NULL,
345                         NULL,
346                         G_PARAM_READWRITE));
347
348         g_object_class_install_property (
349                 object_class,
350                 PROP_KIND,
351                 g_param_spec_ulong (
352                         "kind",
353                         NULL,
354                         NULL,
355                         ICAL_NO_COMPONENT,
356                         ICAL_XLICMIMEPART_COMPONENT,
357                         ICAL_NO_COMPONENT,
358                         G_PARAM_READWRITE |
359                         G_PARAM_CONSTRUCT_ONLY));
360
361         g_object_class_install_property (
362                 object_class,
363                 PROP_SOURCE,
364                 g_param_spec_object (
365                         "source",
366                         NULL,
367                         NULL,
368                         E_TYPE_SOURCE,
369                         G_PARAM_READWRITE |
370                         G_PARAM_CONSTRUCT_ONLY));
371
372         g_object_class_install_property (
373                 object_class,
374                 PROP_URI,
375                 g_param_spec_string (
376                         "uri",
377                         NULL,
378                         NULL,
379                         "",
380                         G_PARAM_READWRITE |
381                         G_PARAM_CONSTRUCT_ONLY));
382
383         signals[LAST_CLIENT_GONE] = g_signal_new (
384                 "last_client_gone",
385                 G_TYPE_FROM_CLASS (klass),
386                 G_SIGNAL_RUN_FIRST,
387                 G_STRUCT_OFFSET (ECalBackendClass, last_client_gone),
388                 NULL, NULL,
389                 g_cclosure_marshal_VOID__VOID,
390                 G_TYPE_NONE, 0);
391 }
392
393 static void
394 e_cal_backend_init (ECalBackend *backend)
395 {
396         backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (
397                 backend, E_TYPE_CAL_BACKEND, ECalBackendPrivate);
398
399         backend->priv->clients = NULL;
400         backend->priv->clients_mutex = g_mutex_new ();
401
402         backend->priv->views = NULL;
403         backend->priv->views_mutex = g_mutex_new ();
404
405         backend->priv->readonly = TRUE;
406         backend->priv->online = FALSE;
407 }
408
409 /**
410  * e_cal_backend_get_source:
411  * @backend: an #ECalBackend
412  *
413  * Gets the #ESource associated with the given backend.
414  *
415  * Returns: The #ESource for the backend.
416  */
417 ESource *
418 e_cal_backend_get_source (ECalBackend *backend)
419 {
420         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
421
422         return backend->priv->source;
423 }
424
425 /**
426  * e_cal_backend_get_uri:
427  * @backend: an #ECalBackend
428  *
429  * Queries the URI of a calendar backend, which must already have an open
430  * calendar.
431  *
432  * Returns: The URI where the calendar is stored.
433  **/
434 const gchar *
435 e_cal_backend_get_uri (ECalBackend *backend)
436 {
437         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
438
439         return backend->priv->uri;
440 }
441
442 /**
443  * e_cal_backend_get_kind:
444  * @backend: an #ECalBackend
445  *
446  * Gets the kind of components the given backend stores.
447  *
448  * Returns: The kind of components for this backend.
449  */
450 icalcomponent_kind
451 e_cal_backend_get_kind (ECalBackend *backend)
452 {
453         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), ICAL_NO_COMPONENT);
454
455         return backend->priv->kind;
456 }
457
458 /**
459  * e_cal_backend_is_online:
460  * @backend: an #ECalBackend
461  *
462  * Returns: Whether is backend online.
463  **/
464 gboolean
465 e_cal_backend_is_online (ECalBackend *backend)
466 {
467         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
468
469         return backend->priv->online;
470 }
471
472 /**
473  * e_cal_backend_is_opened:
474  * @backend: an #ECalBackend
475  *
476  * Checks if @backend's storage has been opened (and
477  * authenticated, if necessary) and the backend itself
478  * is ready for accessing. This property is changed automatically
479  * within call of e_cal_backend_notify_opened().
480  *
481  * Returns: %TRUE if fully opened, %FALSE otherwise.
482  **/
483 gboolean
484 e_cal_backend_is_opened (ECalBackend *backend)
485 {
486         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
487
488         return backend->priv->opened;
489 }
490
491 /**
492  * e_cal_backend_is_opening::
493  * @backend: an #ECalBackend
494  *
495  * Checks if @backend is processing its opening phase, which
496  * includes everything since the e_cal_backend_open() call,
497  * through authentication, up to e_cal_backend_notify_opened().
498  * This property is managed automatically and the backend deny
499  * every operation except of cancel and authenticate_user while
500  * it is being opening.
501  *
502  * Returns: %TRUE if opening phase is in the effect, %FALSE otherwise.
503  **/
504 gboolean
505 e_cal_backend_is_opening (ECalBackend *backend)
506 {
507         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
508
509         return backend->priv->opening;
510 }
511
512 /**
513  * e_cal_backend_is_readonly:
514  * @backend: an #ECalBackend
515  *
516  * Returns: Whether is backend read-only. This value is the last used
517  * in a call of e_cal_backend_notify_readonly().
518  **/
519 gboolean
520 e_cal_backend_is_readonly (ECalBackend *backend)
521 {
522         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
523
524         return backend->priv->readonly;
525 }
526
527 /**
528  * e_cal_backend_is_removed:
529  * @backend: an #ECalBackend
530  *
531  * Checks if @backend has been removed from its physical storage.
532  *
533  * Returns: %TRUE if @backend has been removed, %FALSE otherwise.
534  **/
535 gboolean
536 e_cal_backend_is_removed (ECalBackend *backend)
537 {
538         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), FALSE);
539
540         return backend->priv->removed;
541 }
542
543 /**
544  * e_cal_backend_set_is_removed:
545  * @backend: an #ECalBackend
546  * @is_removed: A flag indicating whether the backend's storage was removed
547  *
548  * Sets the flag indicating whether @backend was removed to @is_removed.
549  * Meant to be used by backend implementations.
550  **/
551 void
552 e_cal_backend_set_is_removed (ECalBackend *backend, gboolean is_removed)
553 {
554         g_return_if_fail (E_IS_CAL_BACKEND (backend));
555
556         backend->priv->removed = is_removed;
557 }
558
559 /**
560  * e_cal_backend_get_cache_dir:
561  * @backend: an #ECalBackend
562  *
563  * Returns the cache directory for the given backend.
564  *
565  * Returns: the cache directory for the backend
566  *
567  * Since: 2.32
568  **/
569 const gchar *
570 e_cal_backend_get_cache_dir (ECalBackend *backend)
571 {
572         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
573
574         return backend->priv->cache_dir;
575 }
576
577 /**
578  * e_cal_backend_set_cache_dir:
579  * @backend: an #ECalBackend
580  * @cache_dir: a local cache directory
581  *
582  * Sets the cache directory for the given backend.
583  *
584  * Note that #ECalBackend is initialized with a usable default based on
585  * #ECalBackend:source and #ECalBackend:kind properties.  Backends should
586  * not override the default without good reason.
587  *
588  * Since: 2.32
589  **/
590 void
591 e_cal_backend_set_cache_dir (ECalBackend *backend,
592                              const gchar *cache_dir)
593 {
594         g_return_if_fail (E_IS_CAL_BACKEND (backend));
595         g_return_if_fail (cache_dir != NULL);
596
597         g_free (backend->priv->cache_dir);
598         backend->priv->cache_dir = g_strdup (cache_dir);
599
600         g_object_notify (G_OBJECT (backend), "cache-dir");
601 }
602
603 /**
604  * e_cal_backend_get_backend_property:
605  * @backend: an #ECalBackend
606  * @cal: an #EDataCal
607  * @opid: the ID to use for this operation
608  * @cancellable: a #GCancellable for the operation
609  * @prop_name: property name to get value of; cannot be NULL
610  *
611  * Calls the get_backend_property method on the given backend.
612  * This might be finished with e_data_cal_respond_get_backend_property().
613  * Default implementation takes care of common properties and returns
614  * an 'unsupported' error for any unknown properties. The subclass may
615  * always call this default implementation for properties which fetching
616  * it doesn't overwrite.
617  *
618  * Since: 3.2
619  **/
620 void
621 e_cal_backend_get_backend_property (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *prop_name)
622 {
623         g_return_if_fail (backend != NULL);
624         g_return_if_fail (E_IS_CAL_BACKEND (backend));
625         g_return_if_fail (prop_name != NULL);
626         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->get_backend_property != NULL);
627
628         (* E_CAL_BACKEND_GET_CLASS (backend)->get_backend_property) (backend, cal, opid, cancellable, prop_name);
629 }
630
631 /**
632  * e_cal_backend_set_backend_property:
633  * @backend: an #ECalBackend
634  * @cal: an #EDataCal
635  * @opid: the ID to use for this operation
636  * @cancellable: a #GCancellable for the operation
637  * @prop_name: property name to change; cannot be NULL
638  * @prop_value: value to set to @prop_name; cannot be NULL
639  *
640  * Calls the set_backend_property method on the given backend.
641  * This might be finished with e_data_cal_respond_set_backend_property().
642  * Default implementation simply returns an 'unsupported' error.
643  * The subclass may always call this default implementation for properties
644  * which fetching it doesn't overwrite.
645  *
646  * Since: 3.2
647  **/
648 void
649 e_cal_backend_set_backend_property (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *prop_name, const gchar *prop_value)
650 {
651         g_return_if_fail (backend != NULL);
652         g_return_if_fail (E_IS_CAL_BACKEND (backend));
653         g_return_if_fail (prop_name != NULL);
654         g_return_if_fail (prop_value != NULL);
655         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->set_backend_property != NULL);
656
657         (* E_CAL_BACKEND_GET_CLASS (backend)->set_backend_property) (backend, cal, opid, cancellable, prop_name, prop_value);
658 }
659
660 static void
661 cal_destroy_cb (gpointer data, GObject *where_cal_was)
662 {
663         e_cal_backend_remove_client_private (E_CAL_BACKEND (data),
664                                              (EDataCal *) where_cal_was, FALSE);
665 }
666
667 /**
668  * e_cal_backend_add_client:
669  * @backend: an #ECalBackend
670  * @cal: an #EDataCal
671  *
672  * Adds a new client to the given backend. For any event, the backend will
673  * notify all clients added via this function.
674  */
675 void
676 e_cal_backend_add_client (ECalBackend *backend, EDataCal *cal)
677 {
678         ECalBackendPrivate *priv;
679
680         g_return_if_fail (backend != NULL);
681         g_return_if_fail (E_IS_CAL_BACKEND (backend));
682         g_return_if_fail (cal != NULL);
683         g_return_if_fail (E_IS_DATA_CAL (cal));
684
685         priv = backend->priv;
686
687         g_object_weak_ref (G_OBJECT (cal), cal_destroy_cb, backend);
688
689         g_mutex_lock (priv->clients_mutex);
690         priv->clients = g_slist_append (priv->clients, cal);
691         g_mutex_unlock (priv->clients_mutex);
692 }
693
694 static void
695 e_cal_backend_remove_client_private (ECalBackend *backend, EDataCal *cal, gboolean weak_unref)
696 {
697         ECalBackendPrivate *priv;
698
699         /* XXX this needs a bit more thinking wrt the mutex - we
700            should be holding it when we check to see if clients is
701            NULL */
702         g_return_if_fail (backend != NULL);
703         g_return_if_fail (E_IS_CAL_BACKEND (backend));
704         g_return_if_fail (cal != NULL);
705         g_return_if_fail (E_IS_DATA_CAL (cal));
706
707         priv = backend->priv;
708
709         if (weak_unref)
710                 g_object_weak_unref (G_OBJECT (cal), cal_destroy_cb, backend);
711
712         /* Disconnect */
713         g_mutex_lock (priv->clients_mutex);
714         priv->clients = g_slist_remove (priv->clients, cal);
715         g_mutex_unlock (priv->clients_mutex);
716
717         /* When all clients go away, notify the parent factory about it so that
718          * it may decide whether to kill the backend or not.
719          */
720         if (!priv->clients)
721                 g_signal_emit (backend, signals[LAST_CLIENT_GONE], 0);
722 }
723
724 /**
725  * e_cal_backend_remove_client:
726  * @backend: an #ECalBackend
727  * @cal: an #EDataCal
728  *
729  * Removes a client from the list of connected clients to the given backend.
730  */
731 void
732 e_cal_backend_remove_client (ECalBackend *backend, EDataCal *cal)
733 {
734         e_cal_backend_remove_client_private (backend, cal, TRUE);
735 }
736
737 /**
738  * e_cal_backend_add_view:
739  * @backend: an #ECalBackend
740  * @view: An #EDataCalView object.
741  *
742  * Adds a view to the list of live views being run by the given backend.
743  * Doing so means that any listener on the view will get notified of any
744  * change that affect the live view.
745  */
746 void
747 e_cal_backend_add_view (ECalBackend *backend, EDataCalView *view)
748 {
749         g_return_if_fail (backend != NULL);
750         g_return_if_fail (E_IS_CAL_BACKEND (backend));
751
752         g_mutex_lock (backend->priv->views_mutex);
753
754         backend->priv->views = g_slist_append (backend->priv->views, view);
755
756         g_mutex_unlock (backend->priv->views_mutex);
757 }
758
759 /**
760  * e_cal_backend_remove_view
761  * @backend: an #ECalBackend
762  * @view: An #EDataCalView object, previously added with @ref e_cal_backend_add_view.
763  *
764  * Removes view from the list of live views for the backend.
765  *
766  * Since: 2.24
767  **/
768 void
769 e_cal_backend_remove_view (ECalBackend *backend, EDataCalView *view)
770 {
771         g_return_if_fail (backend != NULL);
772         g_return_if_fail (E_IS_CAL_BACKEND (backend));
773
774         g_mutex_lock (backend->priv->views_mutex);
775
776         backend->priv->views = g_slist_remove (backend->priv->views, view);
777
778         g_mutex_unlock (backend->priv->views_mutex);
779 }
780
781 /**
782  * e_cal_backend_foreach_view:
783  * @backend: an #ECalBackend
784  * @callback: callback to call
785  * @user_data: user_data passed into the @callback
786  *
787  * Calls @callback for each known calendar view of this @backend.
788  * @callback returns %FALSE to stop further processing.
789  **/
790 void
791 e_cal_backend_foreach_view (ECalBackend *backend, gboolean (* callback) (EDataCalView *view, gpointer user_data), gpointer user_data)
792 {
793         const GSList *views;
794         EDataCalView *view;
795         gboolean stop = FALSE;
796
797         g_return_if_fail (backend != NULL);
798         g_return_if_fail (callback != NULL);
799
800         g_mutex_lock (backend->priv->views_mutex);
801
802         for (views = backend->priv->views; views && !stop; views = views->next) {
803                 view = E_DATA_CAL_VIEW (views->data);
804
805                 g_object_ref (view);
806                 stop = !callback (view, user_data);
807                 g_object_unref (view);
808         }
809
810         g_mutex_unlock (backend->priv->views_mutex);
811 }
812
813 /**
814  * e_cal_backend_set_notification_proxy:
815  * @backend: an #ECalBackend
816  * @proxy: The calendar backend to act as notification proxy.
817  *
818  * Sets the backend that will act as notification proxy for the given backend.
819  */
820 void
821 e_cal_backend_set_notification_proxy (ECalBackend *backend, ECalBackend *proxy)
822 {
823         g_return_if_fail (E_IS_CAL_BACKEND (backend));
824
825         backend->priv->notification_proxy = proxy;
826 }
827
828 /**
829  * e_cal_backend_set_online:
830  * @backend: A calendar backend
831  * @is_online: Whether is online
832  *
833  * Sets the online mode of the calendar
834  */
835 void
836 e_cal_backend_set_online (ECalBackend *backend, gboolean is_online)
837 {
838         g_return_if_fail (backend != NULL);
839         g_return_if_fail (E_IS_CAL_BACKEND (backend));
840         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->set_online != NULL);
841
842         (* E_CAL_BACKEND_GET_CLASS (backend)->set_online) (backend, is_online);
843 }
844
845 /**
846  * e_cal_backend_open:
847  * @backend: an #ECalBackend
848  * @cal: an #EDataCal
849  * @opid: the ID to use for this operation
850  * @cancellable: a #GCancellable for the operation
851  * @only_if_exists: Whether the calendar should be opened only if it already
852  * exists.  If FALSE, a new calendar will be created when the specified @uri
853  * does not exist.
854  *
855  * Opens a calendar backend with data from a calendar stored at the specified URI.
856  * This might be finished with e_data_cal_respond_open() or e_cal_backend_respond_opened(),
857  * though the overall opening phase finishes only after call
858  * of e_cal_backend_notify_opened() after which call the backend
859  * is either fully opened (including authentication against (remote)
860  * server/storage) or an error was encountered during this opening phase.
861  * 'opened' and 'opening' properties are updated automatically.
862  * The backend refuses all other operations until the opening phase is finished.
863  *
864  * The e_cal_backend_notify_opened() is called either from this function
865  * or from e_cal_backend_authenticate_user(), or after necessary steps
866  * initiated by these two functions.
867  *
868  * The opening phase usually works like this:
869  * 1) client requests open for the backend
870  * 2) server receives this request and calls e_cal_backend_open() - the opening phase begun
871  * 3) either the backend is opened during this call, and notifies client
872  *    with e_cal_backend_notify_opened() about that. This is usually
873  *    for local backends; their opening phase is finished
874  * 4) or the backend requires authentication, thus it notifies client
875  *    about that with e_cal_backend_notify_auth_required() and is
876  *    waiting for credentials, which will be received from client
877  *    by e_cal_backend_authenticate_user() call. Backend's opening
878  *    phase is still running in this case, thus it doesn't call
879  *    e_cal_backend_notify_opened() within e_cal_backend_open() call.
880  * 5) when backend receives credentials in e_cal_backend_authenticate_user()
881  *    then it tries to authenticate against a server/storage with them
882  *    and only after it knows result of the authentication, whether user
883  *    was or wasn't authenticated, it notifies client with the result
884  *    by e_cal_backend_notify_opened() and it's opening phase is
885  *    finished now. If there was no error returned then the backend is
886  *    considered opened, otherwise it's considered closed. Use AuthenticationFailed
887  *    error when the given credentials were rejected by the server/store, which
888  *    will result in a re-prompt on the client side, otherwise use AuthenticationRequired
889  *    if there was anything wrong with the given credentials. Set error's
890  *    message to a reason for a re-prompt, it'll be shown to a user.
891  * 6) client checks error returned from e_cal_backend_notify_opened() and
892  *    reprompts for a password if it was AuthenticationFailed. Otherwise
893  *    considers backend opened based on the error presence (no error means success).
894  *
895  * In any case, the call of e_cal_backend_open() should be always finished
896  * with e_data_cal_respond_open(), which has no influence on the opening phase,
897  * or alternatively with e_cal_backend_respond_opened(). Never use authentication
898  * errors in e_data_cal_respond_open() to notify the client the authentication is
899  * required, there is e_cal_backend_notify_auth_required() for this.
900  **/
901 void
902 e_cal_backend_open (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, gboolean only_if_exists)
903 {
904         g_return_if_fail (backend != NULL);
905         g_return_if_fail (E_IS_CAL_BACKEND (backend));
906         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->open != NULL);
907
908         g_mutex_lock (backend->priv->clients_mutex);
909
910         if (e_cal_backend_is_opened (backend)) {
911                 g_mutex_unlock (backend->priv->clients_mutex);
912
913                 e_data_cal_report_readonly (cal, backend->priv->readonly);
914                 e_data_cal_report_online (cal, backend->priv->online);
915
916                 e_cal_backend_respond_opened (backend, cal, opid, NULL);
917         } else if (e_cal_backend_is_opening (backend)) {
918                 g_mutex_unlock (backend->priv->clients_mutex);
919
920                 e_data_cal_respond_open (cal, opid, EDC_OPENING_ERROR);
921         } else {
922                 backend->priv->opening = TRUE;
923                 g_mutex_unlock (backend->priv->clients_mutex);
924
925                 (* E_CAL_BACKEND_GET_CLASS (backend)->open) (backend, cal, opid, cancellable, only_if_exists);
926         }
927 }
928
929 /**
930  * e_cal_backend_authenticate_user:
931  * @backend: an #ECalBackend
932  * @cancellable: a #GCancellable for the operation
933  * @credentials: #ECredentials to use for authentication
934  *
935  * Notifies @backend about @credentials provided by user to use
936  * for authentication. This notification is usually called during
937  * opening phase as a response to e_cal_backend_notify_auth_required()
938  * on the client side and it results in setting property 'opening' to %TRUE
939  * unless the backend is already opened. This function finishes opening
940  * phase, thus it should be finished with e_cal_backend_notify_opened().
941  *
942  * See information at e_cal_backend_open() for more details
943  * how the opening phase works.
944  **/
945 void
946 e_cal_backend_authenticate_user (ECalBackend  *backend,
947                                  GCancellable *cancellable,
948                                  ECredentials *credentials)
949 {
950         g_return_if_fail (E_IS_CAL_BACKEND (backend));
951         g_return_if_fail (credentials != NULL);
952         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->authenticate_user);
953
954         if (!e_cal_backend_is_opened (backend))
955                 backend->priv->opening = TRUE;
956
957         (* E_CAL_BACKEND_GET_CLASS (backend)->authenticate_user) (backend, cancellable, credentials);
958 }
959
960 /**
961  * e_cal_backend_remove:
962  * @backend: an #ECalBackend
963  * @cal: an #EDataCal
964  * @opid: the ID to use for this operation
965  * @cancellable: a #GCancellable for the operation
966  *
967  * Removes the calendar being accessed by the given backend.
968  * This might be finished with e_data_cal_respond_remove().
969  **/
970 void
971 e_cal_backend_remove (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable)
972 {
973         g_return_if_fail (backend != NULL);
974         g_return_if_fail (E_IS_CAL_BACKEND (backend));
975         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->remove != NULL);
976
977         if (e_cal_backend_is_opening (backend))
978                 e_data_cal_respond_remove (cal, opid, EDC_OPENING_ERROR);
979         else
980                 (* E_CAL_BACKEND_GET_CLASS (backend)->remove) (backend, cal, opid, cancellable);
981 }
982
983 /**
984  * e_cal_backend_refresh:
985  * @backend: an #ECalBackend
986  * @cal: an #EDataCal
987  * @opid: the ID to use for this operation
988  * @cancellable: a #GCancellable for the operation
989  *
990  * Refreshes the calendar being accessed by the given backend.
991  * This might be finished with e_data_cal_respond_refresh(),
992  * and it might be called as soon as possible; it doesn't mean
993  * that the refreshing is done after calling that, the backend
994  * is only notifying client whether it started the refresh process
995  * or not.
996  *
997  * Since: 2.30
998  **/
999 void
1000 e_cal_backend_refresh (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable)
1001 {
1002         g_return_if_fail (backend != NULL);
1003         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1004
1005         if (e_cal_backend_is_opening (backend))
1006                 e_data_cal_respond_refresh (cal, opid, EDC_OPENING_ERROR);
1007         else if (!E_CAL_BACKEND_GET_CLASS (backend)->refresh)
1008                 e_data_cal_respond_refresh (cal, opid, EDC_ERROR (UnsupportedMethod));
1009         else if (!e_cal_backend_is_opened (backend))
1010                 e_data_cal_respond_refresh (cal, opid, EDC_NOT_OPENED_ERROR);
1011         else
1012                 (* E_CAL_BACKEND_GET_CLASS (backend)->refresh) (backend, cal, opid, cancellable);
1013 }
1014
1015 /**
1016  * e_cal_backend_get_object:
1017  * @backend: an #ECalBackend
1018  * @cal: an #EDataCal
1019  * @opid: the ID to use for this operation
1020  * @cancellable: a #GCancellable for the operation
1021  * @uid: Unique identifier for a calendar object.
1022  * @rid: ID for the object's recurrence to get.
1023  *
1024  * Queries a calendar backend for a calendar object based on its unique
1025  * identifier and its recurrence ID (if a recurrent appointment).
1026  * This might be finished with e_data_cal_respond_get_object().
1027  **/
1028 void
1029 e_cal_backend_get_object (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *uid, const gchar *rid)
1030 {
1031         g_return_if_fail (backend != NULL);
1032         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1033         g_return_if_fail (uid != NULL);
1034         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->get_object != NULL);
1035
1036         if (e_cal_backend_is_opening (backend))
1037                 e_data_cal_respond_get_object (cal, opid, EDC_OPENING_ERROR, NULL);
1038         else if (!e_cal_backend_is_opened (backend))
1039                 e_data_cal_respond_get_object (cal, opid, EDC_NOT_OPENED_ERROR, NULL);
1040         else
1041                 (* E_CAL_BACKEND_GET_CLASS (backend)->get_object) (backend, cal, opid, cancellable, uid, rid);
1042 }
1043
1044 /**
1045  * e_cal_backend_get_object_list:
1046  * @backend: an #ECalBackend
1047  * @cal: an #EDataCal
1048  * @opid: the ID to use for this operation
1049  * @cancellable: a #GCancellable for the operation
1050  * @sexp: Expression to search for.
1051  *
1052  * Calls the get_object_list method on the given backend.
1053  * This might be finished with e_data_cal_respond_get_object_list().
1054  **/
1055 void
1056 e_cal_backend_get_object_list (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *sexp)
1057 {
1058         g_return_if_fail (backend != NULL);
1059         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1060         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->get_object_list != NULL);
1061
1062         if (e_cal_backend_is_opening (backend))
1063                 e_data_cal_respond_get_object_list (cal, opid, EDC_OPENING_ERROR, NULL);
1064         else if (!e_cal_backend_is_opened (backend))
1065                 e_data_cal_respond_get_object_list (cal, opid, EDC_NOT_OPENED_ERROR, NULL);
1066         else
1067                 (* E_CAL_BACKEND_GET_CLASS (backend)->get_object_list) (backend, cal, opid, cancellable, sexp);
1068 }
1069
1070 /**
1071  * e_cal_backend_get_free_busy:
1072  * @backend: an #ECalBackend
1073  * @cal: an #EDataCal
1074  * @opid: the ID to use for this operation
1075  * @cancellable: a #GCancellable for the operation
1076  * @users: List of users to get free/busy information for.
1077  * @start: Start time for query.
1078  * @end: End time for query.
1079  *
1080  * Gets a free/busy object for the given time interval. Client side is
1081  * notified about free/busy objects throug e_data_cal_report_free_busy_data().
1082  * This might be finished with e_data_cal_respond_get_free_busy().
1083  **/
1084 void
1085 e_cal_backend_get_free_busy (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const GSList *users, time_t start, time_t end)
1086 {
1087         g_return_if_fail (backend != NULL);
1088         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1089         g_return_if_fail (start != -1 && end != -1);
1090         g_return_if_fail (start <= end);
1091         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->get_free_busy != NULL);
1092
1093         if (e_cal_backend_is_opening (backend))
1094                 e_data_cal_respond_get_free_busy (cal, opid, EDC_OPENING_ERROR);
1095         else if (!e_cal_backend_is_opened (backend))
1096                 e_data_cal_respond_get_free_busy (cal, opid, EDC_NOT_OPENED_ERROR);
1097         else
1098                 (* E_CAL_BACKEND_GET_CLASS (backend)->get_free_busy) (backend, cal, opid, cancellable, users, start, end);
1099 }
1100
1101 /**
1102  * e_cal_backend_create_object:
1103  * @backend: an #ECalBackend
1104  * @cal: an #EDataCal
1105  * @opid: the ID to use for this operation
1106  * @cancellable: a #GCancellable for the operation
1107  * @calobj: The object to create.
1108  *
1109  * Calls the create_object method on the given backend.
1110  * This might be finished with e_data_cal_respond_create_object().
1111  **/
1112 void
1113 e_cal_backend_create_object (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj)
1114 {
1115         g_return_if_fail (backend != NULL);
1116         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1117         g_return_if_fail (calobj != NULL);
1118
1119         if (e_cal_backend_is_opening (backend))
1120                 e_data_cal_respond_create_object (cal, opid, EDC_OPENING_ERROR, NULL, NULL);
1121         else if (!E_CAL_BACKEND_GET_CLASS (backend)->create_object)
1122                 e_data_cal_respond_create_object (cal, opid, EDC_ERROR (UnsupportedMethod), NULL, NULL);
1123         else if (!e_cal_backend_is_opened (backend))
1124                 e_data_cal_respond_create_object (cal, opid, EDC_NOT_OPENED_ERROR, NULL, NULL);
1125         else
1126                 (* E_CAL_BACKEND_GET_CLASS (backend)->create_object) (backend, cal, opid, cancellable, calobj);
1127 }
1128
1129 /**
1130  * e_cal_backend_modify_object:
1131  * @backend: an #ECalBackend
1132  * @cal: an #EDataCal
1133  * @opid: the ID to use for this operation
1134  * @cancellable: a #GCancellable for the operation
1135  * @calobj: Object to be modified.
1136  * @mod: Type of modification.
1137  *
1138  * Calls the modify_object method on the given backend.
1139  * This might be finished with e_data_cal_respond_modify_object().
1140  **/
1141 void
1142 e_cal_backend_modify_object (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj, CalObjModType mod)
1143 {
1144         g_return_if_fail (backend != NULL);
1145         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1146         g_return_if_fail (calobj != NULL);
1147
1148         if (e_cal_backend_is_opening (backend))
1149                 e_data_cal_respond_modify_object (cal, opid, EDC_OPENING_ERROR, NULL, NULL);
1150         else if (!E_CAL_BACKEND_GET_CLASS (backend)->modify_object)
1151                 e_data_cal_respond_modify_object (cal, opid, EDC_ERROR (UnsupportedMethod), NULL, NULL);
1152         else if (!e_cal_backend_is_opened (backend))
1153                 e_data_cal_respond_modify_object (cal, opid, EDC_NOT_OPENED_ERROR, NULL, NULL);
1154         else
1155                 (* E_CAL_BACKEND_GET_CLASS (backend)->modify_object) (backend, cal, opid, cancellable, calobj, mod);
1156 }
1157
1158 /**
1159  * e_cal_backend_remove_object:
1160  * @backend: an #ECalBackend
1161  * @cal: an #EDataCal
1162  * @opid: the ID to use for this operation
1163  * @cancellable: a #GCancellable for the operation
1164  * @uid: Unique identifier of the object to remove.
1165  * @rid: A recurrence ID.
1166  * @mod: Type of removal.
1167  *
1168  * Removes an object in a calendar backend.  The backend will notify all of its
1169  * clients about the change.
1170  * This might be finished with e_data_cal_respond_remove_object().
1171  **/
1172 void
1173 e_cal_backend_remove_object (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *uid, const gchar *rid, CalObjModType mod)
1174 {
1175         g_return_if_fail (backend != NULL);
1176         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1177         g_return_if_fail (uid != NULL);
1178         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->remove_object != NULL);
1179
1180         if (e_cal_backend_is_opening (backend))
1181                 e_data_cal_respond_remove_object (cal, opid, EDC_OPENING_ERROR, NULL, NULL, NULL);
1182         else if (!e_cal_backend_is_opened (backend))
1183                 e_data_cal_respond_remove_object (cal, opid, EDC_NOT_OPENED_ERROR, NULL, NULL, NULL);
1184         else
1185                 (* E_CAL_BACKEND_GET_CLASS (backend)->remove_object) (backend, cal, opid, cancellable, uid, rid, mod);
1186 }
1187
1188 /**
1189  * e_cal_backend_receive_objects:
1190  * @backend: an #ECalBackend
1191  * @cal: an #EDataCal
1192  * @opid: the ID to use for this operation
1193  * @cancellable: a #GCancellable for the operation
1194  * @calobj: iCalendar object.
1195  *
1196  * Calls the receive_objects method on the given backend.
1197  * This might be finished with e_data_cal_respond_receive_objects().
1198  **/
1199 void
1200 e_cal_backend_receive_objects (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj)
1201 {
1202         g_return_if_fail (backend != NULL);
1203         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1204         g_return_if_fail (calobj != NULL);
1205         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->receive_objects != NULL);
1206
1207         if (e_cal_backend_is_opening (backend))
1208                 e_data_cal_respond_receive_objects (cal, opid, EDC_OPENING_ERROR);
1209         else if (!e_cal_backend_is_opened (backend))
1210                 e_data_cal_respond_receive_objects (cal, opid, EDC_NOT_OPENED_ERROR);
1211         else
1212                 (* E_CAL_BACKEND_GET_CLASS (backend)->receive_objects) (backend, cal, opid, cancellable, calobj);
1213 }
1214
1215 /**
1216  * e_cal_backend_send_objects:
1217  * @backend: an #ECalBackend
1218  * @cal: an #EDataCal
1219  * @opid: the ID to use for this operation
1220  * @cancellable: a #GCancellable for the operation
1221  * @calobj: iCalendar object to be sent.
1222  *
1223  * Calls the send_objects method on the given backend.
1224  * This might be finished with e_data_cal_respond_send_objects().
1225  **/
1226 void
1227 e_cal_backend_send_objects (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj)
1228 {
1229         g_return_if_fail (backend != NULL);
1230         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1231         g_return_if_fail (calobj != NULL);
1232         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->send_objects != NULL);
1233
1234         if (e_cal_backend_is_opening (backend))
1235                 e_data_cal_respond_send_objects (cal, opid, EDC_OPENING_ERROR, NULL, NULL);
1236         else if (!e_cal_backend_is_opened (backend))
1237                 e_data_cal_respond_send_objects (cal, opid, EDC_NOT_OPENED_ERROR, NULL, NULL);
1238         else
1239                 (* E_CAL_BACKEND_GET_CLASS (backend)->send_objects) (backend, cal, opid, cancellable, calobj);
1240 }
1241
1242 /**
1243  * e_cal_backend_get_attachment_uris:
1244  * @backend: an #ECalBackend
1245  * @cal: an #EDataCal
1246  * @opid: the ID to use for this operation
1247  * @cancellable: a #GCancellable for the operation
1248  * @uid: Unique identifier for a calendar object.
1249  * @rid: ID for the object's recurrence to get.
1250  *
1251  * Queries a calendar backend for attachments present in a calendar object based
1252  * on its unique identifier and its recurrence ID (if a recurrent appointment).
1253  * This might be finished with e_data_cal_respond_get_attachment_uris().
1254  **/
1255 void
1256 e_cal_backend_get_attachment_uris (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *uid, const gchar *rid)
1257 {
1258         g_return_if_fail (backend != NULL);
1259         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1260         g_return_if_fail (uid != NULL);
1261         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->get_attachment_uris != NULL);
1262
1263         if (e_cal_backend_is_opening (backend))
1264                 e_data_cal_respond_get_attachment_uris (cal, opid, EDC_OPENING_ERROR, NULL);
1265         else if (!e_cal_backend_is_opened (backend))
1266                 e_data_cal_respond_get_attachment_uris (cal, opid, EDC_NOT_OPENED_ERROR, NULL);
1267         else
1268                 (* E_CAL_BACKEND_GET_CLASS (backend)->get_attachment_uris) (backend, cal, opid, cancellable, uid, rid);
1269 }
1270
1271 /**
1272  * e_cal_backend_discard_alarm:
1273  * @backend: an #ECalBackend
1274  * @cal: an #EDataCal
1275  * @opid: the ID to use for this operation
1276  * @cancellable: a #GCancellable for the operation
1277  * @uid: Unique identifier for a calendar object.
1278  * @rid: ID for the object's recurrence to discard alarm in.
1279  * @auid: Unique identifier of the alarm itself.
1280  *
1281  * Discards alarm @auid from the object identified by @uid and @rid.
1282  * This might be finished with e_data_cal_respond_discard_alarm().
1283  * Default implementation of this method returns Not Supported error.
1284  **/
1285 void
1286 e_cal_backend_discard_alarm (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *uid, const gchar *rid, const gchar *auid)
1287 {
1288         g_return_if_fail (backend != NULL);
1289         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1290         g_return_if_fail (uid != NULL);
1291         g_return_if_fail (auid != NULL);
1292
1293         if (e_cal_backend_is_opening (backend))
1294                 e_data_cal_respond_discard_alarm (cal, opid, EDC_OPENING_ERROR);
1295         else if (!E_CAL_BACKEND_GET_CLASS (backend)->discard_alarm)
1296                 e_data_cal_respond_discard_alarm (cal, opid, e_data_cal_create_error (NotSupported, NULL));
1297         else if (!e_cal_backend_is_opened (backend))
1298                 e_data_cal_respond_discard_alarm (cal, opid, EDC_NOT_OPENED_ERROR);
1299         else
1300                 (* E_CAL_BACKEND_GET_CLASS (backend)->discard_alarm) (backend, cal, opid, cancellable, uid, rid, auid);
1301 }
1302
1303 /**
1304  * e_cal_backend_get_timezone:
1305  * @backend: an #ECalBackend
1306  * @cal: an #EDataCal
1307  * @opid: the ID to use for this operation
1308  * @cancellable: a #GCancellable for the operation
1309  * @tzid: Unique identifier of a VTIMEZONE object. Note that this must not be
1310  * NULL.
1311  *
1312  * Returns the icaltimezone* corresponding to the TZID, or NULL if the TZID
1313  * can't be found.
1314  * This might be finished with e_data_cal_respond_get_timezone().
1315  **/
1316 void
1317 e_cal_backend_get_timezone (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *tzid)
1318 {
1319         g_return_if_fail (backend != NULL);
1320         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1321         g_return_if_fail (tzid != NULL);
1322         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->get_timezone != NULL);
1323
1324         if (e_cal_backend_is_opening (backend))
1325                 e_data_cal_respond_get_timezone (cal, opid, EDC_OPENING_ERROR, NULL);
1326         else if (!e_cal_backend_is_opened (backend))
1327                 e_data_cal_respond_get_timezone (cal, opid, EDC_NOT_OPENED_ERROR, NULL);
1328         else
1329                 (* E_CAL_BACKEND_GET_CLASS (backend)->get_timezone) (backend, cal, opid, cancellable, tzid);
1330 }
1331
1332 /**
1333  * e_cal_backend_add_timezone
1334  * @backend: an #ECalBackend
1335  * @cal: an #EDataCal
1336  * @opid: the ID to use for this operation
1337  * @cancellable: a #GCancellable for the operation
1338  * @tzobj: The timezone object, in a string.
1339  *
1340  * Add a timezone object to the given backend.
1341  * This might be finished with e_data_cal_respond_add_timezone().
1342  **/
1343 void
1344 e_cal_backend_add_timezone (ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *tzobject)
1345 {
1346         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1347         g_return_if_fail (tzobject != NULL);
1348         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->add_timezone != NULL);
1349
1350         if (e_cal_backend_is_opening (backend))
1351                 e_data_cal_respond_add_timezone (cal, opid, EDC_OPENING_ERROR);
1352         else if (!e_cal_backend_is_opened (backend))
1353                 e_data_cal_respond_add_timezone (cal, opid, EDC_NOT_OPENED_ERROR);
1354         else
1355                 (* E_CAL_BACKEND_GET_CLASS (backend)->add_timezone) (backend, cal, opid, cancellable, tzobject);
1356 }
1357
1358 /**
1359  * e_cal_backend_internal_get_timezone:
1360  * @backend: an #ECalBackend
1361  * @tzid: ID of the timezone to get.
1362  *
1363  * Calls the internal_get_timezone method on the given backend.
1364  */
1365 icaltimezone *
1366 e_cal_backend_internal_get_timezone (ECalBackend *backend, const gchar *tzid)
1367 {
1368         g_return_val_if_fail (E_IS_CAL_BACKEND (backend), NULL);
1369         g_return_val_if_fail (tzid != NULL, NULL);
1370         g_return_val_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->internal_get_timezone != NULL, NULL);
1371
1372         return (* E_CAL_BACKEND_GET_CLASS (backend)->internal_get_timezone) (backend, tzid);
1373 }
1374
1375 /**
1376  * e_cal_backend_start_view:
1377  * @backend: an #ECalBackend
1378  * @view: The view to be started.
1379  *
1380  * Starts a new live view on the given backend.
1381  */
1382 void
1383 e_cal_backend_start_view (ECalBackend *backend, EDataCalView *view)
1384 {
1385         g_return_if_fail (backend != NULL);
1386         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1387         g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->start_view != NULL);
1388
1389         (* E_CAL_BACKEND_GET_CLASS (backend)->start_view) (backend, view);
1390 }
1391
1392 /**
1393  * e_cal_backend_stop_view:
1394  * @backend: an #ECalBackend
1395  * @view: The view to be stopped.
1396  *
1397  * Stops a previously started live view on the given backend.
1398  *
1399  * Since: 3.2
1400  */
1401 void
1402 e_cal_backend_stop_view (ECalBackend *backend, EDataCalView *view)
1403 {
1404         g_return_if_fail (backend != NULL);
1405         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1406
1407         /* backward compatibility, do not force each backend define this function */
1408         if (!E_CAL_BACKEND_GET_CLASS (backend)->stop_view)
1409                 return;
1410
1411         (* E_CAL_BACKEND_GET_CLASS (backend)->stop_view) (backend, view);
1412 }
1413
1414 static gboolean
1415 object_created_cb (EDataCalView *view, gpointer calobj)
1416 {
1417         if (e_data_cal_view_object_matches (view, calobj))
1418                 e_data_cal_view_notify_objects_added_1 (view, calobj);
1419
1420         return TRUE;
1421 }
1422
1423 /**
1424  * e_cal_backend_notify_object_created:
1425  * @backend: an #ECalBackend
1426  * @calobj: iCalendar representation of new object
1427  *
1428  * Notifies each of the backend's listeners about a new object.
1429  *
1430  * #e_data_cal_notify_object_created() calls this for you. You only need to
1431  * call e_cal_backend_notify_object_created() yourself to report objects
1432  * created by non-EDS clients.
1433  **/
1434 void
1435 e_cal_backend_notify_object_created (ECalBackend *backend, const gchar *calobj)
1436 {
1437         ECalBackendPrivate *priv;
1438
1439         priv = backend->priv;
1440
1441         if (priv->notification_proxy) {
1442                 e_cal_backend_notify_object_created (priv->notification_proxy, calobj);
1443                 return;
1444         }
1445
1446         e_cal_backend_foreach_view (backend, object_created_cb, (gpointer) calobj);
1447 }
1448
1449 /**
1450  * e_cal_backend_notify_objects_added:
1451  *
1452  * Since: 2.24
1453  **/
1454 void
1455 e_cal_backend_notify_objects_added (ECalBackend *backend, EDataCalView *view, const GSList *objects)
1456 {
1457         e_data_cal_view_notify_objects_added (view, objects);
1458 }
1459
1460 static void
1461 match_view_and_notify (EDataCalView *view, const gchar *old_object, const gchar *object)
1462 {
1463         gboolean old_match = FALSE, new_match = FALSE;
1464
1465         if (old_object)
1466                 old_match = e_data_cal_view_object_matches (view, old_object);
1467
1468         new_match = e_data_cal_view_object_matches (view, object);
1469         if (old_match && new_match)
1470                 e_data_cal_view_notify_objects_modified_1 (view, object);
1471         else if (new_match)
1472                 e_data_cal_view_notify_objects_added_1 (view, object);
1473         else if (old_match) {
1474                 ECalComponent *comp = NULL;
1475
1476                 comp = e_cal_component_new_from_string (old_object);
1477                 if (comp) {
1478                         ECalComponentId *id = e_cal_component_get_id (comp);
1479
1480                         e_data_cal_view_notify_objects_removed_1 (view, id);
1481
1482                         e_cal_component_free_id (id);
1483                         g_object_unref (comp);
1484                 }
1485         }
1486 }
1487
1488 struct call_data {
1489         const gchar *old_object;
1490         const gchar *object;
1491         const ECalComponentId *id;
1492 };
1493
1494 static gboolean
1495 call_match_and_notify (EDataCalView *view, gpointer user_data)
1496 {
1497         struct call_data *cd = user_data;
1498
1499         g_return_val_if_fail (user_data != NULL, FALSE);
1500
1501         match_view_and_notify (view, cd->old_object, cd->object);
1502
1503         return TRUE;
1504 }
1505
1506 /**
1507  * e_cal_backend_notify_object_modified:
1508  * @backend: an #ECalBackend
1509  * @old_object: iCalendar representation of the original form of the object
1510  * @object: iCalendar representation of the new form of the object
1511  *
1512  * Notifies each of the backend's listeners about a modified object.
1513  *
1514  * #e_data_cal_notify_object_modified() calls this for you. You only need to
1515  * call e_cal_backend_notify_object_modified() yourself to report objects
1516  * modified by non-EDS clients.
1517  **/
1518 void
1519 e_cal_backend_notify_object_modified (ECalBackend *backend, const gchar *old_object, const gchar *object)
1520 {
1521         ECalBackendPrivate *priv;
1522         struct call_data cd;
1523
1524         priv = backend->priv;
1525
1526         if (priv->notification_proxy) {
1527                 e_cal_backend_notify_object_modified (priv->notification_proxy, old_object, object);
1528                 return;
1529         }
1530
1531         cd.old_object = old_object;
1532         cd.object = object;
1533         cd.id = NULL;
1534
1535         e_cal_backend_foreach_view (backend, call_match_and_notify, &cd);
1536 }
1537
1538 /**
1539  * e_cal_backend_notify_objects_modified:
1540  *
1541  * Since: 2.24
1542  **/
1543 void
1544 e_cal_backend_notify_objects_modified (ECalBackend *backend, EDataCalView *view, const GSList *objects)
1545 {
1546         e_data_cal_view_notify_objects_modified (view, objects);
1547 }
1548
1549 static gboolean
1550 object_removed_cb (EDataCalView *view, gpointer user_data)
1551 {
1552         struct call_data *cd = user_data;
1553
1554         g_return_val_if_fail (user_data != NULL, FALSE);
1555
1556         if (cd->object == NULL) {
1557                 /* if object == NULL, it means the object has been completely
1558                    removed from the backend */
1559                 if (!cd->old_object || e_data_cal_view_object_matches (view, cd->old_object))
1560                         e_data_cal_view_notify_objects_removed_1 (view, cd->id);
1561         } else
1562                 match_view_and_notify (view, cd->old_object, cd->object);
1563
1564         return TRUE;
1565 }
1566
1567 /**
1568  * e_cal_backend_notify_object_removed:
1569  * @backend: an #ECalBackend
1570  * @id: the Id of the removed object
1571  * @old_object: iCalendar representation of the removed object
1572  * @new_object: iCalendar representation of the object after the removal. This
1573  * only applies to recurrent appointments that had an instance removed. In that
1574  * case, this function notifies a modification instead of a removal.
1575  *
1576  * Notifies each of the backend's listeners about a removed object.
1577  *
1578  * e_data_cal_notify_object_removed() calls this for you. You only need to
1579  * call e_cal_backend_notify_object_removed() yourself to report objects
1580  * removed by non-EDS clients.
1581  **/
1582 void
1583 e_cal_backend_notify_object_removed (ECalBackend *backend, const ECalComponentId *id,
1584                                      const gchar *old_object, const gchar *object)
1585 {
1586         ECalBackendPrivate *priv;
1587         struct call_data cd;
1588
1589         priv = backend->priv;
1590
1591         if (priv->notification_proxy) {
1592                 e_cal_backend_notify_object_removed (priv->notification_proxy, id, old_object, object);
1593                 return;
1594         }
1595
1596         cd.old_object = old_object;
1597         cd.object = object;
1598         cd.id = id;
1599
1600         e_cal_backend_foreach_view (backend, object_removed_cb, &cd);
1601 }
1602
1603 /**
1604  * e_cal_backend_notify_objects_removed:
1605  *
1606  * Since: 2.24
1607  **/
1608 void
1609 e_cal_backend_notify_objects_removed (ECalBackend *backend, EDataCalView *view, const GSList *ids)
1610 {
1611         e_data_cal_view_notify_objects_removed (view, ids);
1612 }
1613
1614 /**
1615  * e_cal_backend_notify_error:
1616  * @backend: an #ECalBackend
1617  * @message: Error message
1618  *
1619  * Notifies each of the backend's listeners about an error
1620  **/
1621 void
1622 e_cal_backend_notify_error (ECalBackend *backend, const gchar *message)
1623 {
1624         ECalBackendPrivate *priv = backend->priv;
1625         GSList *l;
1626
1627         if (priv->notification_proxy) {
1628                 e_cal_backend_notify_error (priv->notification_proxy, message);
1629                 return;
1630         }
1631
1632         g_mutex_lock (priv->clients_mutex);
1633
1634         for (l = priv->clients; l; l = l->next)
1635                 e_data_cal_report_error (l->data, message);
1636
1637         g_mutex_unlock (priv->clients_mutex);
1638 }
1639
1640 /**
1641  * e_cal_backend_notify_readonly:
1642  * @backend: an #ECalBackend
1643  * @is_readonly: flag indicating readonly status
1644  *
1645  * Notifies all backend's clients about the current readonly state.
1646  * Meant to be used by backend implementations.
1647  **/
1648 void
1649 e_cal_backend_notify_readonly (ECalBackend *backend, gboolean is_readonly)
1650 {
1651         ECalBackendPrivate *priv;
1652         GSList *l;
1653
1654         priv = backend->priv;
1655         priv->readonly = is_readonly;
1656
1657         if (priv->notification_proxy) {
1658                 e_cal_backend_notify_readonly (priv->notification_proxy, is_readonly);
1659                 return;
1660         }
1661
1662         g_mutex_lock (priv->clients_mutex);
1663
1664         for (l = priv->clients; l; l = l->next)
1665                 e_data_cal_report_readonly (l->data, is_readonly);
1666
1667         g_mutex_unlock (priv->clients_mutex);
1668 }
1669
1670 /**
1671  * e_cal_backend_notify_online:
1672  * @backend: an #ECalBackend
1673  * @is_online: flag indicating whether @backend is connected and online
1674  *
1675  * Notifies clients of @backend's connection status indicated by @is_online.
1676  * Meant to be used by backend implementations.
1677  **/
1678 void
1679 e_cal_backend_notify_online (ECalBackend *backend, gboolean is_online)
1680 {
1681         ECalBackendPrivate *priv;
1682         GSList *clients;
1683
1684         priv = backend->priv;
1685         priv->online = is_online;
1686
1687         if (priv->notification_proxy) {
1688                 e_cal_backend_notify_online (priv->notification_proxy, is_online);
1689                 return;
1690         }
1691
1692         g_mutex_lock (priv->clients_mutex);
1693
1694         for (clients = priv->clients; clients != NULL; clients = g_slist_next (clients))
1695                 e_data_cal_report_online (E_DATA_CAL (clients->data), is_online);
1696
1697         g_mutex_unlock (priv->clients_mutex);
1698 }
1699
1700 /**
1701  * e_cal_backend_notify_auth_required:
1702  * @backend: an #ECalBackend
1703  * @is_self: Use %TRUE to indicate the authentication is required
1704  *    for the @backend, otheriwse the authentication is for any
1705  *    other source. Having @credentials %NULL means @is_self
1706  *    automatically.
1707  * @credentials: an #ECredentials that contains extra information for
1708  *    a source for which authentication is requested.
1709  *    This parameter can be NULL to indicate "for this calendar".
1710  *
1711  * Notifies clients that @backend requires authentication in order to
1712  * connect. This function call does not influence 'opening', but 
1713  * influences 'opened' property, which is set to %FALSE when @is_self
1714  * is %TRUE or @credentials is %NULL. Opening phase is finished
1715  * by e_cal_backend_notify_opened() if this is requested for @backend.
1716  *
1717  * See e_cal_backend_open() for a description how the whole opening
1718  * phase works.
1719  *
1720  * Meant to be used by backend implementations.
1721  **/
1722 void
1723 e_cal_backend_notify_auth_required (ECalBackend *backend, gboolean is_self, const ECredentials *credentials)
1724 {
1725         ECalBackendPrivate *priv;
1726         GSList *clients;
1727
1728         priv = backend->priv;
1729
1730         if (priv->notification_proxy) {
1731                 e_cal_backend_notify_auth_required (priv->notification_proxy, is_self, credentials);
1732                 return;
1733         }
1734
1735         g_mutex_lock (priv->clients_mutex);
1736
1737         if (is_self || !credentials)
1738                 priv->opened = FALSE;
1739
1740         for (clients = priv->clients; clients != NULL; clients = g_slist_next (clients))
1741                 e_data_cal_report_auth_required (E_DATA_CAL (clients->data), credentials);
1742
1743         g_mutex_unlock (priv->clients_mutex);
1744 }
1745
1746 /**
1747  * e_cal_backend_notify_opened:
1748  * @backend: an #ECalBackend
1749  * @error: a #GError corresponding to the error encountered during
1750  *    the opening phase. Use %NULL for success. The @error is freed
1751  *    automatically if not %NULL.
1752  *
1753  * Notifies clients that @backend finished its opening phase.
1754  * See e_cal_backend_open() for more information how the opening
1755  * phase works. Calling this function changes 'opening' property,
1756  * same as 'opened'. 'opening' is set to %FALSE and the backend
1757  * is considered 'opened' only if the @error is %NULL.
1758  *
1759  * See also: e_cal_backend_respond_opened()
1760  *
1761  * Note: The @error is freed automatically if not %NULL.
1762  *
1763  * Meant to be used by backend implementations.
1764  **/
1765 void
1766 e_cal_backend_notify_opened (ECalBackend *backend, GError *error)
1767 {
1768         ECalBackendPrivate *priv;
1769         GSList *clients;
1770
1771         priv = backend->priv;
1772         g_mutex_lock (priv->clients_mutex);
1773
1774         priv->opening = FALSE;
1775         priv->opened = error == NULL;
1776
1777         for (clients = priv->clients; clients != NULL; clients = g_slist_next (clients))
1778                 e_data_cal_report_opened (E_DATA_CAL (clients->data), error);
1779
1780         g_mutex_unlock (priv->clients_mutex);
1781
1782         if (error)
1783                 g_error_free (error);
1784 }
1785
1786 /**
1787  * e_cal_backend_respond_opened:
1788  * @backend: an #ECalBackend
1789  * @cal: an #EDataCal
1790  * @opid: an operation ID
1791  * @error: result error; can be %NULL, if it isn't then it's automatically freed
1792  *
1793  * This is a replacement for e_data_cal_respond_open() for cases where
1794  * the finish of 'open' method call also finishes backend opening phase.
1795  * This function covers calling of both e_cal_backend_notify_opened() and
1796  * e_data_cal_respond_open() with the same @error.
1797  *
1798  * See e_cal_backend_open() for more details how the opening phase works.
1799  **/
1800 void
1801 e_cal_backend_respond_opened (ECalBackend *backend, EDataCal *cal, guint32 opid, GError *error)
1802 {
1803         GError *copy = NULL;
1804
1805         g_return_if_fail (backend != NULL);
1806         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1807         g_return_if_fail (cal != NULL);
1808         g_return_if_fail (opid != 0);
1809
1810         if (error)
1811                 copy = g_error_copy (error);
1812
1813         e_cal_backend_notify_opened (backend, copy);
1814         e_data_cal_respond_open (cal, opid, error);
1815 }
1816
1817 /**
1818  * e_cal_backend_empty_cache:
1819  * @backend: an #ECalBackend
1820  * @cache: Backend's cache to empty.
1821  *
1822  * Empties backend's cache with all notifications and so on, thus all listening
1823  * will know there is nothing in this backend.
1824  *
1825  * Since: 2.28
1826  **/
1827 void
1828 e_cal_backend_empty_cache (ECalBackend *backend, ECalBackendCache *cache)
1829 {
1830         GList *comps_in_cache;
1831
1832         g_return_if_fail (backend != NULL);
1833         g_return_if_fail (E_IS_CAL_BACKEND (backend));
1834
1835         if (!cache)
1836                 return;
1837
1838         g_return_if_fail (E_IS_CAL_BACKEND_CACHE (cache));
1839
1840         e_file_cache_freeze_changes (E_FILE_CACHE (cache));
1841
1842         for (comps_in_cache = e_cal_backend_cache_get_components (cache);
1843              comps_in_cache;
1844              comps_in_cache = comps_in_cache->next) {
1845                 gchar *comp_str;
1846                 ECalComponentId *id;
1847                 ECalComponent *comp = comps_in_cache->data;
1848
1849                 id = e_cal_component_get_id (comp);
1850                 comp_str = e_cal_component_get_as_string (comp);
1851
1852                 e_cal_backend_cache_remove_component (cache, id->uid, id->rid);
1853                 e_cal_backend_notify_object_removed (backend, id, comp_str, NULL);
1854
1855                 g_free (comp_str);
1856                 e_cal_component_free_id (id);
1857                 g_object_unref (comp);
1858         }
1859
1860         g_list_free (comps_in_cache);
1861
1862         e_file_cache_thaw_changes (E_FILE_CACHE (cache));
1863 }