Fix few memory leaks in evolution-source-registry
[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: A base class for a data source collection backend
23  *
24  * #ECollectionBackend is a base class for backends which manage a
25  * collection of data sources that collectively represent the resources
26  * on a remote server.  The resources can include any number of private
27  * and shared email stores, calendars and address books.
28  *
29  * The backend's job is to synchronize local representations of remote
30  * resources by adding and removing #EServerSideSource instances in an
31  * #ESourceRegistryServer.  If possible the backend should also listen
32  * for notifications of newly-added or deleted resources on the remote
33  * server or else poll the remote server at regular intervals and then
34  * update the data source collection accordingly.
35  *
36  * As most remote servers require authentication, the backend may also
37  * wish to implement the #ESourceAuthenticator interface so it can submit
38  * its own #EAuthenticationSession instances to the #ESourceRegistryServer.
39  **/
40
41 #include "e-collection-backend.h"
42
43 #include <config.h>
44 #include <glib/gi18n-lib.h>
45
46 #include <libedataserver/libedataserver.h>
47
48 #include <libebackend/e-server-side-source.h>
49 #include <libebackend/e-source-registry-server.h>
50
51 #define E_COLLECTION_BACKEND_GET_PRIVATE(obj) \
52         (G_TYPE_INSTANCE_GET_PRIVATE \
53         ((obj), E_TYPE_COLLECTION_BACKEND, ECollectionBackendPrivate))
54
55 struct _ECollectionBackendPrivate {
56         GWeakRef server;
57
58         /* Set of ESources */
59         GHashTable *children;
60         GMutex children_lock;
61
62         gchar *cache_dir;
63
64         /* Resource ID -> ESource */
65         GHashTable *unclaimed_resources;
66         GMutex unclaimed_resources_lock;
67
68         gulong source_added_handler_id;
69         gulong source_removed_handler_id;
70 };
71
72 enum {
73         PROP_0,
74         PROP_SERVER
75 };
76
77 enum {
78         CHILD_ADDED,
79         CHILD_REMOVED,
80         LAST_SIGNAL
81 };
82
83 static guint signals[LAST_SIGNAL];
84
85 G_DEFINE_TYPE (
86         ECollectionBackend,
87         e_collection_backend,
88         E_TYPE_BACKEND)
89
90 static void
91 collection_backend_children_insert (ECollectionBackend *backend,
92                                     ESource *source)
93 {
94         g_mutex_lock (&backend->priv->children_lock);
95
96         g_hash_table_add (backend->priv->children, g_object_ref (source));
97
98         g_mutex_unlock (&backend->priv->children_lock);
99 }
100
101 static gboolean
102 collection_backend_children_remove (ECollectionBackend *backend,
103                                     ESource *source)
104 {
105         gboolean removed;
106
107         g_mutex_lock (&backend->priv->children_lock);
108
109         removed = g_hash_table_remove (backend->priv->children, source);
110
111         g_mutex_unlock (&backend->priv->children_lock);
112
113         return removed;
114 }
115
116 static GList *
117 collection_backend_children_list (ECollectionBackend *backend)
118 {
119         GList *list, *link;
120
121         g_mutex_lock (&backend->priv->children_lock);
122
123         list = g_hash_table_get_keys (backend->priv->children);
124
125         for (link = list; link != NULL; link = g_list_next (link))
126                 g_object_ref (link->data);
127
128         g_mutex_unlock (&backend->priv->children_lock);
129
130         return list;
131 }
132
133 static GFile *
134 collection_backend_new_user_file (ECollectionBackend *backend)
135 {
136         GFile *file;
137         gchar *safe_uid;
138         gchar *basename;
139         gchar *filename;
140         const gchar *cache_dir;
141
142         /* This is like e_server_side_source_new_user_file()
143          * except that it uses the backend's cache directory. */
144
145         safe_uid = e_uid_new ();
146         e_filename_make_safe (safe_uid);
147
148         cache_dir = e_collection_backend_get_cache_dir (backend);
149         basename = g_strconcat (safe_uid, ".source", NULL);
150         filename = g_build_filename (cache_dir, basename, NULL);
151
152         file = g_file_new_for_path (filename);
153
154         g_free (basename);
155         g_free (filename);
156         g_free (safe_uid);
157
158         return file;
159 }
160
161 static ESource *
162 collection_backend_new_source (ECollectionBackend *backend,
163                                GFile *file,
164                                GError **error)
165 {
166         ESourceRegistryServer *server;
167         ESource *child_source;
168         ESource *collection_source;
169         EServerSideSource *server_side_source;
170         const gchar *cache_dir;
171         const gchar *collection_uid;
172
173         server = e_collection_backend_ref_server (backend);
174         child_source = e_server_side_source_new (server, file, error);
175         g_object_unref (server);
176
177         if (child_source == NULL)
178                 return NULL;
179
180         server_side_source = E_SERVER_SIDE_SOURCE (child_source);
181
182         /* Clients may change the source but may not remove it. */
183         e_server_side_source_set_writable (server_side_source, TRUE);
184         e_server_side_source_set_removable (server_side_source, FALSE);
185
186         /* Changes should be written back to the cache directory. */
187         cache_dir = e_collection_backend_get_cache_dir (backend);
188         e_server_side_source_set_write_directory (
189                 server_side_source, cache_dir);
190
191         /* Configure the child source as a collection member. */
192         collection_source = e_backend_get_source (E_BACKEND (backend));
193         collection_uid = e_source_get_uid (collection_source);
194         e_source_set_parent (child_source, collection_uid);
195
196         return child_source;
197 }
198
199 static void
200 collection_backend_load_resources (ECollectionBackend *backend)
201 {
202         ESourceRegistryServer *server;
203         ECollectionBackendClass *class;
204         GDir *dir;
205         GFile *file;
206         const gchar *name;
207         const gchar *cache_dir;
208         GError *error = NULL;
209
210         /* This is based on e_source_registry_server_load_file()
211          * and e_source_registry_server_load_directory(). */
212
213         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
214         g_return_if_fail (class->dup_resource_id != NULL);
215
216         cache_dir = e_collection_backend_get_cache_dir (backend);
217
218         dir = g_dir_open (cache_dir, 0, &error);
219         if (error != NULL) {
220                 g_warn_if_fail (dir == NULL);
221                 g_warning ("%s: %s", G_STRFUNC, error->message);
222                 g_error_free (error);
223                 return;
224         }
225
226         g_return_if_fail (dir != NULL);
227
228         file = g_file_new_for_path (cache_dir);
229         server = e_collection_backend_ref_server (backend);
230
231         g_mutex_lock (&backend->priv->unclaimed_resources_lock);
232
233         while ((name = g_dir_read_name (dir)) != NULL) {
234                 GFile *child;
235                 ESource *source;
236                 gchar *resource_id;
237
238                 /* Ignore files with no ".source" suffix. */
239                 if (!g_str_has_suffix (name, ".source"))
240                         continue;
241
242                 child = g_file_get_child (file, name);
243                 source = collection_backend_new_source (backend, child, &error);
244                 g_object_unref (child);
245
246                 if (error != NULL) {
247                         g_warn_if_fail (source == NULL);
248                         g_warning ("%s: %s", G_STRFUNC, error->message);
249                         g_error_free (error);
250                         continue;
251                 }
252
253                 g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));
254
255                 resource_id = class->dup_resource_id (backend, source);
256
257                 /* Hash table takes ownership of the resource ID. */
258                 if (resource_id != NULL)
259                         g_hash_table_insert (
260                                 backend->priv->unclaimed_resources,
261                                 resource_id, g_object_ref (source));
262
263                 g_object_unref (source);
264         }
265
266         g_mutex_unlock (&backend->priv->unclaimed_resources_lock);
267
268         g_object_unref (file);
269         g_object_unref (server);
270         g_dir_close (dir);
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 gboolean
353 include_master_source_enabled_transform (GBinding *binding,
354                                          const GValue *source_value,
355                                          GValue *target_value,
356                                          gpointer backend)
357 {
358         g_value_set_boolean (
359                 target_value,
360                 g_value_get_boolean (source_value) &&
361                 e_source_get_enabled (e_backend_get_source (backend)));
362
363         return TRUE;
364 }
365
366 static void
367 collection_backend_bind_child_enabled (ECollectionBackend *backend,
368                                        ESource *child_source)
369 {
370         ESource *collection_source;
371         ESourceCollection *extension;
372         const gchar *extension_name;
373
374         /* See if the child source's "enabled" property can be
375          * bound to any ESourceCollection "enabled" properties. */
376
377         extension_name = E_SOURCE_EXTENSION_COLLECTION;
378         collection_source = e_backend_get_source (E_BACKEND (backend));
379         extension = e_source_get_extension (collection_source, extension_name);
380
381         if (collection_backend_child_is_calendar (child_source)) {
382                 g_object_bind_property_full (
383                         extension, "calendar-enabled",
384                         child_source, "enabled",
385                         G_BINDING_SYNC_CREATE,
386                         include_master_source_enabled_transform,
387                         include_master_source_enabled_transform,
388                         backend,
389                         NULL);
390                 return;
391         }
392
393         if (collection_backend_child_is_contacts (child_source)) {
394                 g_object_bind_property_full (
395                         extension, "contacts-enabled",
396                         child_source, "enabled",
397                         G_BINDING_SYNC_CREATE,
398                         include_master_source_enabled_transform,
399                         include_master_source_enabled_transform,
400                         backend,
401                         NULL);
402                 return;
403         }
404
405         if (collection_backend_child_is_mail (child_source)) {
406                 g_object_bind_property_full (
407                         extension, "mail-enabled",
408                         child_source, "enabled",
409                         G_BINDING_SYNC_CREATE,
410                         include_master_source_enabled_transform,
411                         include_master_source_enabled_transform,
412                         backend,
413                         NULL);
414                 return;
415         }
416
417         g_object_bind_property (
418                 collection_source, "enabled",
419                 child_source, "enabled",
420                 G_BINDING_SYNC_CREATE);
421 }
422
423 static void
424 collection_backend_source_added_cb (ESourceRegistryServer *server,
425                                     ESource *source,
426                                     ECollectionBackend *backend)
427 {
428         ESource *collection_source;
429         ESource *parent_source;
430         const gchar *uid;
431
432         /* If the newly-added source is our own child, emit "child-added". */
433
434         collection_source = e_backend_get_source (E_BACKEND (backend));
435
436         uid = e_source_get_parent (source);
437         if (uid == NULL)
438                 return;
439
440         parent_source = e_source_registry_server_ref_source (server, uid);
441         g_return_if_fail (parent_source != NULL);
442
443         if (e_source_equal (collection_source, parent_source))
444                 g_signal_emit (backend, signals[CHILD_ADDED], 0, source);
445
446         g_object_unref (parent_source);
447 }
448
449 static void
450 collection_backend_source_removed_cb (ESourceRegistryServer *server,
451                                       ESource *source,
452                                       ECollectionBackend *backend)
453 {
454         ESource *collection_source;
455         ESource *parent_source;
456         const gchar *uid;
457
458         /* If the removed source was our own child, emit "child-removed".
459          * Note that the source is already unlinked from the GNode tree. */
460
461         collection_source = e_backend_get_source (E_BACKEND (backend));
462
463         uid = e_source_get_parent (source);
464         if (uid == NULL)
465                 return;
466
467         parent_source = e_source_registry_server_ref_source (server, uid);
468         g_return_if_fail (parent_source != NULL);
469
470         if (e_source_equal (collection_source, parent_source))
471                 g_signal_emit (backend, signals[CHILD_REMOVED], 0, source);
472
473         g_object_unref (parent_source);
474 }
475
476 static gboolean
477 collection_backend_populate_idle_cb (gpointer user_data)
478 {
479         ECollectionBackend *backend;
480         ECollectionBackendClass *class;
481
482         backend = E_COLLECTION_BACKEND (user_data);
483
484         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
485         g_return_val_if_fail (class->populate != NULL, FALSE);
486
487         class->populate (backend);
488
489         return FALSE;
490 }
491
492 static void
493 collection_backend_set_server (ECollectionBackend *backend,
494                                ESourceRegistryServer *server)
495 {
496         g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
497
498         g_weak_ref_set (&backend->priv->server, server);
499 }
500
501 static void
502 collection_backend_set_property (GObject *object,
503                                  guint property_id,
504                                  const GValue *value,
505                                  GParamSpec *pspec)
506 {
507         switch (property_id) {
508                 case PROP_SERVER:
509                         collection_backend_set_server (
510                                 E_COLLECTION_BACKEND (object),
511                                 g_value_get_object (value));
512                         return;
513         }
514
515         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
516 }
517
518 static void
519 collection_backend_get_property (GObject *object,
520                                  guint property_id,
521                                  GValue *value,
522                                  GParamSpec *pspec)
523 {
524         switch (property_id) {
525                 case PROP_SERVER:
526                         g_value_take_object (
527                                 value,
528                                 e_collection_backend_ref_server (
529                                 E_COLLECTION_BACKEND (object)));
530                         return;
531         }
532
533         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
534 }
535
536 static void
537 collection_backend_dispose (GObject *object)
538 {
539         ECollectionBackendPrivate *priv;
540         ESourceRegistryServer *server;
541
542         priv = E_COLLECTION_BACKEND_GET_PRIVATE (object);
543
544         server = g_weak_ref_get (&priv->server);
545         if (server != NULL) {
546                 g_signal_handler_disconnect (
547                         server, priv->source_added_handler_id);
548                 g_signal_handler_disconnect (
549                         server, priv->source_removed_handler_id);
550                 g_weak_ref_set (&priv->server, NULL);
551                 g_object_unref (server);
552         }
553
554         g_mutex_lock (&priv->children_lock);
555         g_hash_table_remove_all (priv->children);
556         g_mutex_unlock (&priv->children_lock);
557
558         g_mutex_lock (&priv->unclaimed_resources_lock);
559         g_hash_table_remove_all (priv->unclaimed_resources);
560         g_mutex_unlock (&priv->unclaimed_resources_lock);
561
562         /* Chain up to parent's dispose() method. */
563         G_OBJECT_CLASS (e_collection_backend_parent_class)->dispose (object);
564 }
565
566 static void
567 collection_backend_finalize (GObject *object)
568 {
569         ECollectionBackendPrivate *priv;
570
571         priv = E_COLLECTION_BACKEND_GET_PRIVATE (object);
572
573         g_hash_table_destroy (priv->children);
574         g_mutex_clear (&priv->children_lock);
575
576         g_hash_table_destroy (priv->unclaimed_resources);
577         g_mutex_clear (&priv->unclaimed_resources_lock);
578
579         /* Chain up to parent's finalize() method. */
580         G_OBJECT_CLASS (e_collection_backend_parent_class)->finalize (object);
581 }
582
583 static void
584 collection_backend_constructed (GObject *object)
585 {
586         ECollectionBackend *backend;
587         ESourceRegistryServer *server;
588         ESource *source;
589         GNode *node;
590         const gchar *collection_uid;
591         const gchar *user_cache_dir;
592         gulong handler_id;
593
594         backend = E_COLLECTION_BACKEND (object);
595
596         /* Chain up to parent's constructed() method. */
597         G_OBJECT_CLASS (e_collection_backend_parent_class)->
598                 constructed (object);
599
600         source = e_backend_get_source (E_BACKEND (backend));
601
602         /* Determine the backend's cache directory. */
603
604         user_cache_dir = e_get_user_cache_dir ();
605         collection_uid = e_source_get_uid (source);
606         backend->priv->cache_dir = g_build_filename (
607                 user_cache_dir, "sources", collection_uid, NULL);
608         g_mkdir_with_parents (backend->priv->cache_dir, 0700);
609
610         /* This requires the cache directory to be set. */
611         collection_backend_load_resources (backend);
612
613         /* Emit "child-added" signals for the children we already have. */
614
615         node = e_server_side_source_get_node (E_SERVER_SIDE_SOURCE (source));
616         node = g_node_first_child (node);
617
618         while (node != NULL) {
619                 ESource *child = E_SOURCE (node->data);
620                 g_signal_emit (backend, signals[CHILD_ADDED], 0, child);
621                 node = g_node_next_sibling (node);
622         }
623
624         /* Listen for "source-added" and "source-removed" signals
625          * from the server, which may trigger our own "child-added"
626          * and "child-removed" signals. */
627
628         server = e_collection_backend_ref_server (backend);
629
630         handler_id = g_signal_connect (
631                 server, "source-added",
632                 G_CALLBACK (collection_backend_source_added_cb), backend);
633
634         backend->priv->source_added_handler_id = handler_id;
635
636         handler_id = g_signal_connect (
637                 server, "source-removed",
638                 G_CALLBACK (collection_backend_source_removed_cb), backend);
639
640         backend->priv->source_removed_handler_id = handler_id;
641
642         g_object_unref (server);
643
644         /* Populate the newly-added collection from an idle callback
645          * so persistent child sources have a chance to be added first. */
646
647         g_idle_add_full (
648                 G_PRIORITY_LOW,
649                 collection_backend_populate_idle_cb,
650                 g_object_ref (backend),
651                 (GDestroyNotify) g_object_unref);
652 }
653
654 static gboolean
655 collection_backend_authenticate_sync (EBackend *backend,
656                                       ESourceAuthenticator *authenticator,
657                                       GCancellable *cancellable,
658                                       GError **error)
659 {
660         ECollectionBackend *collection_backend;
661         ESourceRegistryServer *server;
662         EAuthenticationSession *session;
663         ESource *source;
664         const gchar *source_uid;
665         gboolean success;
666
667         source = e_backend_get_source (backend);
668         source_uid = e_source_get_uid (source);
669
670         collection_backend = E_COLLECTION_BACKEND (backend);
671         server = e_collection_backend_ref_server (collection_backend);
672         session = e_source_registry_server_new_auth_session (
673                 server, authenticator, source_uid);
674
675         success = e_source_registry_server_authenticate_sync (
676                 server, session, cancellable, error);
677
678         g_object_unref (session);
679         g_object_unref (server);
680
681         return success;
682 }
683
684 static void
685 collection_backend_populate (ECollectionBackend *backend)
686 {
687         /* Placeholder so subclasses can safely chain up. */
688 }
689
690 static gchar *
691 collection_backend_dup_resource_id (ECollectionBackend *backend,
692                                     ESource *source)
693 {
694         const gchar *extension_name;
695         gchar *resource_id = NULL;
696
697         extension_name = E_SOURCE_EXTENSION_RESOURCE;
698
699         if (e_source_has_extension (source, extension_name)) {
700                 ESourceResource *extension;
701
702                 extension = e_source_get_extension (source, extension_name);
703                 resource_id = e_source_resource_dup_identity (extension);
704         }
705
706         return resource_id;
707 }
708
709 static void
710 collection_backend_child_added (ECollectionBackend *backend,
711                                 ESource *child_source)
712 {
713         ESource *collection_source;
714
715         collection_backend_children_insert (backend, child_source);
716         collection_backend_bind_child_enabled (backend, child_source);
717
718         collection_source = e_backend_get_source (E_BACKEND (backend));
719
720         /* Collection children are not removable. */
721         e_server_side_source_set_removable (
722                 E_SERVER_SIDE_SOURCE (child_source), FALSE);
723
724         /* Collection children inherit the authentication session type. */
725         g_object_bind_property (
726                 collection_source, "auth-session-type",
727                 child_source, "auth-session-type",
728                 G_BINDING_SYNC_CREATE);
729
730         /* Collection children inherit OAuth 2.0 support if available. */
731         g_object_bind_property (
732                 collection_source, "oauth2-support",
733                 child_source, "oauth2-support",
734                 G_BINDING_SYNC_CREATE);
735 }
736
737 static void
738 collection_backend_child_removed (ECollectionBackend *backend,
739                                   ESource *child_source)
740 {
741         collection_backend_children_remove (backend, child_source);
742 }
743
744 static gboolean
745 collection_backend_create_resource_sync (ECollectionBackend *backend,
746                                          ESource *source,
747                                          GCancellable *cancellable,
748                                          GError **error)
749 {
750         EAsyncClosure *closure;
751         GAsyncResult *result;
752         gboolean success;
753
754         closure = e_async_closure_new ();
755
756         e_collection_backend_create_resource (
757                 backend, source, cancellable,
758                 e_async_closure_callback, closure);
759
760         result = e_async_closure_wait (closure);
761
762         success = e_collection_backend_create_resource_finish (
763                 backend, result, error);
764
765         e_async_closure_free (closure);
766
767         return success;
768 }
769
770 static void
771 collection_backend_create_resource (ECollectionBackend *backend,
772                                     ESource *source,
773                                     GCancellable *cancellable,
774                                     GAsyncReadyCallback callback,
775                                     gpointer user_data)
776 {
777         GSimpleAsyncResult *simple;
778
779         simple = g_simple_async_result_new_error (
780                 G_OBJECT (backend), callback, user_data,
781                 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
782                 _("%s does not support creating remote resources"),
783                 G_OBJECT_TYPE_NAME (backend));
784
785         g_simple_async_result_complete_in_idle (simple);
786
787         g_object_unref (simple);
788 }
789
790 static gboolean
791 collection_backend_create_resource_finish (ECollectionBackend *backend,
792                                            GAsyncResult *result,
793                                            GError **error)
794 {
795         GSimpleAsyncResult *simple;
796
797         simple = G_SIMPLE_ASYNC_RESULT (result);
798
799         /* Assume success unless a GError is set. */
800         return !g_simple_async_result_propagate_error (simple, error);
801 }
802
803 static gboolean
804 collection_backend_delete_resource_sync (ECollectionBackend *backend,
805                                          ESource *source,
806                                          GCancellable *cancellable,
807                                          GError **error)
808 {
809         EAsyncClosure *closure;
810         GAsyncResult *result;
811         gboolean success;
812
813         closure = e_async_closure_new ();
814
815         e_collection_backend_delete_resource (
816                 backend, source, cancellable,
817                 e_async_closure_callback, closure);
818
819         result = e_async_closure_wait (closure);
820
821         success = e_collection_backend_delete_resource_finish (
822                 backend, result, error);
823
824         e_async_closure_free (closure);
825
826         return success;
827 }
828
829 static void
830 collection_backend_delete_resource (ECollectionBackend *backend,
831                                     ESource *source,
832                                     GCancellable *cancellable,
833                                     GAsyncReadyCallback callback,
834                                     gpointer user_data)
835 {
836         GSimpleAsyncResult *simple;
837
838         simple = g_simple_async_result_new_error (
839                 G_OBJECT (backend), callback, user_data,
840                 G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
841                 _("%s does not support deleting remote resources"),
842                 G_OBJECT_TYPE_NAME (backend));
843
844         g_simple_async_result_complete_in_idle (simple);
845
846         g_object_unref (simple);
847 }
848
849 static gboolean
850 collection_backend_delete_resource_finish (ECollectionBackend *backend,
851                                            GAsyncResult *result,
852                                            GError **error)
853 {
854         GSimpleAsyncResult *simple;
855
856         simple = G_SIMPLE_ASYNC_RESULT (result);
857
858         /* Assume success unless a GError is set. */
859         return !g_simple_async_result_propagate_error (simple, error);
860 }
861
862 static void
863 e_collection_backend_class_init (ECollectionBackendClass *class)
864 {
865         GObjectClass *object_class;
866         EBackendClass *backend_class;
867
868         g_type_class_add_private (class, sizeof (ECollectionBackendPrivate));
869
870         object_class = G_OBJECT_CLASS (class);
871         object_class->set_property = collection_backend_set_property;
872         object_class->get_property = collection_backend_get_property;
873         object_class->dispose = collection_backend_dispose;
874         object_class->finalize = collection_backend_finalize;
875         object_class->constructed = collection_backend_constructed;
876
877         backend_class = E_BACKEND_CLASS (class);
878         backend_class->authenticate_sync = collection_backend_authenticate_sync;
879
880         class->populate = collection_backend_populate;
881         class->dup_resource_id = collection_backend_dup_resource_id;
882         class->child_added = collection_backend_child_added;
883         class->child_removed = collection_backend_child_removed;
884         class->create_resource_sync = collection_backend_create_resource_sync;
885         class->create_resource = collection_backend_create_resource;
886         class->create_resource_finish = collection_backend_create_resource_finish;
887         class->delete_resource_sync = collection_backend_delete_resource_sync;
888         class->delete_resource = collection_backend_delete_resource;
889         class->delete_resource_finish = collection_backend_delete_resource_finish;
890
891         g_object_class_install_property (
892                 object_class,
893                 PROP_SERVER,
894                 g_param_spec_object (
895                         "server",
896                         "Server",
897                         "The server to which the backend belongs",
898                         E_TYPE_SOURCE_REGISTRY_SERVER,
899                         G_PARAM_READWRITE |
900                         G_PARAM_CONSTRUCT_ONLY |
901                         G_PARAM_STATIC_STRINGS));
902
903         /**
904          * ECollectionBackend::child-added:
905          * @backend: the #ECollectionBackend which emitted the signal
906          * @child_source: the newly-added child #EServerSideSource
907          *
908          * Emitted when an #EServerSideSource is added to @backend's
909          * #ECollectionBackend:server as a child of @backend's collection
910          * #EBackend:source.
911          *
912          * You can think of this as a filtered version of
913          * #ESourceRegistryServer's #ESourceRegistryServer::source-added
914          * signal which only lets through sources relevant to @backend.
915          **/
916         signals[CHILD_ADDED] = g_signal_new (
917                 "child-added",
918                 G_OBJECT_CLASS_TYPE (object_class),
919                 G_SIGNAL_RUN_LAST,
920                 G_STRUCT_OFFSET (ECollectionBackendClass, child_added),
921                 NULL, NULL,
922                 g_cclosure_marshal_VOID__OBJECT,
923                 G_TYPE_NONE, 1,
924                 E_TYPE_SERVER_SIDE_SOURCE);
925
926         /**
927          * ECollectionBackend::child-removed:
928          * @backend: the #ECollectionBackend which emitted the signal
929          * @child_source: the child #EServerSideSource that got removed
930          *
931          * Emitted when an #EServerSideSource that is a child of
932          * @backend's collection #EBackend:source is removed from
933          * @backend's #ECollectionBackend:server.
934          *
935          * You can think of this as a filtered version of
936          * #ESourceRegistryServer's #ESourceRegistryServer::source-removed
937          * signal which only lets through sources relevant to @backend.
938          **/
939         signals[CHILD_REMOVED] = g_signal_new (
940                 "child-removed",
941                 G_OBJECT_CLASS_TYPE (object_class),
942                 G_SIGNAL_RUN_LAST,
943                 G_STRUCT_OFFSET (ECollectionBackendClass, child_removed),
944                 NULL, NULL,
945                 g_cclosure_marshal_VOID__OBJECT,
946                 G_TYPE_NONE, 1,
947                 E_TYPE_SERVER_SIDE_SOURCE);
948 }
949
950 static void
951 e_collection_backend_init (ECollectionBackend *backend)
952 {
953         GHashTable *children;
954         GHashTable *unclaimed_resources;
955
956         children = g_hash_table_new_full (
957                 (GHashFunc) e_source_hash,
958                 (GEqualFunc) e_source_equal,
959                 (GDestroyNotify) g_object_unref,
960                 (GDestroyNotify) NULL);
961
962         unclaimed_resources = g_hash_table_new_full (
963                 (GHashFunc) g_str_hash,
964                 (GEqualFunc) g_str_equal,
965                 (GDestroyNotify) g_free,
966                 (GDestroyNotify) g_object_unref);
967
968         backend->priv = E_COLLECTION_BACKEND_GET_PRIVATE (backend);
969         backend->priv->children = children;
970         g_mutex_init (&backend->priv->children_lock);
971         backend->priv->unclaimed_resources = unclaimed_resources;
972         g_mutex_init (&backend->priv->unclaimed_resources_lock);
973 }
974
975 /**
976  * e_collection_backend_new_child:
977  * @backend: an #ECollectionBackend
978  * @resource_id: a stable and unique resource ID
979  *
980  * Creates a new #EServerSideSource as a child of the collection
981  * #EBackend:source owned by @backend.  If possible, the #EServerSideSource
982  * is drawn from a cache of previously used sources indexed by @resource_id
983  * so that locally cached data from previous sessions can be reused.
984  *
985  * The returned data source should be passed to
986  * e_source_registry_server_add_source() to export it over D-Bus.
987  *
988  * Return: a newly-created data source
989  *
990  * Since: 3.6
991  **/
992 ESource *
993 e_collection_backend_new_child (ECollectionBackend *backend,
994                                 const gchar *resource_id)
995 {
996         ESource *collection_source;
997         ESource *child_source;
998         GError *error = NULL;
999
1000         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1001         g_return_val_if_fail (resource_id != NULL, NULL);
1002
1003         /* This being a newly-created or existing data source, claiming
1004          * it should never fail but we'll check for errors just the same.
1005          * It's unlikely enough that we don't need a GError parameter. */
1006         child_source = collection_backend_claim_resource (
1007                 backend, resource_id, &error);
1008
1009         if (error != NULL) {
1010                 g_warn_if_fail (child_source == NULL);
1011                 g_warning ("%s: %s", G_STRFUNC, error->message);
1012                 g_error_free (error);
1013                 return NULL;
1014         }
1015
1016         collection_source = e_backend_get_source (E_BACKEND (backend));
1017
1018         g_print (
1019                 "%s: Pairing %s with resource %s\n",
1020                 e_source_get_display_name (collection_source),
1021                 e_source_get_uid (child_source), resource_id);
1022
1023         return child_source;
1024 }
1025
1026 /**
1027  * e_collection_backend_ref_server:
1028  * @backend: an #ECollectionBackend
1029  *
1030  * Returns the #ESourceRegistryServer to which @backend belongs.
1031  *
1032  * The returned #ESourceRegistryServer is referenced for thread-safety.
1033  * Unreference the #ESourceRegistryServer with g_object_unref() when
1034  * finished with it.
1035  *
1036  * Returns: the #ESourceRegisterServer for @backend
1037  *
1038  * Since: 3.6
1039  **/
1040 ESourceRegistryServer *
1041 e_collection_backend_ref_server (ECollectionBackend *backend)
1042 {
1043         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1044
1045         return g_weak_ref_get (&backend->priv->server);
1046 }
1047
1048 /**
1049  * e_collection_backend_get_cache_dir:
1050  * @backend: an #ECollectionBackend
1051  *
1052  * Returns the private cache directory path for @backend, which is named
1053  * after the #ESource:uid of @backend's collection #EBackend:source.
1054  *
1055  * The cache directory is meant to store key files for backend-created
1056  * data sources.  See also: e_server_side_source_set_write_directory()
1057  *
1058  * Returns: the cache directory for @backend
1059  *
1060  * Since: 3.6
1061  **/
1062 const gchar *
1063 e_collection_backend_get_cache_dir (ECollectionBackend *backend)
1064 {
1065         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1066
1067         return backend->priv->cache_dir;
1068 }
1069
1070 /**
1071  * e_collection_backend_dup_resource_id:
1072  * @backend: an #ECollectionBackend
1073  * @child_source: an #ESource managed by @backend
1074  *
1075  * Extracts the resource ID for @child_source, which is supposed to be a
1076  * stable and unique server-assigned identifier for the remote resource
1077  * described by @child_source.  If @child_source is not actually a child
1078  * of the collection #EBackend:source owned by @backend, the function
1079  * returns %NULL.
1080  *
1081  * The returned string should be freed with g_free() when no longer needed.
1082  *
1083  * Returns: a newly-allocated resource ID for @child_source, or %NULL
1084  *
1085  * Since: 3.6
1086  **/
1087 gchar *
1088 e_collection_backend_dup_resource_id (ECollectionBackend *backend,
1089                                       ESource *child_source)
1090 {
1091         ECollectionBackend *backend_for_child_source;
1092         ECollectionBackendClass *class;
1093         ESourceRegistryServer *server;
1094         gboolean child_is_ours = FALSE;
1095
1096         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1097         g_return_val_if_fail (E_IS_SOURCE (child_source), NULL);
1098
1099         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1100         g_return_val_if_fail (class->dup_resource_id != NULL, NULL);
1101
1102         /* Make sure the ESource belongs to the ECollectionBackend to
1103          * avoid accidentally creating a new extension while trying to
1104          * extract a resource ID that isn't there.  Better to test this
1105          * up front than rely on ECollectionBackend subclasses to do it. */
1106         server = e_collection_backend_ref_server (backend);
1107         backend_for_child_source =
1108                 e_source_registry_server_ref_backend (server, child_source);
1109         g_object_unref (server);
1110
1111         if (backend_for_child_source != NULL) {
1112                 child_is_ours = (backend_for_child_source == backend);
1113                 g_object_unref (backend_for_child_source);
1114         }
1115
1116         if (!child_is_ours)
1117                 return NULL;
1118
1119         return class->dup_resource_id (backend, child_source);
1120 }
1121
1122 /**
1123  * e_collection_backend_claim_all_resources:
1124  * @backend: an #ECollectionBackend
1125  *
1126  * Claims all previously used sources that have not yet been claimed by
1127  * e_collection_backend_new_child() and returns them in a #GList.  Note
1128  * that previously used sources can only be claimed once, so subsequent
1129  * calls to this function for @backend will return %NULL.
1130  *
1131  * The @backend is then expected to compare the returned list with a
1132  * current list of resources from a remote server, create new #ESource
1133  * instances as needed with e_collection_backend_new_child(), discard
1134  * unneeded #ESource instances with e_source_remove(), and export the
1135  * remaining instances with e_source_registry_server_add_source().
1136  *
1137  * The sources returned in the list are referenced for thread-safety.
1138  * They must each be unreferenced with g_object_unref() when finished
1139  * with them.  Free the returned #GList itself with g_list_free().
1140  *
1141  * An easy way to free the list properly in one step is as follows:
1142  *
1143  * |[
1144  *   g_list_free_full (list, g_object_unref);
1145  * ]|
1146  *
1147  * Returns: a list of previously used sources
1148  *
1149  * Since: 3.6
1150  **/
1151 GList *
1152 e_collection_backend_claim_all_resources (ECollectionBackend *backend)
1153 {
1154         GHashTable *unclaimed_resources;
1155         GList *resources;
1156
1157         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1158
1159         g_mutex_lock (&backend->priv->unclaimed_resources_lock);
1160
1161         unclaimed_resources = backend->priv->unclaimed_resources;
1162         resources = g_hash_table_get_values (unclaimed_resources);
1163         g_list_foreach (resources, (GFunc) g_object_ref, NULL);
1164         g_hash_table_remove_all (unclaimed_resources);
1165
1166         g_mutex_unlock (&backend->priv->unclaimed_resources_lock);
1167
1168         return resources;
1169 }
1170
1171 /**
1172  * e_collection_backend_list_calendar_sources:
1173  * @backend: an #ECollectionBackend
1174  *
1175  * Returns a list of calendar sources belonging to the data source
1176  * collection managed by @backend.
1177  *
1178  * The sources returned in the list are referenced for thread-safety.
1179  * They must each be unreferenced with g_object_unref() when finished
1180  * with them.  Free the returned #GList itself with g_list_free().
1181  *
1182  * An easy way to free the list properly in one step is as follows:
1183  *
1184  * |[
1185  *   g_list_free_full (list, g_object_unref);
1186  * ]|
1187  *
1188  * Returns: a list of calendar sources
1189  *
1190  * Since: 3.6
1191  **/
1192 GList *
1193 e_collection_backend_list_calendar_sources (ECollectionBackend *backend)
1194 {
1195         GList *result_list = NULL;
1196         GList *list, *link;
1197
1198         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1199
1200         list = collection_backend_children_list (backend);
1201
1202         for (link = list; link != NULL; link = g_list_next (link)) {
1203                 ESource *child_source = E_SOURCE (link->data);
1204                 if (collection_backend_child_is_calendar (child_source))
1205                         result_list = g_list_prepend (
1206                                 result_list, g_object_ref (child_source));
1207         }
1208
1209         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1210
1211         return g_list_reverse (result_list);
1212 }
1213
1214 /**
1215  * e_collection_backend_list_contacts_sources:
1216  * @backend: an #ECollectionBackend
1217  *
1218  * Returns a list of address book sources belonging to the data source
1219  * collection managed by @backend.
1220  *
1221  * The sources returned in the list are referenced for thread-safety.
1222  * They must each be unreferenced with g_object_unref() when finished
1223  * with them.  Free the returned #GList itself with g_list_free().
1224  *
1225  * An easy way to free the list properly in one step is as follows:
1226  *
1227  * |[
1228  *   g_list_free_full (list, g_object_unref);
1229  * ]|
1230  *
1231  * Returns: a list of address book sources
1232  *
1233  * Since: 3.6
1234  **/
1235 GList *
1236 e_collection_backend_list_contacts_sources (ECollectionBackend *backend)
1237 {
1238         GList *result_list = NULL;
1239         GList *list, *link;
1240
1241         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1242
1243         list = collection_backend_children_list (backend);
1244
1245         for (link = list; link != NULL; link = g_list_next (link)) {
1246                 ESource *child_source = E_SOURCE (link->data);
1247                 if (collection_backend_child_is_contacts (child_source))
1248                         result_list = g_list_prepend (
1249                                 result_list, g_object_ref (child_source));
1250         }
1251
1252         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1253
1254         return g_list_reverse (result_list);
1255 }
1256
1257 /**
1258  * e_collection_backend_list_mail_sources:
1259  * @backend: an #ECollectionBackend
1260  *
1261  * Returns a list of mail sources belonging to the data source collection
1262  * managed by @backend.
1263  *
1264  * The sources returned in the list are referenced for thread-safety.
1265  * They must each be unreferenced with g_object_unref() when finished
1266  * with them.  Free the returned #GList itself with g_list_free().
1267  *
1268  * An easy way to free the list properly in one step is as follows:
1269  *
1270  * |[
1271  *   g_list_free_full (list, g_object_unref);
1272  * ]|
1273  *
1274  * Returns: a list of mail sources
1275  *
1276  * Since: 3.6
1277  **/
1278 GList *
1279 e_collection_backend_list_mail_sources (ECollectionBackend *backend)
1280 {
1281         GList *result_list = NULL;
1282         GList *list, *link;
1283
1284         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), NULL);
1285
1286         list = collection_backend_children_list (backend);
1287
1288         for (link = list; link != NULL; link = g_list_next (link)) {
1289                 ESource *child_source = E_SOURCE (link->data);
1290                 if (collection_backend_child_is_mail (child_source))
1291                         result_list = g_list_prepend (
1292                                 result_list, g_object_ref (child_source));
1293         }
1294
1295         g_list_free_full (list, (GDestroyNotify) g_object_unref);
1296
1297         return g_list_reverse (result_list);
1298 }
1299
1300 /**
1301  * e_collection_backend_create_resource_sync
1302  * @backend: an #ECollectionBackend
1303  * @source: an #ESource
1304  * @cancellable: optional #GCancellable object, or %NULL
1305  * @error: return location for a #GError, or %NULL
1306  *
1307  * Creates a server-side resource described by @source.  For example, if
1308  * @source describes a new calendar, an equivalent calendar is created on
1309  * the server.
1310  *
1311  * It is the implementor's responsibility to examine @source and determine
1312  * what the equivalent server-side resource would be.  If this cannot be
1313  * determined without ambiguity, the function must return an error.
1314  *
1315  * After the server-side resource is successfully created, the implementor
1316  * must also add an #ESource to @backend's #ECollectionBackend:server.  This
1317  * can either be done immediately or in response to some "resource created"
1318  * notification from the server.  The added #ESource can be @source itself
1319  * or a different #ESource instance that describes the new resource.
1320  *
1321  * If an error occurs, the function will set @error and return %FALSE.
1322  *
1323  * Returns: %TRUE on success, %FALSE on failure
1324  *
1325  * Since: 3.6
1326  **/
1327 gboolean
1328 e_collection_backend_create_resource_sync (ECollectionBackend *backend,
1329                                            ESource *source,
1330                                            GCancellable *cancellable,
1331                                            GError **error)
1332 {
1333         ECollectionBackendClass *class;
1334
1335         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1336         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1337
1338         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1339         g_return_val_if_fail (class->create_resource_sync != NULL, FALSE);
1340
1341         return class->create_resource_sync (
1342                 backend, source, cancellable, error);
1343 }
1344
1345 /**
1346  * e_collection_backend_create_resource:
1347  * @backend: an #ECollectionBackend
1348  * @source: an #ESource
1349  * @cancellable: optional #GCancellable object, or %NULL
1350  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1351  * @user_data: data to pass to the callback function
1352  *
1353  * Asynchronously creates a server-side resource described by @source.
1354  * For example, if @source describes a new calendar, an equivalent calendar
1355  * is created on the server.
1356  *
1357  * It is the implementor's responsibility to examine @source and determine
1358  * what the equivalent server-side resource would be.  If this cannot be
1359  * determined without ambiguity, the function must return an error.
1360  *
1361  * After the server-side resource is successfully created, the implementor
1362  * must also add an #ESource to @backend's #ECollectionBackend:server.  This
1363  * can either be done immediately or in response to some "resource created"
1364  * notification from the server.  The added #ESource can be @source itself
1365  * or a different #ESource instance that describes the new resource.
1366  *
1367  * When the operation is finished, @callback will be called.  You can then
1368  * call e_collection_backend_create_resource_finish() to get the result of
1369  * the operation.
1370  *
1371  * Since: 3.6
1372  **/
1373 void
1374 e_collection_backend_create_resource (ECollectionBackend *backend,
1375                                       ESource *source,
1376                                       GCancellable *cancellable,
1377                                       GAsyncReadyCallback callback,
1378                                       gpointer user_data)
1379 {
1380         ECollectionBackendClass *class;
1381
1382         g_return_if_fail (E_IS_COLLECTION_BACKEND (backend));
1383         g_return_if_fail (E_IS_SOURCE (source));
1384
1385         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1386         g_return_if_fail (class->create_resource != NULL);
1387
1388         class->create_resource (
1389                 backend, source, cancellable, callback, user_data);
1390 }
1391
1392 /**
1393  * e_collection_backend_create_resource_finish:
1394  * @backend: an #ECollectionBackend
1395  * @result: a #GAsyncResult
1396  * @error: return location for a #GError, or %NULL
1397  *
1398  * Finishes the operation started with e_collection_backend_create_resource().
1399  *
1400  * If an error occurred, the function will set @error and return %FALSE.
1401  *
1402  * Returns: %TRUE on success, %FALSE on failure
1403  *
1404  * Since: 3.6
1405  **/
1406 gboolean
1407 e_collection_backend_create_resource_finish (ECollectionBackend *backend,
1408                                              GAsyncResult *result,
1409                                              GError **error)
1410 {
1411         ECollectionBackendClass *class;
1412
1413         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1414         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
1415
1416         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1417         g_return_val_if_fail (class->create_resource_finish != NULL, FALSE);
1418
1419         return class->create_resource_finish (backend, result, error);
1420 }
1421
1422 /**
1423  * e_collection_backend_delete_resource_sync:
1424  * @backend: an #ECollectionBackend
1425  * @source: an #ESource
1426  * @cancellable: optional #GCancellable object, or %NULL
1427  * @error: return location for a #GError, or %NULL
1428  *
1429  * Deletes a server-side resource described by @source.  The @source must
1430  * be a child of @backend's collection #EBackend:source.
1431  *
1432  * After the server-side resource is successfully deleted, the implementor
1433  * must also remove @source from the @backend's #ECollectionBackend:server.
1434  * This can either be done immediately or in response to some "resource
1435  * deleted" notification from the server.
1436  *
1437  * If an error occurs, the function will set @error and return %FALSE.
1438  *
1439  * Returns: %TRUE on success, %FALSE on failure
1440  *
1441  * Since: 3.6
1442  **/
1443 gboolean
1444 e_collection_backend_delete_resource_sync (ECollectionBackend *backend,
1445                                            ESource *source,
1446                                            GCancellable *cancellable,
1447                                            GError **error)
1448 {
1449         ECollectionBackendClass *class;
1450
1451         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1452         g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
1453
1454         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1455         g_return_val_if_fail (class->delete_resource_sync != NULL, FALSE);
1456
1457         return class->delete_resource_sync (
1458                 backend, source, cancellable, error);
1459 }
1460
1461 /**
1462  * e_collection_backend_delete_resource:
1463  * @backend: an #ECollectionBackend
1464  * @source: an #ESource
1465  * @cancellable: optional #GCancellable object, or %NULL
1466  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1467  * @user_data: data to pass to the callback function
1468  *
1469  * Asynchronously deletes a server-side resource described by @source.
1470  * The @source must be a child of @backend's collection #EBackend:source.
1471  *
1472  * After the server-side resource is successfully deleted, the implementor
1473  * must also remove @source from the @backend's #ECollectionBackend:server.
1474  * This can either be done immediately or in response to some "resource
1475  * deleted" notification from the server.
1476  *
1477  * When the operation is finished, @callback will be called.  You can then
1478  * call e_collection_backend_delete_resource_finish() to get the result of
1479  * the operation.
1480  *
1481  * Since: 3.6
1482  **/
1483 void
1484 e_collection_backend_delete_resource (ECollectionBackend *backend,
1485                                       ESource *source,
1486                                       GCancellable *cancellable,
1487                                       GAsyncReadyCallback callback,
1488                                       gpointer user_data)
1489 {
1490         ECollectionBackendClass *class;
1491
1492         g_return_if_fail (E_IS_COLLECTION_BACKEND (backend));
1493         g_return_if_fail (E_IS_SOURCE (source));
1494
1495         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1496         g_return_if_fail (class->delete_resource != NULL);
1497
1498         return class->delete_resource (
1499                 backend, source, cancellable, callback, user_data);
1500 }
1501
1502 /**
1503  * e_collection_backend_delete_resource_finish:
1504  * @backend: an #ECollectionBackend
1505  * @result: a #GAsyncResult
1506  * @error: return location for a #GError, or %NULL
1507  *
1508  * Finishes the operation started with e_collection_backend_delete_resource().
1509  *
1510  * If an error occurred, the function will set @error and return %FALSE.
1511  *
1512  * Returns: %TRUE on success, %FALSE on failure
1513  *
1514  * Since: 3.6
1515  **/
1516 gboolean
1517 e_collection_backend_delete_resource_finish (ECollectionBackend *backend,
1518                                              GAsyncResult *result,
1519                                              GError **error)
1520 {
1521         ECollectionBackendClass *class;
1522
1523         g_return_val_if_fail (E_IS_COLLECTION_BACKEND (backend), FALSE);
1524         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
1525
1526         class = E_COLLECTION_BACKEND_GET_CLASS (backend);
1527         g_return_val_if_fail (class->delete_resource_finish != NULL, FALSE);
1528
1529         return class->delete_resource_finish (backend, result, error);
1530 }
1531