ESourceRegistry: Do not mandate builtin sources.
[platform/upstream/evolution-data-server.git] / libedataserver / e-source-registry.c
index 01237b1..5d2c553 100644 (file)
@@ -89,6 +89,7 @@
 
 typedef struct _AsyncContext AsyncContext;
 typedef struct _AuthContext AuthContext;
+typedef struct _CreateContext CreateContext;
 typedef struct _SourceClosure SourceClosure;
 typedef struct _ThreadClosure ThreadClosure;
 
@@ -102,10 +103,10 @@ struct _ESourceRegistryPrivate {
        EDBusSourceManager *dbus_source_manager;
 
        GHashTable *object_path_table;
-       GMutex *object_path_table_lock;
+       GMutex object_path_table_lock;
 
        GHashTable *sources;
-       GMutex *sources_lock;
+       GMutex sources_lock;
 
        GSettings *settings;
 };
@@ -129,6 +130,13 @@ struct _AuthContext {
        GError **error;
 };
 
+/* Used in e_source_registry_create_sources_sync() */
+struct _CreateContext {
+       GHashTable *pending_uids;
+       GMainContext *main_context;
+       GMainLoop *main_loop;
+};
+
 struct _SourceClosure {
        ESourceRegistry *registry;
        ESource *source;
@@ -138,8 +146,9 @@ struct _ThreadClosure {
        ESourceRegistry *registry;
        GMainContext *main_context;
        GMainLoop *main_loop;
-       GCond *main_loop_cond;
-       GMutex *main_loop_mutex;
+       GCond main_loop_cond;
+       GMutex main_loop_mutex;
+       GError *error;
 };
 
 enum {
@@ -216,6 +225,37 @@ auth_context_free (AuthContext *auth_context)
        g_slice_free (AuthContext, auth_context);
 }
 
+static CreateContext *
+create_context_new (void)
+{
+       CreateContext *create_context;
+
+       create_context = g_slice_new0 (CreateContext);
+
+       create_context->pending_uids = g_hash_table_new_full (
+               (GHashFunc) g_str_hash,
+               (GEqualFunc) g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) NULL);
+
+       create_context->main_context = g_main_context_new ();
+
+       create_context->main_loop = g_main_loop_new (
+               create_context->main_context, FALSE);
+
+       return create_context;
+}
+
+static void
+create_context_free (CreateContext *create_context)
+{
+       g_main_loop_unref (create_context->main_loop);
+       g_main_context_unref (create_context->main_context);
+       g_hash_table_unref (create_context->pending_uids);
+
+       g_slice_free (CreateContext, create_context);
+}
+
 static void
 source_closure_free (SourceClosure *closure)
 {
@@ -232,8 +272,12 @@ thread_closure_free (ThreadClosure *closure)
 
        g_main_context_unref (closure->main_context);
        g_main_loop_unref (closure->main_loop);
-       g_cond_free (closure->main_loop_cond);
-       g_mutex_free (closure->main_loop_mutex);
+       g_cond_clear (&closure->main_loop_cond);
+       g_mutex_clear (&closure->main_loop_mutex);
+
+       /* The GError should be NULL at this point,
+        * regardless of whether an error occurred. */
+       g_warn_if_fail (closure->error == NULL);
 
        g_slice_free (ThreadClosure, closure);
 }
@@ -246,14 +290,14 @@ source_registry_object_path_table_insert (ESourceRegistry *registry,
        g_return_if_fail (object_path != NULL);
        g_return_if_fail (E_IS_SOURCE (source));
 
-       g_mutex_lock (registry->priv->object_path_table_lock);
+       g_mutex_lock (&registry->priv->object_path_table_lock);
 
        g_hash_table_insert (
                registry->priv->object_path_table,
                g_strdup (object_path),
                g_object_ref (source));
 
-       g_mutex_unlock (registry->priv->object_path_table_lock);
+       g_mutex_unlock (&registry->priv->object_path_table_lock);
 }
 
 static ESource *
@@ -264,14 +308,14 @@ source_registry_object_path_table_lookup (ESourceRegistry *registry,
 
        g_return_val_if_fail (object_path != NULL, NULL);
 
-       g_mutex_lock (registry->priv->object_path_table_lock);
+       g_mutex_lock (&registry->priv->object_path_table_lock);
 
        source = g_hash_table_lookup (
                registry->priv->object_path_table, object_path);
        if (source != NULL)
                g_object_ref (source);
 
-       g_mutex_unlock (registry->priv->object_path_table_lock);
+       g_mutex_unlock (&registry->priv->object_path_table_lock);
 
        return source;
 }
@@ -284,12 +328,12 @@ source_registry_object_path_table_remove (ESourceRegistry *registry,
 
        g_return_val_if_fail (object_path != NULL, FALSE);
 
-       g_mutex_lock (registry->priv->object_path_table_lock);
+       g_mutex_lock (&registry->priv->object_path_table_lock);
 
        removed = g_hash_table_remove (
                registry->priv->object_path_table, object_path);
 
-       g_mutex_unlock (registry->priv->object_path_table_lock);
+       g_mutex_unlock (&registry->priv->object_path_table_lock);
 
        return removed;
 }
@@ -303,13 +347,13 @@ source_registry_sources_insert (ESourceRegistry *registry,
        uid = e_source_get_uid (source);
        g_return_if_fail (uid != NULL);
 
-       g_mutex_lock (registry->priv->sources_lock);
+       g_mutex_lock (&registry->priv->sources_lock);
 
        g_hash_table_insert (
                registry->priv->sources,
                g_strdup (uid), g_object_ref (source));
 
-       g_mutex_unlock (registry->priv->sources_lock);
+       g_mutex_unlock (&registry->priv->sources_lock);
 }
 
 static gboolean
@@ -322,11 +366,11 @@ source_registry_sources_remove (ESourceRegistry *registry,
        uid = e_source_get_uid (source);
        g_return_val_if_fail (uid != NULL, FALSE);
 
-       g_mutex_lock (registry->priv->sources_lock);
+       g_mutex_lock (&registry->priv->sources_lock);
 
        removed = g_hash_table_remove (registry->priv->sources, uid);
 
-       g_mutex_unlock (registry->priv->sources_lock);
+       g_mutex_unlock (&registry->priv->sources_lock);
 
        return removed;
 }
@@ -339,14 +383,14 @@ source_registry_sources_lookup (ESourceRegistry *registry,
 
        g_return_val_if_fail (uid != NULL, NULL);
 
-       g_mutex_lock (registry->priv->sources_lock);
+       g_mutex_lock (&registry->priv->sources_lock);
 
        source = g_hash_table_lookup (registry->priv->sources, uid);
 
        if (source != NULL)
                g_object_ref (source);
 
-       g_mutex_unlock (registry->priv->sources_lock);
+       g_mutex_unlock (&registry->priv->sources_lock);
 
        return source;
 }
@@ -356,13 +400,13 @@ source_registry_sources_get_values (ESourceRegistry *registry)
 {
        GList *values;
 
-       g_mutex_lock (registry->priv->sources_lock);
+       g_mutex_lock (&registry->priv->sources_lock);
 
        values = g_hash_table_get_values (registry->priv->sources);
 
        g_list_foreach (values, (GFunc) g_object_ref, NULL);
 
-       g_mutex_unlock (registry->priv->sources_lock);
+       g_mutex_unlock (&registry->priv->sources_lock);
 
        return values;
 }
@@ -375,7 +419,7 @@ source_registry_sources_build_tree (ESourceRegistry *registry)
        GHashTableIter iter;
        gpointer key, value;
 
-       g_mutex_lock (registry->priv->sources_lock);
+       g_mutex_lock (&registry->priv->sources_lock);
 
        root = g_node_new (NULL);
        index = g_hash_table_new (g_str_hash, g_str_equal);
@@ -413,7 +457,7 @@ source_registry_sources_build_tree (ESourceRegistry *registry)
 
        g_hash_table_destroy (index);
 
-       g_mutex_unlock (registry->priv->sources_lock);
+       g_mutex_unlock (&registry->priv->sources_lock);
 
        return root;
 }
@@ -501,6 +545,43 @@ source_registry_source_notify_enabled_cb (ESource *source,
        g_source_unref (idle_source);
 }
 
+static ESource *
+source_registry_new_source (ESourceRegistry *registry,
+                            GDBusObject *dbus_object)
+{
+       GMainContext *main_context;
+       ESource *source;
+       const gchar *object_path;
+       GError *error = NULL;
+
+       /* We don't want the ESource emitting "changed" signals from
+        * the manager thread, so we pass it the same main context the
+        * registry uses for scheduling signal emissions. */
+       main_context = registry->priv->main_context;
+       source = e_source_new (dbus_object, main_context, &error);
+       object_path = g_dbus_object_get_object_path (dbus_object);
+
+       /* The likelihood of an error here is slim, so it's
+        * sufficient to just print a warning if one occurs. */
+       if (error != NULL) {
+               g_warn_if_fail (source == NULL);
+               g_critical (
+                       "ESourceRegistry: Failed to create a "
+                       "data source object for path '%s': %s",
+                       object_path, error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+       /* Add the ESource to the object path table immediately. */
+       source_registry_object_path_table_insert (
+               registry, object_path, source);
+
+       return source;
+}
+
 static void
 source_registry_unref_source (ESource *source)
 {
@@ -521,14 +602,17 @@ source_registry_add_source (ESourceRegistry *registry,
 {
        const gchar *uid;
 
+       /* This is called in the manager thread during initialization
+        * and in response to "object-added" signals from the manager. */
+
        uid = e_source_get_uid (source);
        g_return_if_fail (uid != NULL);
 
-       g_mutex_lock (registry->priv->sources_lock);
+       g_mutex_lock (&registry->priv->sources_lock);
 
        /* Check if we already have this source in the registry. */
        if (g_hash_table_lookup (registry->priv->sources, uid) != NULL) {
-               g_mutex_unlock (registry->priv->sources_lock);
+               g_mutex_unlock (&registry->priv->sources_lock);
                return;
        }
 
@@ -542,31 +626,19 @@ source_registry_add_source (ESourceRegistry *registry,
                G_CALLBACK (source_registry_source_notify_enabled_cb),
                registry);
 
-       g_mutex_unlock (registry->priv->sources_lock);
+       g_mutex_unlock (&registry->priv->sources_lock);
 
        source_registry_sources_insert (registry, source);
-
-       g_signal_emit (registry, signals[SOURCE_ADDED], 0, source);
-}
-
-static void
-source_registry_remove_source (ESourceRegistry *registry,
-                               ESource *source)
-{
-       g_object_ref (source);
-
-       if (source_registry_sources_remove (registry, source))
-               g_signal_emit (registry, signals[SOURCE_REMOVED], 0, source);
-
-       g_object_unref (source);
 }
 
 static gboolean
 source_registry_object_added_idle_cb (gpointer user_data)
 {
        SourceClosure *closure = user_data;
+       ESourceRegistry *registry = closure->registry;
+       ESource *source = closure->source;
 
-       source_registry_add_source (closure->registry, closure->source);
+       g_signal_emit (registry, signals[SOURCE_ADDED], 0, source);
 
        return FALSE;
 }
@@ -577,38 +649,17 @@ source_registry_object_added_cb (GDBusObjectManager *object_manager,
                                  ESourceRegistry *registry)
 {
        SourceClosure *closure;
-       GMainContext *main_context;
        GSource *idle_source;
        ESource *source;
-       const gchar *object_path;
-       GError *error = NULL;
 
        g_return_if_fail (E_DBUS_IS_OBJECT (dbus_object));
 
-       /* We don't want the ESource emitting "changed" signals from
-        * the manager thread, so we pass it the same main context the
-        * registry uses for scheduling signal emissions. */
-       main_context = registry->priv->main_context;
-       source = e_source_new (dbus_object, main_context, &error);
-       object_path = g_dbus_object_get_object_path (dbus_object);
-
-       /* The likelihood of an error here is slim, so it's
-        * sufficient to just print a warning if one occurs. */
-       if (error != NULL) {
-               g_warn_if_fail (source == NULL);
-               g_critical (
-                       "ESourceRegistry: Failed to create a "
-                       "data source object for path '%s': %s",
-                       object_path, error->message);
-               g_error_free (error);
-               return;
-       }
-
-       g_return_if_fail (E_IS_SOURCE (source));
+       source = source_registry_new_source (registry, dbus_object);
+       g_return_if_fail (source != NULL);
 
-       /* Add the ESource to the object path table immediately. */
-       source_registry_object_path_table_insert (
-               registry, object_path, source);
+       /* Add the new ESource to our internal hash table so it can be
+        * obtained through e_source_registry_ref_source() immediately. */
+       source_registry_add_source (registry, source);
 
        /* Schedule a callback on the ESourceRegistry's GMainContext. */
 
@@ -631,8 +682,13 @@ static gboolean
 source_registry_object_removed_idle_cb (gpointer user_data)
 {
        SourceClosure *closure = user_data;
+       ESourceRegistry *registry = closure->registry;
+       ESource *source = closure->source;
 
-       source_registry_remove_source (closure->registry, closure->source);
+       /* Removing the ESource won't finalize it because the
+        * SourceClosure itself still holds a reference on it. */
+       if (source_registry_sources_remove (registry, source))
+               g_signal_emit (registry, signals[SOURCE_REMOVED], 0, source);
 
        return FALSE;
 }
@@ -679,9 +735,9 @@ source_registry_object_manager_running (gpointer data)
 {
        ThreadClosure *closure = data;
 
-       g_mutex_lock (closure->main_loop_mutex);
-       g_cond_broadcast (closure->main_loop_cond);
-       g_mutex_unlock (closure->main_loop_mutex);
+       g_mutex_lock (&closure->main_loop_mutex);
+       g_cond_broadcast (&closure->main_loop_cond);
+       g_mutex_unlock (&closure->main_loop_mutex);
 
        return FALSE;
 }
@@ -693,9 +749,8 @@ source_registry_object_manager_thread (gpointer data)
        ThreadClosure *closure = data;
        GSource *idle_source;
        GList *list, *link;
-       gulong object_added_id;
-       gulong object_removed_id;
-       GError *error = NULL;
+       gulong object_added_id = 0;
+       gulong object_removed_id = 0;
 
        /* GDBusObjectManagerClient grabs the thread-default GMainContext
         * at creation time and only emits signals from that GMainContext.
@@ -713,15 +768,19 @@ source_registry_object_manager_thread (gpointer data)
                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
                SOURCES_DBUS_SERVICE_NAME,
                DBUS_OBJECT_PATH,
-               NULL, &error);
+               NULL, &closure->error);
 
-       /* If this fails there's really no point in continuing
-        * since we rely on the object manager to populate the
-        * registry.  Abort the process with a fatal error. */
-       if (error != NULL) {
-               g_error ("%s", error->message);
-               g_assert_not_reached ();
-       }
+       /* Sanity check. */
+       g_warn_if_fail (
+               ((object_manager != NULL) && (closure->error == NULL)) ||
+               ((object_manager == NULL) && (closure->error != NULL)));
+
+       /* If we failed to create the GDBusObjectManagerClient, skip
+        * straight to the main loop.  The GError will be propagated
+        * back to the caller, the main loop will terminate, and the
+        * partially-initialized ESourceRegistry will be destroyed. */
+       if (object_manager == NULL)
+               goto notify;
 
        /* Give the registry a handle to the object manager. */
        closure->registry->priv->dbus_object_manager =
@@ -731,24 +790,23 @@ source_registry_object_manager_thread (gpointer data)
 
        list = g_dbus_object_manager_get_objects (object_manager);
 
-       for (link = list; link != NULL; link = g_list_next (link))
-               source_registry_object_added_cb (
-                       object_manager,
-                       G_DBUS_OBJECT (link->data),
-                       closure->registry);
+       for (link = list; link != NULL; link = g_list_next (link)) {
+               GDBusObject *dbus_object;
+               ESource *source;
 
-       g_list_free_full (list, (GDestroyNotify) g_object_unref);
+               dbus_object = G_DBUS_OBJECT (link->data);
 
-       /* Schedule a one-time idle callback to broadcast through a
-        * condition variable that our main loop is up and running. */
+               source = source_registry_new_source (
+                       closure->registry, dbus_object);
 
-       idle_source = g_idle_source_new ();
-       g_source_set_callback (
-               idle_source,
-               source_registry_object_manager_running,
-               closure, (GDestroyNotify) NULL);
-       g_source_attach (idle_source, closure->main_context);
-       g_source_unref (idle_source);
+               if (source != NULL) {
+                       source_registry_add_source (
+                               closure->registry, source);
+                       g_object_unref (source);
+               }
+       }
+
+       g_list_free_full (list, (GDestroyNotify) g_object_unref);
 
        /* Listen for D-Bus object additions and removals. */
 
@@ -762,16 +820,29 @@ source_registry_object_manager_thread (gpointer data)
                G_CALLBACK (source_registry_object_removed_cb),
                closure->registry);
 
+notify:
+       /* Schedule a one-time idle callback to broadcast through a
+        * condition variable that our main loop is up and running. */
+
+       idle_source = g_idle_source_new ();
+       g_source_set_callback (
+               idle_source,
+               source_registry_object_manager_running,
+               closure, (GDestroyNotify) NULL);
+       g_source_attach (idle_source, closure->main_context);
+       g_source_unref (idle_source);
+
        /* Now we mostly idle here for the rest of the session. */
 
        g_main_loop_run (closure->main_loop);
 
        /* Clean up and exit. */
 
-       g_signal_handler_disconnect (object_manager, object_added_id);
-       g_signal_handler_disconnect (object_manager, object_removed_id);
-
-       g_object_unref (object_manager);
+       if (object_manager != NULL) {
+               g_signal_handler_disconnect (object_manager, object_added_id);
+               g_signal_handler_disconnect (object_manager, object_removed_id);
+               g_object_unref (object_manager);
+       }
 
        g_main_context_pop_thread_default (closure->main_context);
 
@@ -914,6 +985,7 @@ source_registry_dispose (GObject *object)
        g_hash_table_remove_all (priv->sources);
 
        if (priv->settings != NULL) {
+               g_signal_handlers_disconnect_by_data (priv->settings, object);
                g_object_unref (priv->settings);
                priv->settings = NULL;
        }
@@ -930,10 +1002,10 @@ source_registry_finalize (GObject *object)
        priv = E_SOURCE_REGISTRY_GET_PRIVATE (object);
 
        g_hash_table_destroy (priv->object_path_table);
-       g_mutex_free (priv->object_path_table_lock);
+       g_mutex_clear (&priv->object_path_table_lock);
 
        g_hash_table_destroy (priv->sources);
-       g_mutex_free (priv->sources_lock);
+       g_mutex_clear (&priv->sources_lock);
 
        /* Chain up to parent's finalize() method. */
        G_OBJECT_CLASS (e_source_registry_parent_class)->finalize (object);
@@ -956,37 +1028,53 @@ source_registry_initable_init (GInitable *initable,
         * we wait for the main loop to start running as a way of
         * synchronizing with the manager thread. */
        closure->main_loop = g_main_loop_new (closure->main_context, FALSE);
-       closure->main_loop_cond = g_cond_new ();
-       closure->main_loop_mutex = g_mutex_new ();
+       g_cond_init (&closure->main_loop_cond);
+       g_mutex_init (&closure->main_loop_mutex);
 
        registry->priv->thread_closure = closure;
 
-       registry->priv->manager_thread = g_thread_create (
+       registry->priv->manager_thread = g_thread_new (
+               NULL,
                source_registry_object_manager_thread,
-               closure, TRUE /* joinable */, error);
+               closure);
 
        if (registry->priv->manager_thread == NULL)
                return FALSE;
 
        /* Wait for notification that the manager
         * thread's main loop has been started. */
-       g_mutex_lock (closure->main_loop_mutex);
+       g_mutex_lock (&closure->main_loop_mutex);
        while (!g_main_loop_is_running (closure->main_loop))
                g_cond_wait (
-                       closure->main_loop_cond,
-                       closure->main_loop_mutex);
-       g_mutex_unlock (closure->main_loop_mutex);
-
-       /* We should now have a GDBusObjectManagerClient available. */
-       g_return_val_if_fail (
-               G_IS_DBUS_OBJECT_MANAGER_CLIENT (
-               registry->priv->dbus_object_manager), FALSE);
+                       &closure->main_loop_cond,
+                       &closure->main_loop_mutex);
+       g_mutex_unlock (&closure->main_loop_mutex);
+
+       /* Check for error in the manager thread. */
+       if (closure->error != NULL) {
+               g_propagate_error (error, closure->error);
+               closure->error = NULL;
+               return FALSE;
+       }
 
-       /* The manager thread will have queued up a bunch of idle
-        * sources on our GMainContext to populate the registry.
-        * Iterate our GMainContext until they get dispatched. */
-       while (g_hash_table_size (registry->priv->sources) == 0)
-               g_main_context_iteration (registry->priv->main_context, TRUE);
+       /* The registry should now be populated with sources.
+        *
+        * XXX Actually, not necessarily if the registry service was
+        *     just now activated.  There may yet be a small window
+        *     while the registry service starts up before it exports
+        *     any sources, even built-in sources.  This COULD create
+        *     problems if any logic that depends on those built-in
+        *     sources executes during this time window, but so far
+        *     we haven't seen any cases of that.
+        *
+        *     Attempts in the past to stop and wait for sources to
+        *     show up have proven problematic.  See for example:
+        *     https://bugzilla.gnome.org/678378
+        *
+        *     Leave the runtime check disabled for the moment.
+        *     I have a feeling I'll be revisiting this again.
+        */
+       /*g_warn_if_fail (g_hash_table_size (registry->priv->sources) > 0);*/
 
        /* The EDBusSourceManagerProxy is just another D-Bus interface
         * that resides at the same object path.  It's unrelated to the
@@ -1002,6 +1090,14 @@ source_registry_initable_init (GInitable *initable,
        if (registry->priv->dbus_source_manager == NULL)
                return FALSE;
 
+       /* Allow authentication prompts for all exported data sources
+        * when a new EDBusSourceManagerProxy is created.  The thought
+        * being, if you cancel an authentication prompt you will not
+        * be bothered again until you start (or restart) a new E-D-S
+        * client app.  Failure here is non-fatal, ignore errors. */
+       e_dbus_source_manager_call_allow_auth_prompt_all_sync (
+               registry->priv->dbus_source_manager, cancellable, NULL);
+
        return TRUE;
 }
 
@@ -1022,7 +1118,7 @@ e_source_registry_class_init (ESourceRegistryClass *class)
         * "org.gnome.Evolution.DefaultSources" GSettings schema. */
 
        /**
-        * ESourceRegistry:default-address-book
+        * ESourceRegistry:default-address-book:
         *
         * The default address book #ESource.
         **/
@@ -1038,7 +1134,7 @@ e_source_registry_class_init (ESourceRegistryClass *class)
                        G_PARAM_STATIC_STRINGS));
 
        /**
-        * ESourceRegistry:default-calendar
+        * ESourceRegistry:default-calendar:
         *
         * The default calendar #ESource.
         **/
@@ -1054,7 +1150,7 @@ e_source_registry_class_init (ESourceRegistryClass *class)
                        G_PARAM_STATIC_STRINGS));
 
        /**
-        * ESourceRegistry:default-mail-account
+        * ESourceRegistry:default-mail-account:
         *
         * The default mail account #ESource.
         **/
@@ -1070,7 +1166,7 @@ e_source_registry_class_init (ESourceRegistryClass *class)
                        G_PARAM_STATIC_STRINGS));
 
        /**
-        * ESourceRegistry:default-mail-identity
+        * ESourceRegistry:default-mail-identity:
         *
         * The default mail identity #ESource.
         **/
@@ -1086,7 +1182,7 @@ e_source_registry_class_init (ESourceRegistryClass *class)
                        G_PARAM_STATIC_STRINGS));
 
        /**
-        * ESourceRegistry:default-memo-list
+        * ESourceRegistry:default-memo-list:
         *
         * The default memo list #ESource.
         **/
@@ -1102,7 +1198,7 @@ e_source_registry_class_init (ESourceRegistryClass *class)
                        G_PARAM_STATIC_STRINGS));
 
        /**
-        * ESourceRegistry:default-task-list
+        * ESourceRegistry:default-task-list:
         *
         * The default task list #ESource.
         **/
@@ -1135,7 +1231,7 @@ e_source_registry_class_init (ESourceRegistryClass *class)
                E_TYPE_SOURCE);
 
        /**
-        * ESourceRegistry::source-changed
+        * ESourceRegistry::source-changed:
         * @registry: the #ESourceRegistry which emitted the signal
         * @source: the #ESource that changed
         *
@@ -1217,9 +1313,7 @@ e_source_registry_init (ESourceRegistry *registry)
 
        /* This is so the object manager thread can schedule signal
         * emissions on the thread-default context for this thread. */
-       registry->priv->main_context = g_main_context_get_thread_default ();
-       if (registry->priv->main_context != NULL)
-               g_main_context_ref (registry->priv->main_context);
+       registry->priv->main_context = g_main_context_ref_thread_default ();
 
        /* D-Bus object path -> ESource */
        registry->priv->object_path_table =
@@ -1229,7 +1323,7 @@ e_source_registry_init (ESourceRegistry *registry)
                        (GDestroyNotify) g_free,
                        (GDestroyNotify) g_object_unref);
 
-       registry->priv->object_path_table_lock = g_mutex_new ();
+       g_mutex_init (&registry->priv->object_path_table_lock);
 
        /* UID string -> ESource */
        registry->priv->sources = g_hash_table_new_full (
@@ -1238,7 +1332,7 @@ e_source_registry_init (ESourceRegistry *registry)
                (GDestroyNotify) g_free,
                (GDestroyNotify) source_registry_unref_source);
 
-       registry->priv->sources_lock = g_mutex_new ();
+       g_mutex_init (&registry->priv->sources_lock);
 
        registry->priv->settings = g_settings_new (GSETTINGS_SCHEMA);
 
@@ -1264,6 +1358,11 @@ ESourceRegistry *
 e_source_registry_new_sync (GCancellable *cancellable,
                             GError **error)
 {
+       /* XXX Work around http://bugzilla.gnome.org/show_bug.cgi?id=683519
+        *     until GObject's type initialization deadlock issue is fixed.
+        *     Apparently only the synchronous instantiation is affected. */
+       g_type_ensure (G_TYPE_DBUS_CONNECTION);
+
        return g_initable_new (
                E_TYPE_SOURCE_REGISTRY,
                cancellable, error, NULL);
@@ -1369,9 +1468,25 @@ source_registry_authenticate_respond_cb (AuthContext *auth_context)
         * session will either time out on its own or the authentication
         * dialog will eventually be dismissed by the user. */
 
+       /* If we were cancelled from our side, we have a bit of a dilemma.
+        * We need to tell the server to cancel the authentication session,
+        * but that involves making a synchronous D-Bus call, which we are
+        * not supposed to do if we know we've been cancelled.  But if we
+        * don't tell the server, the authentication session will be left
+        * to timeout on its own (which may take minutes), and meanwhile
+        * all other authentication requests are blocked.  So choose the
+        * lesser evil and make the synchronous call but without passing
+        * the already-cancelled GCancellable. */
+       if (g_cancellable_is_cancelled (auth_context->cancellable)) {
+               e_dbus_authenticator_call_cancel_sync (
+                       auth_context->dbus_auth,
+                       NULL, &non_fatal_error);
+               g_main_loop_quit (auth_context->main_loop);
+               auth_context->success = FALSE;
+
        /* If an error occurred while attempting to authenticate,
         * tell the server to cancel the authentication session. */
-       if (auth_result == E_SOURCE_AUTHENTICATION_ERROR) {
+       } else if (auth_result == E_SOURCE_AUTHENTICATION_ERROR) {
                e_dbus_authenticator_call_cancel_sync (
                        auth_context->dbus_auth,
                        auth_context->cancellable,
@@ -1657,6 +1772,12 @@ e_source_registry_authenticate_sync (ESourceRegistry *registry,
 
 exit:
        g_main_context_pop_thread_default (main_context);
+
+       /* Make sure the main_context doesn't have pending operations;
+          workarounds https://bugzilla.gnome.org/show_bug.cgi?id=690126 */
+       while (g_main_context_pending (main_context))
+               g_main_context_iteration (main_context, FALSE);
+
        g_main_context_unref (main_context);
 
        return success;
@@ -1791,7 +1912,9 @@ source_registry_commit_source_thread (GSimpleAsyncResult *simple,
  *
  * If @source does NOT have a #GDBusObject (implying it's a scratch
  * #ESource), its contents are submitted to the D-Bus service through
- * e_source_registry_create_sources_sync().
+ * either e_source_remote_create_sync() if @source is to be a collection
+ * member, or e_source_registry_create_sources_sync() if @source to be an
+ * independent data source.
  *
  * If an error occurs, the function will set @error and return %FALSE.
  *
@@ -1806,6 +1929,8 @@ e_source_registry_commit_source_sync (ESourceRegistry *registry,
                                       GError **error)
 {
        GDBusObject *dbus_object;
+       ESource *collection_source;
+       gboolean collection_member;
        gboolean success;
 
        g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
@@ -1813,9 +1938,21 @@ e_source_registry_commit_source_sync (ESourceRegistry *registry,
 
        dbus_object = e_source_ref_dbus_object (source);
 
+       collection_source = e_source_registry_find_extension (
+               registry, source, E_SOURCE_EXTENSION_COLLECTION);
+
+       collection_member =
+               (collection_source != NULL) &&
+               (collection_source != source);
+
        if (dbus_object != NULL) {
                success = e_source_write_sync (source, cancellable, error);
                g_object_unref (dbus_object);
+
+       } else if (collection_member) {
+               success = e_source_remote_create_sync (
+                       collection_source, source, cancellable, error);
+
        } else {
                GList *list = g_list_prepend (NULL, source);
                success = e_source_registry_create_sources_sync (
@@ -1823,6 +1960,9 @@ e_source_registry_commit_source_sync (ESourceRegistry *registry,
                g_list_free (list);
        }
 
+       if (collection_source != NULL)
+               g_object_unref (collection_source);
+
        return success;
 }
 
@@ -1927,10 +2067,54 @@ source_registry_create_sources_thread (GSimpleAsyncResult *simple,
                g_simple_async_result_take_error (simple, error);
 }
 
+/* Helper for e_source_registry_create_sources_sync() */
+static gboolean
+source_registry_create_sources_main_loop_quit_cb (gpointer user_data)
+{
+       GMainLoop *main_loop = user_data;
+
+       g_main_loop_quit (main_loop);
+
+       return FALSE;
+}
+
+/* Helper for e_source_registry_create_sources_sync() */
+static void
+source_registry_create_sources_object_added_cb (GDBusObjectManager *object_manager,
+                                                GDBusObject *dbus_object,
+                                                CreateContext *create_context)
+{
+       EDBusObject *e_dbus_object;
+       EDBusSource *e_dbus_source;
+       const gchar *uid;
+
+       e_dbus_object = E_DBUS_OBJECT (dbus_object);
+       e_dbus_source = e_dbus_object_get_source (e_dbus_object);
+       uid = e_dbus_source_get_uid (e_dbus_source);
+
+       g_hash_table_remove (create_context->pending_uids, uid);
+
+       /* The hash table will be empty when all of the expected
+        * GDBusObjects have been added to the GDBusObjectManager. */
+       if (g_hash_table_size (create_context->pending_uids) == 0) {
+               GSource *idle_source;
+
+               idle_source = g_idle_source_new ();
+               g_source_set_callback (
+                       idle_source,
+                       source_registry_create_sources_main_loop_quit_cb,
+                       g_main_loop_ref (create_context->main_loop),
+                       (GDestroyNotify) g_main_loop_unref);
+               g_source_attach (idle_source, create_context->main_context);
+               g_source_unref (idle_source);
+       }
+}
+
 /**
  * e_source_registry_create_sources_sync:
  * @registry: an #ESourceRegistry
- * @list_of_sources: a list of #ESource instances with no #GDBusObject
+ * @list_of_sources: (element-type ESource): a list of #ESource instances with
+ * no #GDBusObject
  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
  * @error: return location for a #GError, or %NULL
  *
@@ -1950,9 +2134,11 @@ e_source_registry_create_sources_sync (ESourceRegistry *registry,
                                        GCancellable *cancellable,
                                        GError **error)
 {
+       CreateContext *create_context;
        GVariantBuilder builder;
        GVariant *variant;
        GList *link;
+       gulong object_added_id;
        gboolean success;
 
        g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
@@ -1961,15 +2147,21 @@ e_source_registry_create_sources_sync (ESourceRegistry *registry,
        for (link = list_of_sources; link != NULL; link = g_list_next (link))
                g_return_val_if_fail (E_IS_SOURCE (link->data), FALSE);
 
+       create_context = create_context_new ();
+       g_main_context_push_thread_default (create_context->main_context);
+
        g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
 
        for (link = list_of_sources; link != NULL; link = g_list_next (link)) {
                ESource *source;
-               const gchar *uid;
                gchar *source_data;
+               gchar *uid;
 
                source = E_SOURCE (link->data);
-               uid = e_source_get_uid (source);
+               uid = e_source_dup_uid (source);
+
+               /* Takes ownership of the UID string. */
+               g_hash_table_add (create_context->pending_uids, uid);
 
                source_data = e_source_to_string (source, NULL);
                g_variant_builder_add (&builder, "{ss}", uid, source_data);
@@ -1978,6 +2170,14 @@ e_source_registry_create_sources_sync (ESourceRegistry *registry,
 
        variant = g_variant_builder_end (&builder);
 
+       /* Use G_CONNECT_AFTER so source_registry_object_added_cb()
+        * runs first and actually adds the ESource to the internal
+        * hash table before we go quitting our main loop. */
+       object_added_id = g_signal_connect_after (
+               registry->priv->dbus_object_manager, "object-added",
+               G_CALLBACK (source_registry_create_sources_object_added_cb),
+               create_context);
+
        /* This function sinks the floating GVariant reference. */
        success = e_dbus_source_manager_call_create_sources_sync (
                registry->priv->dbus_source_manager,
@@ -1985,13 +2185,39 @@ e_source_registry_create_sources_sync (ESourceRegistry *registry,
 
        g_variant_builder_clear (&builder);
 
+       /* Wait for an "object-added" signal for each created ESource.
+        * But also set a short timeout to avoid getting stuck here in
+        * case the registry service adds sources to its orphan table,
+        * which prevents them from being exported over D-Bus. */
+       if (success) {
+               GSource *timeout_source;
+
+               timeout_source = g_timeout_source_new_seconds (2);
+               g_source_set_callback (
+                       timeout_source,
+                       source_registry_create_sources_main_loop_quit_cb,
+                       g_main_loop_ref (create_context->main_loop),
+                       (GDestroyNotify) g_main_loop_unref);
+               g_source_attach (timeout_source, create_context->main_context);
+               g_source_unref (timeout_source);
+
+               g_main_loop_run (create_context->main_loop);
+       }
+
+       g_signal_handler_disconnect (
+               registry->priv->dbus_object_manager, object_added_id);
+
+       g_main_context_pop_thread_default (create_context->main_context);
+       create_context_free (create_context);
+
        return success;
 }
 
 /**
  * e_source_registry_create_sources:
  * @registry: an #ESourceRegistry
- * @list_of_sources: a list of #ESource instances with no #GDBusObject
+ * @list_of_sources: (element-type ESource): a list of #ESource instances with
+ * no #GDBusObject
  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
  * @callback: (scope async): a #GAsyncReadyCallback to call when the request
  *            is satisfied
@@ -2122,7 +2348,7 @@ e_source_registry_ref_source (ESourceRegistry *registry,
  *   g_list_free_full (list, g_object_unref);
  * ]|
  *
- * Returns: (transfer full): a sorted list of sources
+ * Returns: (element-type ESource) (transfer full): a sorted list of sources
  *
  * Since: 3.6
  **/
@@ -2159,6 +2385,58 @@ e_source_registry_list_sources (ESourceRegistry *registry,
 }
 
 /**
+ * e_source_registry_check_enabled:
+ * @registry: an #ESourceRegistry
+ * @source: an #ESource
+ *
+ * Determines whether @source is "effectively" enabled by examining its
+ * own #ESource:enabled property as well as those of its ancestors in the
+ * #ESource hierarchy.  If all examined #ESource:enabled properties are
+ * %TRUE, then the function returns %TRUE.  If any are %FALSE, then the
+ * function returns %FALSE.
+ *
+ * Use this function instead of e_source_get_enabled() to determine
+ * things like whether to display an #ESource in a user interface or
+ * whether to act on the data set described by the #ESource.
+ *
+ * Returns: whether @source is "effectively" enabled
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_source_registry_check_enabled (ESourceRegistry *registry,
+                                 ESource *source)
+{
+       gboolean enabled;
+       gchar *parent_uid;
+
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
+       g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+       enabled = e_source_get_enabled (source);
+       parent_uid = e_source_dup_parent (source);
+
+       while (enabled && parent_uid != NULL) {
+               ESource *parent;
+
+               parent = e_source_registry_ref_source (registry, parent_uid);
+
+               g_free (parent_uid);
+               parent_uid = NULL;
+
+               if (parent != NULL) {
+                       enabled = e_source_get_enabled (parent);
+                       parent_uid = e_source_dup_parent (parent);
+                       g_object_unref (parent);
+               }
+       }
+
+       g_free (parent_uid);
+
+       return enabled;
+}
+
+/**
  * e_source_registry_find_extension:
  * @registry: an #ESourceRegistry
  * @source: an #ESource
@@ -2266,12 +2544,12 @@ source_registry_prune_nodes (GNode *node,
                              const gchar *extension_name)
 {
        GQueue queue = G_QUEUE_INIT;
-       GNode *child;
+       GNode *child_node;
 
        /* Unlink all the child nodes and place them in a queue. */
-       while ((child = g_node_first_child (node)) != NULL) {
-               g_node_unlink (child);
-               g_queue_push_tail (&queue, child);
+       while ((child_node = g_node_first_child (node)) != NULL) {
+               g_node_unlink (child_node);
+               g_queue_push_tail (&queue, child_node);
        }
 
        /* Sort the queue by source name. */
@@ -2282,33 +2560,24 @@ source_registry_prune_nodes (GNode *node,
        /* Pop nodes off the head of the queue until the queue is empty.
         * If the node has either its own children or the given extension
         * name, put it back under the parent node (preserving the sorted
-        * order).  Otherwise delete the node. */
-       while ((child = g_queue_pop_head (&queue)) != NULL) {
-               ESource *source = E_SOURCE (child->data);
-
-               if (extension_name == NULL) {
-                       if (e_source_get_enabled (source)) {
-                               g_node_append (node, child);
-                       } else {
-                               g_object_unref (source);
-                               g_node_destroy (child);
-                       }
-
-               } else if (e_source_has_extension (source, extension_name)) {
-                       if (e_source_get_enabled (source)) {
-                               g_node_append (node, child);
-                       } else {
-                               g_object_unref (source);
-                               g_node_destroy (child);
-                       }
-
-               } else if (g_node_first_child (child) != NULL) {
-                       g_node_append (node, child);
+        * order).  Otherwise delete the node and its descendants. */
+       while ((child_node = g_queue_pop_head (&queue)) != NULL) {
+               ESource *child = E_SOURCE (child_node->data);
+               gboolean append_child_node = FALSE;
 
-               } else {
-                       g_object_unref (source);
-                       g_node_destroy (child);
-               }
+               if (extension_name == NULL)
+                       append_child_node = e_source_get_enabled (child);
+
+               else if (e_source_has_extension (child, extension_name))
+                       append_child_node = e_source_get_enabled (child);
+
+               else if (g_node_first_child (child_node) != NULL)
+                       append_child_node = e_source_get_enabled (child);
+
+               if (append_child_node)
+                       g_node_append (node, child_node);
+               else
+                       e_source_registry_free_display_tree (child_node);
        }
 
        return FALSE;
@@ -2335,7 +2604,8 @@ source_registry_prune_nodes (GNode *node,
  * For convenience, e_source_registry_free_display_tree() does all
  * that in one step.
  *
- * Returns: (transfer full): a tree of sources, arranged for display
+ * Returns: (element-type ESource) (transfer full): a tree of sources,
+ *          arranged for display
  *
  * Since: 3.6
  **/
@@ -2473,7 +2743,6 @@ e_source_registry_ref_builtin_address_book (ESourceRegistry *registry)
 
        uid = E_SOURCE_BUILTIN_ADDRESS_BOOK_UID;
        source = e_source_registry_ref_source (registry, uid);
-       g_return_val_if_fail (source != NULL, NULL);
 
        return source;
 }
@@ -2507,12 +2776,10 @@ e_source_registry_ref_default_address_book (ESourceRegistry *registry)
        source = e_source_registry_ref_source (registry, uid);
        g_free (uid);
 
-       /* The built-in source is always present. */
+       /* The built-in source is present in normal EDS installations. */
        if (source == NULL)
                source = e_source_registry_ref_builtin_address_book (registry);
 
-       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
-
        return source;
 }
 
@@ -2575,7 +2842,6 @@ e_source_registry_ref_builtin_calendar (ESourceRegistry *registry)
 
        uid = E_SOURCE_BUILTIN_CALENDAR_UID;
        source = e_source_registry_ref_source (registry, uid);
-       g_return_val_if_fail (source != NULL, NULL);
 
        return source;
 }
@@ -2609,12 +2875,10 @@ e_source_registry_ref_default_calendar (ESourceRegistry *registry)
        source = e_source_registry_ref_source (registry, uid);
        g_free (uid);
 
-       /* The built-in source is always present. */
+       /* The built-in source is present in normal EDS installations. */
        if (source == NULL)
                source = e_source_registry_ref_builtin_calendar (registry);
 
-       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
-
        return source;
 }
 
@@ -2797,14 +3061,14 @@ source_registry_ref_any_mail_identity (ESourceRegistry *registry)
        for (link = list; link != NULL; link = g_list_next (link)) {
                ESource *candidate = E_SOURCE (link->data);
 
-               if (e_source_get_enabled (candidate)) {
+               if (e_source_registry_check_enabled (registry, candidate)) {
                        source = g_object_ref (candidate);
                        break;
                }
        }
 
        if (source == NULL && list != NULL)
-               source = g_object_ref (link->data);
+               source = g_object_ref (list->data);
 
        g_list_free_full (list, (GDestroyNotify) g_object_unref);