ECollectionBackend: Add virual methods to create/delete resources.
[platform/upstream/evolution-data-server.git] / libebackend / e-collection-backend.c
1 /*
2  * e-collection-backend.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  */
18
19 /**
20  * SECTION: e-collection-backend
21  * @include: libebackend/libebackend.h
22  * @short_description: An abstract base class for a data source
23  *                     collection backend
24  *
25  * #ECollectionBackend is an abstract base class for backends which
26  * manage a collection of data sources which collectively represent the
27  * resources on a remote server.  The resources can include any number
28  * of private and shared email stores, calendars and address books.
29  *
30  * The backend's job is to synchronize local representations of remote
31  * resources by adding and removing #EServerSideSource instances in an
32  * #ESourceRegistryServer.  If possible the backend should also listen
33  * for notifications of newly-added or deleted resources on the remote
34  * server or else poll the remote server at regular intervals and then
35  * update the data source collection accordingly.
36  *
37  * As most remote servers require authentication, the backend may also
38  * wish to implement the #ESourceAuthenticator interface so it can submit
39  * its own #EAuthenticationSession instances to the #ESourceRegistryServer.
40  **/
41
42 #include "e-collection-backend.h"
43
44 #include <config.h>
45 #include <glib/gi18n-lib.h>
46
47 #include <libedataserver/libedataserver.h>
48
49 #include <libebackend/e-server-side-source.h>
50 #include <libebackend/e-source-registry-server.h>
51
52 #define E_COLLECTION_BACKEND_GET_PRIVATE(obj) \
53         (G_TYPE_INSTANCE_GET_PRIVATE \
54         ((obj), E_TYPE_COLLECTION_BACKEND, ECollectionBackendPrivate))
55
56 struct _ECollectionBackendPrivate {
57         GWeakRef server;
58
59         /* Set of ESources */
60         GHashTable *children;
61         GMutex *children_lock;
62
63         gchar *cache_dir;
64
65         /* Resource ID -> ESource */
66         GHashTable *unclaimed_resources;
67         GMutex *unclaimed_resources_lock;
68
69         gulong source_added_handler_id;
70         gulong source_removed_handler_id;
71 };
72
73 enum {
74         PROP_0,
75         PROP_SERVER
76 };
77
78 enum {
79         CHILD_ADDED,
80         CHILD_REMOVED,
81         LAST_SIGNAL
82 };
83
84 static guint signals[LAST_SIGNAL];
85
86 G_DEFINE_ABSTRACT_TYPE (
87         ECollectionBackend,
88         e_collection_backend,
89         E_TYPE_BACKEND)
90
91 static void
92 collection_backend_children_insert (ECollectionBackend *backend,
93                                     ESource *source)
94 {
95         g_mutex_lock (backend->priv->children_lock);
96
97         g_hash_table_add (backend->priv->children, g_object_ref (source));
98
99         g_mutex_unlock (backend->priv->children_lock);
100 }
101
102 static gboolean
103 collection_backend_children_remove (ECollectionBackend *backend,
104                                     ESource *source)
105 {
106         gboolean removed;
107
108         g_mutex_lock (backend->priv->children_lock);
109
110         removed = g_hash_table_remove (backend->priv->children, source);
111
112         g_mutex_unlock (backend->priv->children_lock);
113
114         return removed;
115 }
116
117 static GList *
118 collection_backend_children_list (ECollectionBackend *backend)
119 {
120         GList *list, *link;
121
122         g_mutex_lock (backend->priv->children_lock);
123
124         list = g_hash_table_get_keys (backend->priv->children);
125
126         for (link = list; link != NULL; link = g_list_next (link))
127                 g_object_ref (link->data);
128
129         g_mutex_unlock (backend->priv->children_lock);
130
131         return list;
132 }
133
134 static GFile *
135 collection_backend_new_user_file (ECollectionBackend *backend)
136 {
137         GFile *file;
138         gchar *safe_uid;
139         gchar *basename;
140         gchar *filename;
141         const gchar *cache_dir;
142
143         /* This is like e_server_side_source_new_user_file()
144          * except that it uses the backend's cache directory. */
145
146         safe_uid = e_uid_new ();
147         e_filename_make_safe (safe_uid);
148
149         cache_dir = e_collection_backend_get_cache_dir (backend);
150         basename = g_strconcat (safe_uid, ".source", NULL);
151         filename = g_build_filename (cache_dir, basename, NULL);
152
153         file = g_file_new_for_path (filename);
154
155         g_free (basename);
156         g_free (filename);
157         g_free (safe_uid);
158
159         return file;
160 }
161
162 static ESource *
163 collection_backend_new_source (ECollectionBackend *backend,
164                                GFile *file,
165                                GError **error)
166 {
167         ESourceRegistryServer *server;
168         ESource *child_source;
169         ESource *collection_source;
170         EServerSideSource *server_side_source;
171         const gchar *cache_dir;
172         const gchar *collection_uid;
173
174         server = e_collection_backend_ref_server (backend);
175         child_source = e_server_side_source_new (server, file, error);
176         g_object_unref (server);
177
178         if (child_source == NULL)
179                 return NULL;
180
181         server_side_source = E_SERVER_SIDE_SOURCE (child_source);
182
183         /* Clients may change the source but may not remove it. */
184         e_server_side_source_set_writable (server_side_source, TRUE);
185         e_server_side_source_set_removable (server_side_source, FALSE);
186
187         /* Changes should be written back to the cache directory. */
188         cache_dir = e_collection_backend_get_cache_dir (backend);
189         e_server_side_source_set_write_directory (
190                 server_side_source, cache_dir);
191
192         /* Configure the child source as a collection member. */
193         collection_source = e_backend_get_source (E_BACKEND (backend));
194         collection_uid = e_source_get_uid (collection_source);
195         e_source_set_parent (child_source, collection_uid);
196
197         return child_source;
198 }
199
200 static void
201 collection_backend_load_resources (ECollectionBackend *backend)
202 {
203         ESourceRegistryServer *server;
204         ECollectionBackendClass *class;
205         GDir *dir;
206         GFile *file;
207         const gchar *name;
208         const gchar *cache_dir;
209         GError *error = NULL;
210
211         /* This is based on e_source_registry_server_load_file()
212          * and e_source_registry_server_load_directory(). */
213
214         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
215         g_return_if_fail (class->dup_resource_id != NULL);
216
217         cache_dir = e_collection_backend_get_cache_dir (backend);
218
219         dir = g_dir_open (cache_dir, 0, &error);
220         if (error != NULL) {
221                 g_warn_if_fail (dir == NULL);
222                 g_warning ("%s: %s", G_STRFUNC, error->message);
223                 g_error_free (error);
224                 return;
225         }
226
227         g_return_if_fail (dir != NULL);
228
229         file = g_file_new_for_path (cache_dir);
230         server = e_collection_backend_ref_server (backend);
231
232         g_mutex_lock (backend->priv->unclaimed_resources_lock);
233
234         while ((name = g_dir_read_name (dir)) != NULL) {
235                 GFile *child;
236                 ESource *source;
237                 gchar *resource_id;
238
239                 /* Ignore files with no ".source" suffix. */
240                 if (!g_str_has_suffix (name, ".source"))
241                         continue;
242
243                 child = g_file_get_child (file, name);
244                 source = collection_backend_new_source (backend, child, &error);
245                 g_object_unref (child);
246
247                 if (error != NULL) {
248                         g_warn_if_fail (source == NULL);
249                         g_warning ("%s: %s", G_STRFUNC, error->message);
250                         g_error_free (error);
251                         continue;
252                 }
253
254                 g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
255
256                 resource_id = class->dup_resource_id (backend, source);
257
258                 /* Hash table takes ownership of the resource ID. */
259                 if (resource_id != NULL)
260                         g_hash_table_insert (
261                                 backend->priv->unclaimed_resources,
262                                 resource_id, g_object_ref (source));
263
264                 g_object_unref (source);
265         }
266
267         g_mutex_unlock (backend->priv->unclaimed_resources_lock);
268
269         g_object_unref (file);
270         g_object_unref (server);
271 }
272
273 static ESource *
274 collection_backend_claim_resource (ECollectionBackend *backend,
275                                    const gchar *resource_id,
276                                    GError **error)
277 {
278         GHashTable *unclaimed_resources;
279         ESource *source;
280
281         g_mutex_lock (backend->priv->unclaimed_resources_lock);
282
283         unclaimed_resources = backend->priv->unclaimed_resources;
284         source = g_hash_table_lookup (unclaimed_resources, resource_id);
285
286         if (source != NULL) {
287                 g_object_ref (source);
288                 g_hash_table_remove (unclaimed_resources, resource_id);
289         } else {
290                 GFile *file = collection_backend_new_user_file (backend);
291                 source = collection_backend_new_source (backend, file, error);
292                 g_object_unref (file);
293         }
294
295         g_mutex_unlock (backend->priv->unclaimed_resources_lock);
296
297         return source;
298 }
299
300 static gboolean
301 collection_backend_child_is_calendar (ESource *child_source)
302 {
303         const gchar *extension_name;
304
305         extension_name = E_SOURCE_EXTENSION_CALENDAR;
306         if (e_source_has_extension (child_source, extension_name))
307                 return TRUE;
308
309         extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
310         if (e_source_has_extension (child_source, extension_name))
311                 return TRUE;
312
313         extension_name = E_SOURCE_EXTENSION_TASK_LIST;
314         if (e_source_has_extension (child_source, extension_name))
315                 return TRUE;
316
317         return FALSE;
318 }
319
320 static gboolean
321 collection_backend_child_is_contacts (ESource *child_source)
322 {
323         const gchar *extension_name;
324
325         extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
326         if (e_source_has_extension (child_source, extension_name))
327                 return TRUE;
328
329         return FALSE;
330 }
331
332 static gboolean
333 collection_backend_child_is_mail (ESource *child_source)
334 {
335         const gchar *extension_name;
336
337         extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
338         if (e_source_has_extension (child_source, extension_name))
339                 return TRUE;
340
341         extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
342         if (e_source_has_extension (child_source, extension_name))
343                 return TRUE;
344
345         extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
346         if (e_source_has_extension (child_source, extension_name))
347                 return TRUE;
348
349         return FALSE;
350 }
351
352 static void
353 collection_backend_bind_child_enabled (ECollectionBackend *backend,
354                                        ESource *child_source)
355 {
356         ESource *collection_source;
357         ESourceCollection *extension;
358         const gchar *extension_name;
359
360         /* See if the child source's "enabled" property can be
361          * bound to any ESourceCollection "enabled" properties. */
362
363         extension_name = E_SOURCE_EXTENSION_COLLECTION;
364         collection_source = e_backend_get_source (E_BACKEND (backend));
365         extension = e_source_get_extension (collection_source, extension_name);
366
367         if (collection_backend_child_is_calendar (child_source)) {
368                 g_object_bind_property (
369                         extension, "calendar-enabled",
370                         child_source, "enabled",
371                         G_BINDING_SYNC_CREATE);
372                 return;
373         }
374
375         if (collection_backend_child_is_contacts (child_source)) {
376                 g_object_bind_property (
377                         extension, "contacts-enabled",
378                         child_source, "enabled",
379                         G_BINDING_SYNC_CREATE);
380                 return;
381         }
382
383         if (collection_backend_child_is_mail (child_source)) {
384                 g_object_bind_property (
385                         extension, "mail-enabled",
386                         child_source, "enabled",
387                         G_BINDING_SYNC_CREATE);
388                 return;
389         }
390 }
391
392 static void
393 collection_backend_source_added_cb (ESourceRegistryServer *server,
394                                     ESource *source,
395                                     ECollectionBackend *backend)
396 {
397         ESource *collection_source;
398         ESource *parent_source;
399         const gchar *uid;
400
401         /* If the newly-added source is our own child, emit "child-added". */
402
403         collection_source = e_backend_get_source (E_BACKEND (backend));
404
405         uid = e_source_get_parent (source);
406         if (uid == NULL)
407                 return;
408
409         parent_source = e_source_registry_server_ref_source (server, uid);
410         g_return_if_fail (parent_source != NULL);
411
412         if (e_source_equal (collection_source, parent_source))
413                 g_signal_emit (backend, signals[CHILD_ADDED], 0, source);
414
415         g_object_unref (parent_source);
416 }
417
418 static void
419 collection_backend_source_removed_cb (ESourceRegistryServer *server,
420                                       ESource *source,
421                                       ECollectionBackend *backend)
422 {
423         ESource *collection_source;
424         ESource *parent_source;
425         const gchar *uid;
426
427         /* If the removed source was our own child, emit "child-removed".
428          * Note that the source is already unlinked from the GNode tree. */
429
430         collection_source = e_backend_get_source (E_BACKEND (backend));
431
432         uid = e_source_get_parent (source);
433         if (uid == NULL)
434                 return;
435
436         parent_source = e_source_registry_server_ref_source (server, uid);
437         g_return_if_fail (parent_source != NULL);
438
439         if (e_source_equal (collection_source, parent_source))
440                 g_signal_emit (backend, signals[CHILD_REMOVED], 0, source);
441
442         g_object_unref (parent_source);
443 }
444
445 static gboolean
446 collection_backend_populate_idle_cb (gpointer user_data)
447 {
448         ECollectionBackend *backend;
449         ECollectionBackendClass *class;
450
451         backend = E_COLLECTION_BACKEND (user_data);
452
453         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
454         g_return_val_if_fail (class->populate != NULL, FALSE);
455
456         class->populate (backend);
457
458         return FALSE;
459 }
460
461 static void
462 collection_backend_set_server (ECollectionBackend *backend,
463                                ESourceRegistryServer *server)
464 {
465         g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
466
467         g_weak_ref_set (&backend->priv->server, server);
468 }
469
470 static void
471 collection_backend_set_property (GObject *object,
472                                  guint property_id,
473                                  const GValue *value,
474                                  GParamSpec *pspec)
475 {
476         switch (property_id) {
477                 case PROP_SERVER:
478                         collection_backend_set_server (
479                                 E_COLLECTION_BACKEND (object),
480                                 g_value_get_object (value));
481                         return;
482         }
483
484         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
485 }
486
487 static void
488 collection_backend_get_property (GObject *object,
489                                  guint property_id,
490                                  GValue *value,
491                                  GParamSpec *pspec)
492 {
493         switch (property_id) {
494                 case PROP_SERVER:
495                         g_value_take_object (
496                                 value,
497                                 e_collection_backend_ref_server (
498                                 E_COLLECTION_BACKEND (object)));
499                         return;
500         }
501
502         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
503 }
504
505 static void
506 collection_backend_dispose (GObject *object)
507 {
508         ECollectionBackendPrivate *priv;
509         ESourceRegistryServer *server;
510
511         priv = E_COLLECTION_BACKEND_GET_PRIVATE (object);
512
513         server = g_weak_ref_get (&priv->server);
514         if (server != NULL) {
515                 g_signal_handler_disconnect (
516                         server, priv->source_added_handler_id);
517                 g_signal_handler_disconnect (
518                         server, priv->source_removed_handler_id);
519                 g_weak_ref_set (&priv->server, NULL);
520                 g_object_unref (server);
521         }
522
523         g_mutex_lock (priv->children_lock);
524         g_hash_table_remove_all (priv->children);
525         g_mutex_unlock (priv->children_lock);
526
527         g_mutex_lock (priv->unclaimed_resources_lock);
528         g_hash_table_remove_all (priv->unclaimed_resources);
529         g_mutex_unlock (priv->unclaimed_resources_lock);
530
531         /* Chain up to parent's dispose() method. */
532         G_OBJECT_CLASS (e_collection_backend_parent_class)->dispose (object);
533 }
534
535 static void
536 collection_backend_finalize (GObject *object)
537 {
538         ECollectionBackendPrivate *priv;
539
540         priv = E_COLLECTION_BACKEND_GET_PRIVATE (object);
541
542         g_hash_table_destroy (priv->children);
543         g_mutex_free (priv->children_lock);
544
545         g_hash_table_destroy (priv->unclaimed_resources);
546         g_mutex_free (priv->unclaimed_resources_lock);
547
548         /* Chain up to parent's finalize() method. */
549         G_OBJECT_CLASS (e_collection_backend_parent_class)->finalize (object);
550 }
551
552 static void
553 collection_backend_constructed (GObject *object)
554 {
555         ECollectionBackend *backend;
556         ESourceRegistryServer *server;
557         ESource *source;
558         GNode *node;
559         const gchar *collection_uid;
560         const gchar *user_cache_dir;
561         gulong handler_id;
562
563         backend = E_COLLECTION_BACKEND (object);
564
565         /* Chain up to parent's constructed() method. */
566         G_OBJECT_CLASS (e_collection_backend_parent_class)->
567                 constructed (object);
568
569         source = e_backend_get_source (E_BACKEND (backend));
570
571         /* Determine the backend's cache directory. */
572
573         user_cache_dir = e_get_user_cache_dir ();
574         collection_uid = e_source_get_uid (source);
575         backend->priv->cache_dir = g_build_filename (
576                 user_cache_dir, "sources", collection_uid, NULL);
577         g_mkdir_with_parents (backend->priv->cache_dir, 0700);
578
579         /* This requires the cache directory to be set. */
580         collection_backend_load_resources (backend);
581
582         /* Emit "child-added" signals for the children we already have. */
583
584         node = e_server_side_source_get_node (E_SERVER_SIDE_SOURCE (source));
585         node = g_node_first_child (node);
586
587         while (node != NULL) {
588                 ESource *child = E_SOURCE (node->data);
589                 g_signal_emit (backend, signals[CHILD_ADDED], 0, child);
590                 node = g_node_next_sibling (node);
591         }
592
593         /* Listen for "source-added" and "source-removed" signals
594          * from the server, which may trigger our own "child-added"
595          * and "child-removed" signals. */
596
597         server = e_collection_backend_ref_server (backend);
598
599         handler_id = g_signal_connect (
600                 server, "source-added",
601                 G_CALLBACK (collection_backend_source_added_cb), backend);
602
603         backend->priv->source_added_handler_id = handler_id;
604
605         handler_id = g_signal_connect (
606                 server, "source-removed",
607                 G_CALLBACK (collection_backend_source_removed_cb), backend);
608
609         backend->priv->source_removed_handler_id = handler_id;
610
611         g_object_unref (server);
612
613         /* Populate the newly-added collection from an idle callback
614          * so persistent child sources have a chance to be added first. */
615
616         g_idle_add_full (
617                 G_PRIORITY_LOW,
618                 collection_backend_populate_idle_cb,
619                 g_object_ref (backend),
620                 (GDestroyNotify) g_object_unref);
621 }
622
623 static gboolean
624 collection_backend_authenticate_sync (EBackend *backend,
625                                       ESourceAuthenticator *auth,
626                                       GCancellable *cancellable,
627                                       GError **error)
628 {
629         ECollectionBackend *collection_backend;
630         ESourceRegistryServer *server;
631         EAuthenticationSession *session;
632         ESource *source;
633         const gchar *uid;
634         gboolean success;
635
636         source = e_backend_get_source (backend);
637         uid = e_source_get_uid (source);
638
639         collection_backend = E_COLLECTION_BACKEND (backend);
640         server = e_collection_backend_ref_server (collection_backend);
641         session = e_authentication_session_new (server, auth, uid);
642
643         success = e_source_registry_server_authenticate_sync (
644                 server, session, cancellable, error);
645
646         g_object_unref (session);
647         g_object_unref (server);
648
649         return success;
650 }
651
652 static void
653 collection_backend_populate (ECollectionBackend *backend)
654 {
655         /* Placeholder so subclasses can safely chain up. */
656 }
657
658 static gchar *
659 collection_backend_dup_resource_id (ECollectionBackend *backend,
660                                     ESource *source)
661 {
662         const gchar *extension_name;
663         gchar *resource_id = NULL;
664
665         extension_name = E_SOURCE_EXTENSION_RESOURCE;
666
667         if (e_source_has_extension (source, extension_name)) {
668                 ESourceResource *extension;
669
670                 extension = e_source_get_extension (source, extension_name);
671                 resource_id = e_source_resource_dup_identity (extension);
672         }
673
674         return resource_id;
675 }
676
677 static void
678 collection_backend_child_added (ECollectionBackend *backend,
679                                 ESource *child_source)
680 {
681         collection_backend_children_insert (backend, child_source);
682         collection_backend_bind_child_enabled (backend, child_source);
683
684         /* Collection children are not removable. */
685         e_server_side_source_set_removable (
686                 E_SERVER_SIDE_SOURCE (child_source), FALSE);
687 }
688
689 static void
690 collection_backend_child_removed (ECollectionBackend *backend,
691                                   ESource *child_source)
692 {
693         collection_backend_children_remove (backend, child_source);
694 }
695
696 static gboolean
697 collection_backend_create_resource_sync (ECollectionBackend *backend,
698                                          ESource *source,
699                                          GCancellable *cancellable,
700                                          GError **error)
701 {
702         EAsyncClosure *closure;
703         GAsyncResult *result;
704         gboolean success;
705
706         closure = e_async_closure_new ();
707
708         e_collection_backend_create_resource (
709                 backend, source, cancellable,
710                 e_async_closure_callback, closure);
711
712         result = e_async_closure_wait (closure);
713
714         success = e_collection_backend_create_resource_finish (
715                 backend, result, error);
716
717         e_async_closure_free (closure);
718
719         return success;
720 }
721
722 static void
723 collection_backend_create_resource (ECollectionBackend *backend,
724                                     ESource *source,
725                                     GCancellable *cancellable,
726                                     GAsyncReadyCallback callback,
727                                     gpointer user_data)
728 {
729         GSimpleAsyncResult *simple;
730
731         simple = g_simple_async_result_new_error (
732                 G_OBJECT (backend), callback, user_data,
733                 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
734                 _("%s does not support creating remote resources"),
735                 G_OBJECT_TYPE_NAME (backend));
736
737         g_simple_async_result_complete_in_idle (simple);
738
739         g_object_unref (simple);
740 }
741
742 static gboolean
743 collection_backend_create_resource_finish (ECollectionBackend *backend,
744                                            GAsyncResult *result,
745                                            GError **error)
746 {
747         GSimpleAsyncResult *simple;
748
749         simple = G_SIMPLE_ASYNC_RESULT (result);
750
751         /* Assume success unless a GError is set. */
752         return !g_simple_async_result_propagate_error (simple, error);
753 }
754
755 static gboolean
756 collection_backend_delete_resource_sync (ECollectionBackend *backend,
757                                          ESource *source,
758                                          GCancellable *cancellable,
759                                          GError **error)
760 {
761         EAsyncClosure *closure;
762         GAsyncResult *result;
763         gboolean success;
764
765         closure = e_async_closure_new ();
766
767         e_collection_backend_delete_resource (
768                 backend, source, cancellable,
769                 e_async_closure_callback, closure);
770
771         result = e_async_closure_wait (closure);
772
773         success = e_collection_backend_delete_resource_finish (
774                 backend, result, error);
775
776         e_async_closure_free (closure);
777
778         return success;
779 }
780
781 static void
782 collection_backend_delete_resource (ECollectionBackend *backend,
783                                     ESource *source,
784                                     GCancellable *cancellable,
785                                     GAsyncReadyCallback callback,
786                                     gpointer user_data)
787 {
788         GSimpleAsyncResult *simple;
789
790         simple = g_simple_async_result_new_error (
791                 G_OBJECT (backend), callback, user_data,
792                 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
793                 _("%s does not support deleting remote resources"),
794                 G_OBJECT_TYPE_NAME (backend));
795
796         g_simple_async_result_complete_in_idle (simple);
797
798         g_object_unref (simple);
799 }
800
801 static gboolean
802 collection_backend_delete_resource_finish (ECollectionBackend *backend,
803                                            GAsyncResult *result,
804                                            GError **error)
805 {
806         GSimpleAsyncResult *simple;
807
808         simple = G_SIMPLE_ASYNC_RESULT (result);
809
810         /* Assume success unless a GError is set. */
811         return !g_simple_async_result_propagate_error (simple, error);
812 }
813
814 static void
815 e_collection_backend_class_init (ECollectionBackendClass *class)
816 {
817         GObjectClass *object_class;
818         EBackendClass *backend_class;
819
820         g_type_class_add_private (class, sizeof (ECollectionBackendPrivate));
821
822         object_class = G_OBJECT_CLASS (class);
823         object_class->set_property = collection_backend_set_property;
824         object_class->get_property = collection_backend_get_property;
825         object_class->dispose = collection_backend_dispose;
826         object_class->finalize = collection_backend_finalize;
827         object_class->constructed = collection_backend_constructed;
828
829         backend_class = E_BACKEND_CLASS (class);
830         backend_class->authenticate_sync = collection_backend_authenticate_sync;
831
832         class->populate = collection_backend_populate;
833         class->dup_resource_id = collection_backend_dup_resource_id;
834         class->child_added = collection_backend_child_added;
835         class->child_removed = collection_backend_child_removed;
836         class->create_resource_sync = collection_backend_create_resource_sync;
837         class->create_resource = collection_backend_create_resource;
838         class->create_resource_finish = collection_backend_create_resource_finish;
839         class->delete_resource_sync = collection_backend_delete_resource_sync;
840         class->delete_resource = collection_backend_delete_resource;
841         class->delete_resource_finish = collection_backend_delete_resource_finish;
842
843         g_object_class_install_property (
844                 object_class,
845                 PROP_SERVER,
846                 g_param_spec_object (
847                         "server",
848                         "Server",
849                         "The server to which the backend belongs",
850                         E_TYPE_SOURCE_REGISTRY_SERVER,
851                         G_PARAM_READWRITE |
852                         G_PARAM_CONSTRUCT_ONLY |
853                         G_PARAM_STATIC_STRINGS));
854
855         /**
856          * ECollectionBackend::child-added:
857          * @backend: the #ECollectionBackend which emitted the signal
858          * @child_source: the newly-added child #EServerSideSource
859          *
860          * Emitted when an #EServerSideSource is added to @backend's
861          * #ECollectionBackend:server as a child of @backend's collection
862          * #EBackend:source.
863          *
864          * You can think of this as a filtered version of
865          * #ESourceRegistryServer's #ESourceRegistryServer::source-added
866          * signal which only lets through sources relevant to @backend.
867          **/
868         signals[CHILD_ADDED] = g_signal_new (
869                 "child-added",
870                 G_OBJECT_CLASS_TYPE (object_class),
871                 G_SIGNAL_RUN_LAST,
872                 G_STRUCT_OFFSET (ECollectionBackendClass, child_added),
873                 NULL, NULL,
874                 g_cclosure_marshal_VOID__OBJECT,
875                 G_TYPE_NONE, 1,
876                 E_TYPE_SERVER_SIDE_SOURCE);
877
878         /**
879          * ECollectionBackend::child-removed:
880          * @backend: the #ECollectionBackend which emitted the signal
881          * @child_source: the child #EServerSideSource that got removed
882          *
883          * Emitted when an #EServerSideSource that is a child of
884          * @backend's collection #EBackend:source is removed from
885          * @backend's #ECollectionBackend:server.
886          *
887          * You can think of this as a filtered version of
888          * #ESourceRegistryServer's #ESourceRegistryServer::source-removed
889          * signal which only lets through sources relevant to @backend.
890          **/
891         signals[CHILD_REMOVED] = g_signal_new (
892                 "child-removed",
893                 G_OBJECT_CLASS_TYPE (object_class),
894                 G_SIGNAL_RUN_LAST,
895                 G_STRUCT_OFFSET (ECollectionBackendClass, child_removed),
896                 NULL, NULL,
897                 g_cclosure_marshal_VOID__OBJECT,
898                 G_TYPE_NONE, 1,
899                 E_TYPE_SERVER_SIDE_SOURCE);
900 }
901
902 static void
903 e_collection_backend_init (ECollectionBackend *backend)
904 {
905         GHashTable *children;
906         GHashTable *unclaimed_resources;
907
908         children = g_hash_table_new_full (
909                 (GHashFunc) e_source_hash,
910                 (GEqualFunc) e_source_equal,
911                 (GDestroyNotify) g_object_unref,
912                 (GDestroyNotify) NULL);
913
914         unclaimed_resources = g_hash_table_new_full (
915                 (GHashFunc) g_str_hash,
916                 (GEqualFunc) g_str_equal,
917                 (GDestroyNotify) g_free,
918                 (GDestroyNotify) g_object_unref);
919
920         backend->priv = E_COLLECTION_BACKEND_GET_PRIVATE (backend);
921         backend->priv->children = children;
922         backend->priv->children_lock = g_mutex_new ();
923         backend->priv->unclaimed_resources = unclaimed_resources;
924         backend->priv->unclaimed_resources_lock = g_mutex_new ();
925 }
926
927 /**
928  * e_collection_backend_new_child:
929  * @backend: an #ECollectionBackend
930  * @resource_id: a stable and unique resource ID
931  *
932  * Creates a new #EServerSideSource as a child of the collection
933  * #EBackend:source owned by @backend.  If possible, the #EServerSideSource
934  * is drawn from a cache of previously used sources indexed by @resource_id
935  * so that locally cached data from previous sessions can be reused.
936  *
937  * The returned data source should be passed to
938  * e_source_registry_server_add_source() to export it over D-Bus.
939  *
940  * Return: a newly-created data source
941  *
942  * Since: 3.6
943  **/
944 ESource *
945 e_collection_backend_new_child (ECollectionBackend *backend,
946                                 const gchar *resource_id)
947 {
948         ESource *collection_source;
949         ESource *child_source;
950         GError *error = NULL;
951
952         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
953         g_return_val_if_fail (resource_id != NULL, NULL);
954
955         /* This being a newly-created or existing data source, claiming
956          * it should never fail but we'll check for errors just the same.
957          * It's unlikely enough that we don't need a GError parameter. */
958         child_source = collection_backend_claim_resource (
959                 backend, resource_id, &error);
960
961         if (error != NULL) {
962                 g_warn_if_fail (child_source == NULL);
963                 g_warning ("%s: %s", G_STRFUNC, error->message);
964                 g_error_free (error);
965                 return NULL;
966         }
967
968         collection_source = e_backend_get_source (E_BACKEND (backend));
969
970         g_print ("%s: Pairing %s with resource %s\n",
971                 e_source_get_display_name (collection_source),
972                 e_source_get_uid (child_source), resource_id);
973
974         return child_source;
975 }
976
977 /**
978  * e_collection_backend_ref_server:
979  * @backend: an #ECollectionBackend
980  *
981  * Returns the #ESourceRegistryServer to which @backend belongs.
982  *
983  * The returned #ESourceRegistryServer is referenced for thread-safety.
984  * Unreference the #ESourceRegistryServer with g_object_unref() when
985  * finished with it.
986  *
987  * Returns: the #ESourceRegisterServer for @backend
988  *
989  * Since: 3.6
990  **/
991 ESourceRegistryServer *
992 e_collection_backend_ref_server (ECollectionBackend *backend)
993 {
994         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
995
996         return g_weak_ref_get (&backend->priv->server);
997 }
998
999 /**
1000  * e_collection_backend_get_cache_dir:
1001  * @backend: an #ECollectionBackend
1002  *
1003  * Returns the private cache directory path for @backend, which is named
1004  * after the #ESource:uid of @backend's collection #EBackend:source.
1005  *
1006  * The cache directory is meant to store key files for backend-created
1007  * data sources.  See also: e_server_side_source_set_write_directory()
1008  *
1009  * Returns: the cache directory for @backend
1010  *
1011  * Since: 3.6
1012  **/
1013 const gchar *
1014 e_collection_backend_get_cache_dir (ECollectionBackend *backend)
1015 {
1016         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1017
1018         return backend->priv->cache_dir;
1019 }
1020
1021 /**
1022  * e_collection_backend_list_calendar_sources:
1023  * @backend: an #ECollectionBackend
1024  *
1025  * Returns a list of calendar sources belonging to the data source
1026  * collection managed by @backend.
1027  *
1028  * The sources returned in the list are referenced for thread-safety.
1029  * They must each be unreferenced with g_object_unref() when finished
1030  * with them.  Free the returned #GList itself with g_list_free().
1031  *
1032  * An easy way to free the list properly in one step is as follows:
1033  *
1034  * |[
1035  *   g_list_free_full (list, g_object_unref);
1036  * ]|
1037  *
1038  * Returns: a list of calendar sources
1039  *
1040  * Since: 3.6
1041  **/
1042 GList *
1043 e_collection_backend_list_calendar_sources (ECollectionBackend *backend)
1044 {
1045         GList *result_list = NULL;
1046         GList *list, *link;
1047
1048         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1049
1050         list = collection_backend_children_list (backend);
1051
1052         for (link = list; link != NULL; link = g_list_next (link)) {
1053                 ESource *child_source = E_SOURCE (link->data);
1054                 if (collection_backend_child_is_calendar (child_source))
1055                         result_list = g_list_prepend (
1056                                 result_list, g_object_ref (child_source));
1057         }
1058
1059         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1060
1061         return g_list_reverse (result_list);
1062 }
1063
1064 /**
1065  * e_collection_backend_list_contacts_sources:
1066  * @backend: an #ECollectionBackend
1067  *
1068  * Returns a list of address book sources belonging to the data source
1069  * collection managed by @backend.
1070  *
1071  * The sources returned in the list are referenced for thread-safety.
1072  * They must each be unreferenced with g_object_unref() when finished
1073  * with them.  Free the returned #GList itself with g_list_free().
1074  *
1075  * An easy way to free the list properly in one step is as follows:
1076  *
1077  * |[
1078  *   g_list_free_full (list, g_object_unref);
1079  * ]|
1080  *
1081  * Returns: a list of address book sources
1082  *
1083  * Since: 3.6
1084  **/
1085 GList *
1086 e_collection_backend_list_contacts_sources (ECollectionBackend *backend)
1087 {
1088         GList *result_list = NULL;
1089         GList *list, *link;
1090
1091         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1092
1093         list = collection_backend_children_list (backend);
1094
1095         for (link = list; link != NULL; link = g_list_next (link)) {
1096                 ESource *child_source = E_SOURCE (link->data);
1097                 if (collection_backend_child_is_contacts (child_source))
1098                         result_list = g_list_prepend (
1099                                 result_list, g_object_ref (child_source));
1100         }
1101
1102         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1103
1104         return g_list_reverse (result_list);
1105 }
1106
1107 /**
1108  * e_collection_backend_list_mail_sources:
1109  * @backend: an #ECollectionBackend
1110  *
1111  * Returns a list of mail sources belonging to the data source collection
1112  * managed by @backend.
1113  *
1114  * The sources returned in the list are referenced for thread-safety.
1115  * They must each be unreferenced with g_object_unref() when finished
1116  * with them.  Free the returned #GList itself with g_list_free().
1117  *
1118  * An easy way to free the list properly in one step is as follows:
1119  *
1120  * |[
1121  *   g_list_free_full (list, g_object_unref);
1122  * ]|
1123  *
1124  * Returns: a list of mail sources
1125  *
1126  * Since: 3.6
1127  **/
1128 GList *
1129 e_collection_backend_list_mail_sources (ECollectionBackend *backend)
1130 {
1131         GList *result_list = NULL;
1132         GList *list, *link;
1133
1134         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1135
1136         list = collection_backend_children_list (backend);
1137
1138         for (link = list; link != NULL; link = g_list_next (link)) {
1139                 ESource *child_source = E_SOURCE (link->data);
1140                 if (collection_backend_child_is_mail (child_source))
1141                         result_list = g_list_prepend (
1142                                 result_list, g_object_ref (child_source));
1143         }
1144
1145         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1146
1147         return g_list_reverse (result_list);
1148 }
1149
1150 /**
1151  * e_collection_backend_create_resource_sync
1152  * @backend: an #ECollectionBackend
1153  * @source: an #ESource
1154  * @cancellable: optional #GCancellable object, or %NULL
1155  * @error: return location for a #GError, or %NULL
1156  *
1157  * Creates a server-side resource described by @source.  For example, if
1158  * @source describes a new calendar, an equivalent calendar is created on
1159  * the server.
1160  *
1161  * It is the implementor's responsibility to examine @source and determine
1162  * what the equivalent server-side resource would be.  If this cannot be
1163  * determined without ambiguity, the function must return an error.
1164  *
1165  * After the server-side resource is successfully created, the implementor
1166  * must also add an #ESource to @backend's #ECollectionBackend:server.  This
1167  * can either be done immediately or in response to some "resource created"
1168  * notification from the server.  The added #ESource can be @source itself
1169  * or a different #ESource instance that describes the new resource.
1170  * implementor's responsibility to add an #ESource to.
1171  *
1172  * Since: 3.6
1173  **/
1174 gboolean
1175 e_collection_backend_create_resource_sync (ECollectionBackend *backend,
1176                                            ESource *source,
1177                                            GCancellable *cancellable,
1178                                            GError **error)
1179 {
1180         ECollectionBackendClass *class;
1181
1182         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1183         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1184
1185         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1186         g_return_val_if_fail (class->create_resource_sync != NULL, FALSE);
1187
1188         return class->create_resource_sync (
1189                 backend, source, cancellable, error);
1190 }
1191
1192 void
1193 e_collection_backend_create_resource (ECollectionBackend *backend,
1194                                       ESource *source,
1195                                       GCancellable *cancellable,
1196                                       GAsyncReadyCallback callback,
1197                                       gpointer user_data)
1198 {
1199         ECollectionBackendClass *class;
1200
1201         g_return_if_fail (E_IS_COLLECTION_BACKEND (backend));
1202         g_return_if_fail (E_IS_SOURCE (source));
1203
1204         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1205         g_return_if_fail (class->create_resource != NULL);
1206
1207         class->create_resource (
1208                 backend, source, cancellable, callback, user_data);
1209 }
1210
1211 gboolean
1212 e_collection_backend_create_resource_finish (ECollectionBackend *backend,
1213                                              GAsyncResult *result,
1214                                              GError **error)
1215 {
1216         ECollectionBackendClass *class;
1217
1218         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1219         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
1220
1221         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1222         g_return_val_if_fail (class->create_resource_finish != NULL, FALSE);
1223
1224         return class->create_resource_finish (backend, result, error);
1225 }
1226
1227 gboolean
1228 e_collection_backend_delete_resource_sync (ECollectionBackend *backend,
1229                                            ESource *source,
1230                                            GCancellable *cancellable,
1231                                            GError **error)
1232 {
1233         ECollectionBackendClass *class;
1234
1235         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1236         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1237
1238         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1239         g_return_val_if_fail (class->delete_resource_sync != NULL, FALSE);
1240
1241         return class->delete_resource_sync (
1242                 backend, source, cancellable, error);
1243 }
1244
1245 void
1246 e_collection_backend_delete_resource (ECollectionBackend *backend,
1247                                       ESource *source,
1248                                       GCancellable *cancellable,
1249                                       GAsyncReadyCallback callback,
1250                                       gpointer user_data)
1251 {
1252         ECollectionBackendClass *class;
1253
1254         g_return_if_fail (E_IS_COLLECTION_BACKEND (backend));
1255         g_return_if_fail (E_IS_SOURCE (source));
1256
1257         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1258         g_return_if_fail (class->delete_resource != NULL);
1259
1260         return class->delete_resource (
1261                 backend, source, cancellable, callback, user_data);
1262 }
1263
1264 gboolean
1265 e_collection_backend_delete_resource_finish (ECollectionBackend *backend,
1266                                              GAsyncResult *result,
1267                                              GError **error)
1268 {
1269         ECollectionBackendClass *class;
1270
1271         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1272         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
1273
1274         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1275         g_return_val_if_fail (class->delete_resource_finish != NULL, FALSE);
1276
1277         return class->delete_resource_finish (backend, result, error);
1278 }
1279