/* * e-source-registry.c * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /** * SECTION: e-source-registry * @include: libedataserver/libedataserver.h * @short_description: A central repository for data sources * * The #ESourceRegistry is a global singleton store for all #ESource * instances. It uses file monitors to react to key file creation and * deletion events, either constructing an #ESource instance from the * newly created key file, or removing from the logical #ESource * hierarchy the instance corresponding to the deleted key file. * * The #ESourceRegistry can be queried for individual #ESource instances * by their unique identifier string or key file path, for collections of * #ESource instances having a particular extension, or for all available * #ESource instances. * * The #ESourceRegistry API also provides a front-end for the * "org.gnome.Evolution.DefaultSources" #GSettings schema which tracks * which #ESource instances are designated to be the user's default address * book, calendar, memo list and task list for desktop integration. * * Note: The #ESourceRegistry uses thread default main context from the time * of its creation to deliver D-Bus signals, finish operations and so on, * thus it requires a running main loop for its proper functionality. **/ #include "e-source-registry.h" #include #include #include /* XXX Yeah, yeah... */ #define GCR_API_SUBJECT_TO_CHANGE #include /* Private D-Bus classes. */ #include #include #include #include #include /* Needed for the defaults API. */ #include #include #include #include #define E_SOURCE_REGISTRY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_SOURCE_REGISTRY, ESourceRegistryPrivate)) #define DBUS_OBJECT_PATH "/org/gnome/evolution/dataserver/SourceManager" #define GSETTINGS_SCHEMA "org.gnome.Evolution.DefaultSources" /* Built-in data source UIDs. */ #define E_SOURCE_BUILTIN_ADDRESS_BOOK_UID "system-address-book" #define E_SOURCE_BUILTIN_CALENDAR_UID "system-calendar" #define E_SOURCE_BUILTIN_MAIL_ACCOUNT_UID "local" #define E_SOURCE_BUILTIN_MEMO_LIST_UID "system-memo-list" #define E_SOURCE_BUILTIN_PROXY_UID "system-proxy" #define E_SOURCE_BUILTIN_TASK_LIST_UID "system-task-list" /* GSettings keys for default data sources. */ #define E_SETTINGS_DEFAULT_ADDRESS_BOOK_KEY "default-address-book" #define E_SETTINGS_DEFAULT_CALENDAR_KEY "default-calendar" #define E_SETTINGS_DEFAULT_MAIL_ACCOUNT_KEY "default-mail-account" #define E_SETTINGS_DEFAULT_MAIL_IDENTITY_KEY "default-mail-identity" #define E_SETTINGS_DEFAULT_MEMO_LIST_KEY "default-memo-list" #define E_SETTINGS_DEFAULT_TASK_LIST_KEY "default-task-list" typedef struct _AsyncContext AsyncContext; typedef struct _CreateContext CreateContext; typedef struct _SourceClosure SourceClosure; typedef struct _ThreadClosure ThreadClosure; typedef struct _CredentialsRequiredClosure CredentialsRequiredClosure; struct _ESourceRegistryPrivate { GMainContext *main_context; GThread *manager_thread; ThreadClosure *thread_closure; GDBusObjectManager *dbus_object_manager; EDBusSourceManager *dbus_source_manager; GHashTable *object_path_table; GMutex object_path_table_lock; GHashTable *service_restart_table; GMutex service_restart_table_lock; GHashTable *sources; GMutex sources_lock; GSettings *settings; gboolean initialized; GError *init_error; GMutex init_lock; }; struct _AsyncContext { ESource *source; GList *list_of_sources; }; /* Used in e_source_registry_create_sources_sync() */ struct _CreateContext { GHashTable *pending_uids; GMainContext *main_context; GMainLoop *main_loop; }; struct _SourceClosure { GWeakRef registry; ESource *source; }; struct _ThreadClosure { ESourceRegistry *registry; GMainContext *main_context; GMainLoop *main_loop; GCond main_loop_cond; GMutex main_loop_mutex; GError *error; }; struct _CredentialsRequiredClosure { GWeakRef registry; ESource *source; ESourceCredentialsReason reason; gchar *certificate_pem; GTlsCertificateFlags certificate_errors; GError *op_error; }; enum { PROP_0, PROP_DEFAULT_ADDRESS_BOOK, PROP_DEFAULT_CALENDAR, PROP_DEFAULT_MAIL_ACCOUNT, PROP_DEFAULT_MAIL_IDENTITY, PROP_DEFAULT_MEMO_LIST, PROP_DEFAULT_TASK_LIST }; enum { SOURCE_ADDED, SOURCE_CHANGED, SOURCE_REMOVED, SOURCE_ENABLED, SOURCE_DISABLED, CREDENTIALS_REQUIRED, LAST_SIGNAL }; /* Forward Declarations */ static void source_registry_add_source (ESourceRegistry *registry, ESource *source); static void e_source_registry_initable_init (GInitableIface *iface); /* Private ESource function, for our use only. */ void __e_source_private_replace_dbus_object (ESource *source, GDBusObject *dbus_object); static guint signals[LAST_SIGNAL]; /* By default, the GAsyncInitable interface calls GInitable.init() * from a separate thread, so we only have to override GInitable. */ G_DEFINE_TYPE_WITH_CODE ( ESourceRegistry, e_source_registry, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE ( G_TYPE_INITABLE, e_source_registry_initable_init) G_IMPLEMENT_INTERFACE ( G_TYPE_ASYNC_INITABLE, NULL)) static void async_context_free (AsyncContext *async_context) { if (async_context->source != NULL) g_object_unref (async_context->source); g_list_free_full ( async_context->list_of_sources, (GDestroyNotify) g_object_unref); g_slice_free (AsyncContext, async_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) { g_weak_ref_clear (&closure->registry); g_object_unref (closure->source); g_slice_free (SourceClosure, closure); } static void thread_closure_free (ThreadClosure *closure) { /* The registry member is not referenced. */ g_main_context_unref (closure->main_context); g_main_loop_unref (closure->main_loop); 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); } static void credentials_required_closure_free (gpointer ptr) { CredentialsRequiredClosure *closure = ptr; if (closure) { g_weak_ref_clear (&closure->registry); g_object_unref (closure->source); g_free (closure->certificate_pem); g_clear_error (&closure->op_error); g_slice_free (CredentialsRequiredClosure, closure); } }; G_LOCK_DEFINE_STATIC (singleton_lock); static GWeakRef singleton; static ESourceRegistry * source_registry_dup_uninitialized_singleton (void) { ESourceRegistry *registry; G_LOCK (singleton_lock); registry = g_weak_ref_get (&singleton); if (registry == NULL) { registry = g_object_new (E_TYPE_SOURCE_REGISTRY, NULL); g_weak_ref_set (&singleton, registry); } G_UNLOCK (singleton_lock); return registry; } static gchar * source_registry_dbus_object_dup_uid (GDBusObject *dbus_object) { EDBusObject *e_dbus_object; EDBusSource *e_dbus_source; /* EDBusSource interface should always be present. */ e_dbus_object = E_DBUS_OBJECT (dbus_object); e_dbus_source = e_dbus_object_peek_source (e_dbus_object); return e_dbus_source_dup_uid (e_dbus_source); } static void source_registry_object_path_table_insert (ESourceRegistry *registry, const gchar *object_path, ESource *source) { GHashTable *object_path_table; g_return_if_fail (object_path != NULL); g_return_if_fail (E_IS_SOURCE (source)); object_path_table = registry->priv->object_path_table; g_mutex_lock (®istry->priv->object_path_table_lock); g_hash_table_insert ( object_path_table, g_strdup (object_path), g_object_ref (source)); g_mutex_unlock (®istry->priv->object_path_table_lock); } static ESource * source_registry_object_path_table_lookup (ESourceRegistry *registry, const gchar *object_path) { GHashTable *object_path_table; ESource *source; g_return_val_if_fail (object_path != NULL, NULL); object_path_table = registry->priv->object_path_table; g_mutex_lock (®istry->priv->object_path_table_lock); source = g_hash_table_lookup (object_path_table, object_path); if (source != NULL) g_object_ref (source); g_mutex_unlock (®istry->priv->object_path_table_lock); return source; } static gboolean source_registry_object_path_table_remove (ESourceRegistry *registry, const gchar *object_path) { GHashTable *object_path_table; gboolean removed; g_return_val_if_fail (object_path != NULL, FALSE); object_path_table = registry->priv->object_path_table; g_mutex_lock (®istry->priv->object_path_table_lock); removed = g_hash_table_remove (object_path_table, object_path); g_mutex_unlock (®istry->priv->object_path_table_lock); return removed; } static void source_registry_service_restart_table_add (ESourceRegistry *registry, const gchar *uid) { GHashTable *service_restart_table; g_return_if_fail (uid != NULL); service_restart_table = registry->priv->service_restart_table; g_mutex_lock (®istry->priv->service_restart_table_lock); g_hash_table_add (service_restart_table, g_strdup (uid)); g_mutex_unlock (®istry->priv->service_restart_table_lock); } static gboolean source_registry_service_restart_table_remove (ESourceRegistry *registry, const gchar *uid) { GHashTable *service_restart_table; gboolean removed; g_return_val_if_fail (uid != NULL, FALSE); service_restart_table = registry->priv->service_restart_table; g_mutex_lock (®istry->priv->service_restart_table_lock); removed = g_hash_table_remove (service_restart_table, uid); g_mutex_unlock (®istry->priv->service_restart_table_lock); return removed; } static GList * source_registry_service_restart_table_steal_all (ESourceRegistry *registry) { GHashTable *service_restart_table; GList *list; service_restart_table = registry->priv->service_restart_table; g_mutex_lock (®istry->priv->service_restart_table_lock); list = g_hash_table_get_keys (service_restart_table); g_hash_table_steal_all (service_restart_table); g_mutex_unlock (®istry->priv->service_restart_table_lock); return list; } static gboolean source_registry_sources_remove (ESourceRegistry *registry, ESource *source) { const gchar *uid; gboolean removed; uid = e_source_get_uid (source); g_return_val_if_fail (uid != NULL, FALSE); g_mutex_lock (®istry->priv->sources_lock); removed = g_hash_table_remove (registry->priv->sources, uid); g_mutex_unlock (®istry->priv->sources_lock); return removed; } static ESource * source_registry_sources_lookup (ESourceRegistry *registry, const gchar *uid) { ESource *source; g_return_val_if_fail (uid != NULL, NULL); g_mutex_lock (®istry->priv->sources_lock); source = g_hash_table_lookup (registry->priv->sources, uid); if (source != NULL) g_object_ref (source); g_mutex_unlock (®istry->priv->sources_lock); return source; } static GList * source_registry_sources_get_values (ESourceRegistry *registry) { GList *values; g_mutex_lock (®istry->priv->sources_lock); values = g_hash_table_get_values (registry->priv->sources); g_list_foreach (values, (GFunc) g_object_ref, NULL); g_mutex_unlock (®istry->priv->sources_lock); return values; } static GNode * source_registry_sources_build_tree (ESourceRegistry *registry) { GNode *root; GHashTable *index; GHashTableIter iter; gpointer key, value; g_mutex_lock (®istry->priv->sources_lock); root = g_node_new (NULL); index = g_hash_table_new (g_str_hash, g_str_equal); /* Add a GNode for each ESource to the index. */ g_hash_table_iter_init (&iter, registry->priv->sources); while (g_hash_table_iter_next (&iter, &key, &value)) { ESource *source = g_object_ref (value); g_hash_table_insert (index, key, g_node_new (source)); } /* Traverse the index and link the nodes together. */ g_hash_table_iter_init (&iter, index); while (g_hash_table_iter_next (&iter, NULL, &value)) { ESource *source; GNode *source_node; GNode *parent_node; const gchar *parent_uid; source_node = (GNode *) value; source = E_SOURCE (source_node->data); parent_uid = e_source_get_parent (source); if (parent_uid == NULL || *parent_uid == '\0') { parent_node = root; } else { parent_node = g_hash_table_lookup (index, parent_uid); g_warn_if_fail (parent_node != NULL); } /* Should never be NULL, but just to be safe. */ if (parent_node != NULL) g_node_append (parent_node, source_node); } g_hash_table_destroy (index); g_mutex_unlock (®istry->priv->sources_lock); return root; } static void source_registry_settings_changed_cb (GSettings *settings, const gchar *key, ESourceRegistry *registry) { /* We define a property name that matches every key in * the "org.gnome.Evolution.DefaultSources" schema. */ g_object_notify (G_OBJECT (registry), key); } static gboolean source_registry_source_changed_idle_cb (gpointer user_data) { SourceClosure *closure = user_data; ESourceRegistry *registry; registry = g_weak_ref_get (&closure->registry); if (registry != NULL) { g_signal_emit ( registry, signals[SOURCE_CHANGED], 0, closure->source); g_object_unref (registry); } return FALSE; } static gboolean source_registry_source_notify_enabled_idle_cb (gpointer user_data) { SourceClosure *closure = user_data; ESourceRegistry *registry; registry = g_weak_ref_get (&closure->registry); if (registry != NULL) { if (e_source_get_enabled (closure->source)) { g_signal_emit ( registry, signals[SOURCE_ENABLED], 0, closure->source); } else { g_signal_emit ( registry, signals[SOURCE_DISABLED], 0, closure->source); } g_object_unref (registry); } return FALSE; } static void source_registry_source_changed_cb (ESource *source, ESourceRegistry *registry) { GSource *idle_source; SourceClosure *closure; closure = g_slice_new0 (SourceClosure); g_weak_ref_init (&closure->registry, registry); closure->source = g_object_ref (source); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, source_registry_source_changed_idle_cb, closure, (GDestroyNotify) source_closure_free); g_source_attach (idle_source, registry->priv->main_context); g_source_unref (idle_source); } static void source_registry_source_notify_enabled_cb (ESource *source, GParamSpec *pspec, ESourceRegistry *registry) { GSource *idle_source; SourceClosure *closure; closure = g_slice_new0 (SourceClosure); g_weak_ref_init (&closure->registry, registry); closure->source = g_object_ref (source); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, source_registry_source_notify_enabled_idle_cb, closure, (GDestroyNotify) source_closure_free); g_source_attach (idle_source, registry->priv->main_context); g_source_unref (idle_source); } static gboolean source_registry_source_credentials_required_idle_cb (gpointer user_data) { CredentialsRequiredClosure *closure = user_data; ESourceRegistry *registry; registry = g_weak_ref_get (&closure->registry); if (registry != NULL) { g_signal_emit ( registry, signals[CREDENTIALS_REQUIRED], 0, closure->source, closure->reason, closure->certificate_pem, closure->certificate_errors, closure->op_error); g_object_unref (registry); } return FALSE; } static void source_registry_source_credentials_required_cb (ESource *source, ESourceCredentialsReason reason, const gchar *certificate_pem, GTlsCertificateFlags certificate_errors, const GError *op_error, ESourceRegistry *registry) { GSource *idle_source; CredentialsRequiredClosure *closure; closure = g_slice_new0 (CredentialsRequiredClosure); g_weak_ref_init (&closure->registry, registry); closure->source = g_object_ref (source); closure->reason = reason; closure->certificate_pem = g_strdup (certificate_pem); closure->certificate_errors = certificate_errors; closure->op_error = op_error ? g_error_copy (op_error) : NULL; idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, source_registry_source_credentials_required_idle_cb, closure, credentials_required_closure_free); g_source_attach (idle_source, registry->priv->main_context); 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 *local_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, &local_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 (local_error != NULL) { g_warn_if_fail (source == NULL); g_critical ( "ESourceRegistry: Failed to create a " "data source object for path '%s': %s", object_path, local_error->message); g_error_free (local_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) { g_signal_handlers_disconnect_matched ( source, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, source_registry_source_changed_cb, NULL); g_signal_handlers_disconnect_matched ( source, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, source_registry_source_notify_enabled_cb, NULL); g_signal_handlers_disconnect_matched ( source, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, source_registry_source_credentials_required_cb, NULL); g_object_unref (source); } static void source_registry_add_source (ESourceRegistry *registry, ESource *source) { 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 (®istry->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 (®istry->priv->sources_lock); return; } g_signal_connect ( source, "changed", G_CALLBACK (source_registry_source_changed_cb), registry); g_signal_connect ( source, "notify::enabled", G_CALLBACK (source_registry_source_notify_enabled_cb), registry); g_signal_connect ( source, "credentials-required", G_CALLBACK (source_registry_source_credentials_required_cb), registry); g_hash_table_insert ( registry->priv->sources, g_strdup (uid), g_object_ref (source)); g_mutex_unlock (®istry->priv->sources_lock); } static gboolean source_registry_object_added_idle_cb (gpointer user_data) { SourceClosure *closure = user_data; ESourceRegistry *registry; registry = g_weak_ref_get (&closure->registry); if (registry != NULL) { g_signal_emit ( registry, signals[SOURCE_ADDED], 0, closure->source); g_object_unref (registry); } return FALSE; } static void source_registry_object_added_by_owner (ESourceRegistry *registry, GDBusObject *dbus_object) { SourceClosure *closure; GSource *idle_source; ESource *source; g_return_if_fail (E_DBUS_IS_OBJECT (dbus_object)); source = source_registry_new_source (registry, dbus_object); g_return_if_fail (source != NULL); /* 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. */ closure = g_slice_new0 (SourceClosure); g_weak_ref_init (&closure->registry, registry); closure->source = g_object_ref (source); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, source_registry_object_added_idle_cb, closure, (GDestroyNotify) source_closure_free); g_source_attach (idle_source, registry->priv->main_context); g_source_unref (idle_source); g_object_unref (source); } static void source_registry_object_added_no_owner (ESourceRegistry *registry, GDBusObject *dbus_object) { ESource *source = NULL; gchar *uid; uid = source_registry_dbus_object_dup_uid (dbus_object); if (source_registry_service_restart_table_remove (registry, uid)) source = e_source_registry_ref_source (registry, uid); if (source != NULL) { const gchar *object_path; object_path = g_dbus_object_get_object_path (dbus_object); source_registry_object_path_table_insert ( registry, object_path, source); __e_source_private_replace_dbus_object (source, dbus_object); g_object_unref (source); } else { source_registry_object_added_by_owner (registry, dbus_object); } g_free (uid); } static void source_registry_object_added_cb (GDBusObjectManager *object_manager, GDBusObject *dbus_object, ESourceRegistry *registry) { gchar *name_owner; name_owner = g_dbus_object_manager_client_get_name_owner ( G_DBUS_OBJECT_MANAGER_CLIENT (object_manager)); if (name_owner != NULL) source_registry_object_added_by_owner (registry, dbus_object); else source_registry_object_added_no_owner (registry, dbus_object); g_free (name_owner); } static gboolean source_registry_object_removed_idle_cb (gpointer user_data) { SourceClosure *closure = user_data; ESourceRegistry *registry; registry = g_weak_ref_get (&closure->registry); if (registry != NULL) { g_signal_emit ( registry, signals[SOURCE_REMOVED], 0, closure->source); g_object_unref (registry); } return FALSE; } static void source_registry_object_removed_by_owner (ESourceRegistry *registry, GDBusObject *dbus_object) { SourceClosure *closure; GSource *idle_source; ESource *source; const gchar *object_path; /* Find the corresponding ESource in the object path table. * Note that the lookup returns a new ESource reference. */ object_path = g_dbus_object_get_object_path (dbus_object); source = source_registry_object_path_table_lookup ( registry, object_path); g_return_if_fail (E_IS_SOURCE (source)); /* Remove the ESource from the object path table immediately. */ source_registry_object_path_table_remove (registry, object_path); /* Also remove the ESource from the sources table immediately. */ if (!source_registry_sources_remove (registry, source)) { g_object_unref (source); g_return_if_reached (); } /* Strip the ESource of its GDBusObject. */ __e_source_private_replace_dbus_object (source, NULL); /* Schedule a callback on the ESourceRegistry's GMainContext. */ closure = g_slice_new0 (SourceClosure); g_weak_ref_init (&closure->registry, registry); closure->source = g_object_ref (source); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, source_registry_object_removed_idle_cb, closure, (GDestroyNotify) source_closure_free); g_source_attach (idle_source, registry->priv->main_context); g_source_unref (idle_source); g_object_unref (source); } static void source_registry_object_removed_no_owner (ESourceRegistry *registry, GDBusObject *dbus_object) { const gchar *object_path; object_path = g_dbus_object_get_object_path (dbus_object); if (source_registry_object_path_table_remove (registry, object_path)) { gchar *uid; uid = source_registry_dbus_object_dup_uid (dbus_object); source_registry_service_restart_table_add (registry, uid); g_free (uid); } } static void source_registry_object_removed_cb (GDBusObjectManager *object_manager, GDBusObject *dbus_object, ESourceRegistry *registry) { gchar *name_owner; name_owner = g_dbus_object_manager_client_get_name_owner ( G_DBUS_OBJECT_MANAGER_CLIENT (object_manager)); if (name_owner != NULL) source_registry_object_removed_by_owner (registry, dbus_object); else source_registry_object_removed_no_owner (registry, dbus_object); g_free (name_owner); } static void source_registry_name_appeared (ESourceRegistry *registry) { GList *list, *link; /* The D-Bus service restarted, and the GDBusObjectManager has * just set its "name-owner" property having finished emitting * an "object-added" signal for each GDBusObject. */ list = source_registry_service_restart_table_steal_all (registry); for (link = list; link != NULL; link = g_list_next (link)) { SourceClosure *closure; GSource *idle_source; ESource *source; const gchar *uid = link->data; source = e_source_registry_ref_source (registry, uid); if (source == NULL) continue; closure = g_slice_new0 (SourceClosure); g_weak_ref_init (&closure->registry, registry); closure->source = g_object_ref (source); idle_source = g_idle_source_new (); g_source_set_callback ( idle_source, source_registry_object_removed_idle_cb, closure, (GDestroyNotify) source_closure_free); g_source_attach (idle_source, registry->priv->main_context); g_source_unref (idle_source); g_object_unref (source); } g_list_free_full (list, (GDestroyNotify) g_free); } static void source_registry_name_vanished (ESourceRegistry *registry) { /* This function is just a convenience breakpoint. The D-Bus * service aborted, so the GDBusObjectManager has cleared its * "name-owner" property and will now emit a "object-removed" * signal for each GDBusObject. */ } static void source_registry_notify_name_owner_cb (GDBusObjectManager *object_manager, GParamSpec *pspec, ESourceRegistry *registry) { gchar *name_owner; name_owner = g_dbus_object_manager_client_get_name_owner ( G_DBUS_OBJECT_MANAGER_CLIENT (object_manager)); if (name_owner != NULL) source_registry_name_appeared (registry); else source_registry_name_vanished (registry); g_free (name_owner); } static gboolean 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); return FALSE; } static gpointer source_registry_object_manager_thread (gpointer data) { GDBusObjectManager *object_manager; ThreadClosure *closure = data; GSource *idle_source; GList *list, *link; gulong object_added_handler_id = 0; gulong object_removed_handler_id = 0; gulong notify_name_owner_handler_id = 0; /* GDBusObjectManagerClient grabs the thread-default GMainContext * at creation time and only emits signals from that GMainContext. * Running it in a separate thread prevents its signal emissions * from being inhibited by someone overriding the thread-default * GMainContext. */ /* This becomes the GMainContext that GDBusObjectManagerClient * will emit signals from. Make it the thread-default context * for this thread before creating the client. */ g_main_context_push_thread_default (closure->main_context); object_manager = e_dbus_object_manager_client_new_for_bus_sync ( G_BUS_TYPE_SESSION, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, SOURCES_DBUS_SERVICE_NAME, DBUS_OBJECT_PATH, NULL, &closure->error); /* 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 = g_object_ref (object_manager); /* Now populate the registry with an initial set of ESources. */ list = g_dbus_object_manager_get_objects (object_manager); for (link = list; link != NULL; link = g_list_next (link)) { GDBusObject *dbus_object; ESource *source; dbus_object = G_DBUS_OBJECT (link->data); source = source_registry_new_source ( closure->registry, dbus_object); 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. */ object_added_handler_id = g_signal_connect ( object_manager, "object-added", G_CALLBACK (source_registry_object_added_cb), closure->registry); object_removed_handler_id = g_signal_connect ( object_manager, "object-removed", G_CALLBACK (source_registry_object_removed_cb), closure->registry); notify_name_owner_handler_id = g_signal_connect ( object_manager, "notify::name-owner", G_CALLBACK (source_registry_notify_name_owner_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. */ if (object_manager != NULL) { g_signal_handler_disconnect ( object_manager, object_added_handler_id); g_signal_handler_disconnect ( object_manager, object_removed_handler_id); g_signal_handler_disconnect ( object_manager, notify_name_owner_handler_id); g_object_unref (object_manager); } g_main_context_pop_thread_default (closure->main_context); return NULL; } static void source_registry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_DEFAULT_ADDRESS_BOOK: e_source_registry_set_default_address_book ( E_SOURCE_REGISTRY (object), g_value_get_object (value)); return; case PROP_DEFAULT_CALENDAR: e_source_registry_set_default_calendar ( E_SOURCE_REGISTRY (object), g_value_get_object (value)); return; case PROP_DEFAULT_MAIL_ACCOUNT: e_source_registry_set_default_mail_account ( E_SOURCE_REGISTRY (object), g_value_get_object (value)); return; case PROP_DEFAULT_MAIL_IDENTITY: e_source_registry_set_default_mail_identity ( E_SOURCE_REGISTRY (object), g_value_get_object (value)); return; case PROP_DEFAULT_MEMO_LIST: e_source_registry_set_default_memo_list ( E_SOURCE_REGISTRY (object), g_value_get_object (value)); return; case PROP_DEFAULT_TASK_LIST: e_source_registry_set_default_task_list ( E_SOURCE_REGISTRY (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void source_registry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_DEFAULT_ADDRESS_BOOK: g_value_take_object ( value, e_source_registry_ref_default_address_book ( E_SOURCE_REGISTRY (object))); return; case PROP_DEFAULT_CALENDAR: g_value_take_object ( value, e_source_registry_ref_default_calendar ( E_SOURCE_REGISTRY (object))); return; case PROP_DEFAULT_MAIL_ACCOUNT: g_value_take_object ( value, e_source_registry_ref_default_mail_account ( E_SOURCE_REGISTRY (object))); return; case PROP_DEFAULT_MAIL_IDENTITY: g_value_take_object ( value, e_source_registry_ref_default_mail_identity ( E_SOURCE_REGISTRY (object))); return; case PROP_DEFAULT_MEMO_LIST: g_value_take_object ( value, e_source_registry_ref_default_memo_list ( E_SOURCE_REGISTRY (object))); return; case PROP_DEFAULT_TASK_LIST: g_value_take_object ( value, e_source_registry_ref_default_task_list ( E_SOURCE_REGISTRY (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void source_registry_dispose (GObject *object) { ESourceRegistryPrivate *priv; priv = E_SOURCE_REGISTRY_GET_PRIVATE (object); /* Terminate the manager thread first. */ if (priv->manager_thread != NULL) { g_main_loop_quit (priv->thread_closure->main_loop); g_thread_join (priv->manager_thread); thread_closure_free (priv->thread_closure); priv->manager_thread = NULL; priv->thread_closure = NULL; } if (priv->dbus_object_manager != NULL) { g_object_unref (priv->dbus_object_manager); priv->dbus_object_manager = NULL; } if (priv->dbus_source_manager != NULL) { g_object_unref (priv->dbus_source_manager); priv->dbus_source_manager = NULL; } g_hash_table_remove_all (priv->object_path_table); g_hash_table_remove_all (priv->sources); if (priv->main_context != NULL) { while (g_main_context_pending (priv->main_context)) { g_main_context_iteration (priv->main_context, FALSE); } g_main_context_unref (priv->main_context); priv->main_context = NULL; } if (priv->settings != NULL) { g_signal_handlers_disconnect_by_data (priv->settings, object); g_object_unref (priv->settings); priv->settings = NULL; } /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_source_registry_parent_class)->dispose (object); } static void source_registry_finalize (GObject *object) { ESourceRegistryPrivate *priv; priv = E_SOURCE_REGISTRY_GET_PRIVATE (object); g_hash_table_destroy (priv->object_path_table); g_mutex_clear (&priv->object_path_table_lock); g_hash_table_destroy (priv->service_restart_table); g_mutex_clear (&priv->service_restart_table_lock); g_hash_table_destroy (priv->sources); g_mutex_clear (&priv->sources_lock); g_clear_error (&priv->init_error); g_mutex_clear (&priv->init_lock); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_source_registry_parent_class)->finalize (object); } static gboolean source_registry_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { ESourceRegistry *registry; ThreadClosure *closure; GError *local_error = NULL; registry = E_SOURCE_REGISTRY (initable); g_mutex_lock (®istry->priv->init_lock); if (registry->priv->initialized) goto exit; closure = g_slice_new0 (ThreadClosure); closure->registry = registry; /* do not reference */ closure->main_context = g_main_context_new (); /* It's important to pass 'is_running=FALSE' here because * 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); 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_new ( NULL, source_registry_object_manager_thread, closure); /* Wait for notification that the manager * thread's main loop has been started. */ 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); /* Check for error in the manager thread. */ if (closure->error != NULL) { g_dbus_error_strip_remote_error (closure->error); g_propagate_error (®istry->priv->init_error, closure->error); closure->error = NULL; goto exit; } /* 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 * GDBusObjectManagerClient and doesn't need its own thread. */ registry->priv->dbus_source_manager = e_dbus_source_manager_proxy_new_for_bus_sync ( G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, SOURCES_DBUS_SERVICE_NAME, DBUS_OBJECT_PATH, cancellable, &local_error); if (local_error != NULL) { g_dbus_error_strip_remote_error (local_error); g_propagate_error (®istry->priv->init_error, local_error); goto exit; } exit: registry->priv->initialized = TRUE; g_mutex_unlock (®istry->priv->init_lock); if (registry->priv->init_error != NULL) { GError *init_error_copy; /* Return a copy of the same error to * all pending initialization requests. */ init_error_copy = g_error_copy (registry->priv->init_error); g_propagate_error (error, init_error_copy); return FALSE; } return TRUE; } static void e_source_registry_class_init (ESourceRegistryClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (ESourceRegistryPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = source_registry_set_property; object_class->get_property = source_registry_get_property; object_class->dispose = source_registry_dispose; object_class->finalize = source_registry_finalize; /* The property names correspond to the key names in the * "org.gnome.Evolution.DefaultSources" GSettings schema. */ /** * ESourceRegistry:default-address-book: * * The default address book #ESource. **/ g_object_class_install_property ( object_class, PROP_DEFAULT_ADDRESS_BOOK, g_param_spec_object ( "default-address-book", "Default Address Book", "The default address book ESource", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ESourceRegistry:default-calendar: * * The default calendar #ESource. **/ g_object_class_install_property ( object_class, PROP_DEFAULT_CALENDAR, g_param_spec_object ( "default-calendar", "Default Calendar", "The default calendar ESource", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ESourceRegistry:default-mail-account: * * The default mail account #ESource. **/ g_object_class_install_property ( object_class, PROP_DEFAULT_MAIL_ACCOUNT, g_param_spec_object ( "default-mail-account", "Default Mail Account", "The default mail account ESource", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ESourceRegistry:default-mail-identity: * * The default mail identity #ESource. **/ g_object_class_install_property ( object_class, PROP_DEFAULT_MAIL_IDENTITY, g_param_spec_object ( "default-mail-identity", "Default Mail Identity", "The default mail identity ESource", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ESourceRegistry:default-memo-list: * * The default memo list #ESource. **/ g_object_class_install_property ( object_class, PROP_DEFAULT_MEMO_LIST, g_param_spec_object ( "default-memo-list", "Default Memo List", "The default memo list ESource", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ESourceRegistry:default-task-list: * * The default task list #ESource. **/ g_object_class_install_property ( object_class, PROP_DEFAULT_TASK_LIST, g_param_spec_object ( "default-task-list", "Default Task List", "The default task list ESource", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ESourceRegistry::source-added: * @registry: the #ESourceRegistry which emitted the signal * @source: the newly-added #ESource * * Emitted when an #ESource is added to @registry. **/ signals[SOURCE_ADDED] = g_signal_new ( "source-added", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ESourceRegistryClass, source_added), NULL, NULL, NULL, G_TYPE_NONE, 1, E_TYPE_SOURCE); /** * ESourceRegistry::source-changed: * @registry: the #ESourceRegistry which emitted the signal * @source: the #ESource that changed * * Emitted when an #ESource registered with @registry emits * its #ESource::changed signal. **/ signals[SOURCE_CHANGED] = g_signal_new ( "source-changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ESourceRegistryClass, source_changed), NULL, NULL, NULL, G_TYPE_NONE, 1, E_TYPE_SOURCE); /** * ESourceRegistry::source-removed: * @registry: the #ESourceRegistry which emitted the signal * @source: the #ESource that got removed * * Emitted when an #ESource is removed from @registry. **/ signals[SOURCE_REMOVED] = g_signal_new ( "source-removed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ESourceRegistryClass, source_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, E_TYPE_SOURCE); /** * ESourceRegistry::source-enabled: * @registry: the #ESourceRegistry which emitted the signal * @source: the #ESource that got enabled * * Emitted when an #ESource #ESource:enabled property becomes %TRUE. **/ signals[SOURCE_ENABLED] = g_signal_new ( "source-enabled", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ESourceRegistryClass, source_enabled), NULL, NULL, NULL, G_TYPE_NONE, 1, E_TYPE_SOURCE); /** * ESourceRegistry::source-disabled: * @registry: the #ESourceRegistry which emitted the signal * @source: the #ESource that got disabled * * Emitted when an #ESource #ESource:enabled property becomes %FALSE. **/ signals[SOURCE_DISABLED] = g_signal_new ( "source-disabled", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ESourceRegistryClass, source_disabled), NULL, NULL, NULL, G_TYPE_NONE, 1, E_TYPE_SOURCE); /** * ESourceRegistry::credentials-required: * @registry: the #ESourceRegistry which emitted the signal * @source: the #ESource that requires credentials * @reason: an #ESourceCredentialsReason indicating why the credentials are requested * @certificate_pem: PEM-encoded secure connection certificate for failed SSL checks * @certificate_errors: what failed with the SSL certificate * @op_error: a #GError with a description of the error, or %NULL * * The ::credentials-required signal is emitted when the @source * requires credentials to connect to (possibly remote) * data store. The credentials can be passed to the source using * e_source_authenticate() function. The signal is emitted in * the thread-default main context from the time the @registry was created. * * Note: This is just a proxy signal for the ESource::credentials-required signal. **/ signals[CREDENTIALS_REQUIRED] = g_signal_new ( "credentials-required", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE, G_STRUCT_OFFSET (ESourceRegistryClass, credentials_required), NULL, NULL, NULL, G_TYPE_NONE, 5, E_TYPE_SOURCE, E_TYPE_SOURCE_CREDENTIALS_REASON, G_TYPE_STRING, G_TYPE_TLS_CERTIFICATE_FLAGS, G_TYPE_ERROR); } static void e_source_registry_initable_init (GInitableIface *iface) { iface->init = source_registry_initable_init; } static void e_source_registry_init (ESourceRegistry *registry) { registry->priv = E_SOURCE_REGISTRY_GET_PRIVATE (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_ref_thread_default (); /* D-Bus object path -> ESource */ registry->priv->object_path_table = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); g_mutex_init (®istry->priv->object_path_table_lock); /* Set of UID strings */ registry->priv->service_restart_table = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) NULL); g_mutex_init (®istry->priv->service_restart_table_lock); /* UID string -> ESource */ registry->priv->sources = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) source_registry_unref_source); g_mutex_init (®istry->priv->sources_lock); registry->priv->settings = g_settings_new (GSETTINGS_SCHEMA); g_signal_connect ( registry->priv->settings, "changed", G_CALLBACK (source_registry_settings_changed_cb), registry); g_mutex_init (®istry->priv->init_lock); } /** * e_source_registry_new_sync: * @cancellable: (allow-none): optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Creates a new #ESourceRegistry front-end for the registry D-Bus service. * If an error occurs in connecting to the D-Bus service, the function sets * @error and returns %NULL. * * Since 3.12 a singleton will be returned. No strong reference is kept * internally, so it is the caller's responsibility to keep one. * * Returns: a new #ESourceRegistry, or %NULL * * Since: 3.6 **/ ESourceRegistry * e_source_registry_new_sync (GCancellable *cancellable, GError **error) { ESourceRegistry *registry; /* 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); registry = source_registry_dup_uninitialized_singleton (); if (!g_initable_init (G_INITABLE (registry), cancellable, error)) g_clear_object (®istry); return registry; } /* Helper for e_source_registry_new() */ static void source_registry_init_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GTask *task = user_data; GError *local_error = NULL; g_async_initable_init_finish ( G_ASYNC_INITABLE (source_object), result, &local_error); if (local_error == NULL) { g_task_return_pointer ( task, g_object_ref (source_object), (GDestroyNotify) g_object_unref); } else { g_task_return_error (task, local_error); } g_object_unref (task); } /** * e_source_registry_new: * @cancellable: (allow-none): optional #GCancellable object, or %NULL * @callback: (scope async): a #GAsyncReadyCallback to call when the request * is satisfied * @user_data: (closure): data to pass to the callback function * * Asynchronously creates a new #ESourceRegistry front-end for the registry * D-Bus service. * * When the operation is finished, @callback will be called. You can then * call e_source_registry_new_finish() to get the result of the operation. * * Since 3.12 a singleton will be returned. No strong reference is kept * internally, so it is the caller's responsibility to keep one. * * Since: 3.6 **/ void e_source_registry_new (GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { ESourceRegistry *registry; GTask *task; task = g_task_new (NULL, cancellable, callback, user_data); registry = source_registry_dup_uninitialized_singleton (); g_async_initable_init_async ( G_ASYNC_INITABLE (registry), G_PRIORITY_DEFAULT, cancellable, source_registry_init_cb, task); g_object_unref (registry); } /** * e_source_registry_new_finish: * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with e_source_registry_new_finish(). * If an error occurs in connecting to the D-Bus service, the function * sets @error and returns %NULL. * * Returns: a new #ESourceRegistry, or %NULL * * Since: 3.6 **/ ESourceRegistry * e_source_registry_new_finish (GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, NULL), NULL); return g_task_propagate_pointer (G_TASK (result), error); } /* Helper for e_source_registry_commit_source() */ static void source_registry_commit_source_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { AsyncContext *async_context; GError *local_error = NULL; async_context = g_simple_async_result_get_op_res_gpointer (simple); e_source_registry_commit_source_sync ( E_SOURCE_REGISTRY (object), async_context->source, cancellable, &local_error); if (local_error != NULL) g_simple_async_result_take_error (simple, local_error); } /** * e_source_registry_commit_source_sync: * @registry: an #ESourceRegistry * @source: an #ESource with changes to commit * @cancellable: (allow-none): optional #GCancellable object, or %NULL * @error: return location for #GError, or %NULL * * This is a convenience function intended for use with graphical * #ESource editors. Call this function when the user is finished * making changes to @source. * * If @source has a #GDBusObject, its contents are submitted to the D-Bus * service through e_source_write_sync(). * * If @source does NOT have a #GDBusObject (implying it's a scratch * #ESource), its contents are submitted to the D-Bus service through * 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. * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_registry_commit_source_sync (ESourceRegistry *registry, ESource *source, GCancellable *cancellable, GError **error) { GDBusObject *dbus_object; ESource *collection_source; gboolean collection_member; gboolean success; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); 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 ( registry, list, cancellable, error); g_list_free (list); } if (collection_source != NULL) g_object_unref (collection_source); return success; } /** * e_source_registry_commit_source: * @registry: an #ESourceRegistry * @source: an #ESource with changes to commit * @cancellable: (allow-none): optional #GCancellable object, or %NULL * @callback: (scope async): a #GAsyncReadyCallback to call when the request * is satisfied * @user_data: (closure): data to pass to the callback function * * See e_source_registry_commit_source_sync() for details. * * When the operation is finished, @callback will be called. You can then * call e_source_registry_commit_source_finish() to get the result of the * operation. * * Since: 3.6 **/ void e_source_registry_commit_source (ESourceRegistry *registry, ESource *source, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *simple; AsyncContext *async_context; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (E_IS_SOURCE (source)); async_context = g_slice_new0 (AsyncContext); async_context->source = g_object_ref (source); simple = g_simple_async_result_new ( G_OBJECT (registry), callback, user_data, e_source_registry_commit_source); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_set_op_res_gpointer ( simple, async_context, (GDestroyNotify) async_context_free); g_simple_async_result_run_in_thread ( simple, source_registry_commit_source_thread, G_PRIORITY_DEFAULT, cancellable); g_object_unref (simple); } /** * e_source_registry_commit_source_finish: * @registry: an #ESourceRegistry * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with e_source_registry_commit_source(). * * If an error occurred, the function will set @error and return %FALSE. * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_registry_commit_source_finish (ESourceRegistry *registry, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (registry), e_source_registry_commit_source), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); /* Assume success unless a GError is set. */ return !g_simple_async_result_propagate_error (simple, error); } /* Helper for e_source_registry_create_sources() */ static void source_registry_create_sources_thread (GSimpleAsyncResult *simple, GObject *object, GCancellable *cancellable) { AsyncContext *async_context; GError *local_error = NULL; async_context = g_simple_async_result_get_op_res_gpointer (simple); e_source_registry_create_sources_sync ( E_SOURCE_REGISTRY (object), async_context->list_of_sources, cancellable, &local_error); if (local_error != NULL) g_simple_async_result_take_error (simple, local_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) { gchar *uid; uid = source_registry_dbus_object_dup_uid (dbus_object); if (uid != NULL) { g_hash_table_remove (create_context->pending_uids, uid); g_free (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: (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 * * Requests the D-Bus service create new key files for each #ESource in * @list_of_sources. Each list element must be a scratch #ESource with * no #GDBusObject. * * If an error occurs, the function will set @error and return %FALSE. * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_registry_create_sources_sync (ESourceRegistry *registry, GList *list_of_sources, GCancellable *cancellable, GError **error) { CreateContext *create_context; GVariantBuilder builder; GVariant *variant; GList *link; gulong object_added_id; GError *local_error = NULL; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE); /* Verify the list elements are all ESources. */ 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; gchar *source_data; gchar *uid; source = E_SOURCE (link->data); 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); g_free (source_data); } 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. */ e_dbus_source_manager_call_create_sources_sync ( registry->priv->dbus_source_manager, variant, cancellable, &local_error); 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 (local_error == NULL) { 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); if (local_error != NULL) { g_dbus_error_strip_remote_error (local_error); g_propagate_error (error, local_error); return FALSE; } return TRUE; } /** * e_source_registry_create_sources: * @registry: an #ESourceRegistry * @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 * @user_data: (closure): data to pass to the callback function * * Asynchronously requests the D-Bus service create new key files for each * #ESource in @list_of_sources. Each list element must be a scratch * #ESource with no #GDBusObject. * * When the operation is finished, @callback will be called. You can then * call e_source_registry_create_sources_finish() to get the result of the * operation. * * Since: 3.6 **/ void e_source_registry_create_sources (ESourceRegistry *registry, GList *list_of_sources, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *simple; AsyncContext *async_context; GList *link; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); /* Verify the list elements are all ESources. */ for (link = list_of_sources; link != NULL; link = g_list_next (link)) g_return_if_fail (E_IS_SOURCE (link->data)); async_context = g_slice_new0 (AsyncContext); async_context->list_of_sources = g_list_copy (list_of_sources); g_list_foreach ( async_context->list_of_sources, (GFunc) g_object_ref, NULL); simple = g_simple_async_result_new ( G_OBJECT (registry), callback, user_data, e_source_registry_create_sources); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_set_op_res_gpointer ( simple, async_context, (GDestroyNotify) async_context_free); g_simple_async_result_run_in_thread ( simple, source_registry_create_sources_thread, G_PRIORITY_DEFAULT, cancellable); g_object_unref (simple); } /** * e_source_registry_create_sources_finish: * @registry: an #ESourceRegistry * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with e_source_registry_create_sources(). * * If an error occurred, the function will set @error and return %FALSE. * * Returns: %TRUE on success, %FALSE on failure * * Since: 3.6 **/ gboolean e_source_registry_create_sources_finish (ESourceRegistry *registry, GAsyncResult *result, GError **error) { GSimpleAsyncResult *simple; g_return_val_if_fail ( g_simple_async_result_is_valid ( result, G_OBJECT (registry), e_source_registry_create_sources), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); /* Assume success unless a GError is set. */ return !g_simple_async_result_propagate_error (simple, error); } /** * e_source_registry_ref_source: * @registry: an #ESourceRegistry * @uid: a unique identifier string * * Looks up an #ESource in @registry by its unique identifier string. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): an #ESource, or %NULL if no match was found * * Since: 3.6 **/ ESource * e_source_registry_ref_source (ESourceRegistry *registry, const gchar *uid) { g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); g_return_val_if_fail (uid != NULL, NULL); return source_registry_sources_lookup (registry, uid); } /** * e_source_registry_list_sources: * @registry: an #ESourceRegistry * @extension_name: (allow-none): an extension name, or %NULL * * Returns a list of registered sources, sorted by display name. If * @extension_name is given, restrict the list to sources having that * extension name. * * The sources returned in the list are referenced for thread-safety. * They must each be unreferenced with g_object_unref() when finished * with them. Free the returned list itself with g_list_free(). * * An easy way to free the list properly in one step is as follows: * * |[ * g_list_free_full (list, g_object_unref); * ]| * * Returns: (element-type ESource) (transfer full): a sorted list of sources * * Since: 3.6 **/ GList * e_source_registry_list_sources (ESourceRegistry *registry, const gchar *extension_name) { GList *list, *link; GQueue trash = G_QUEUE_INIT; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); list = g_list_sort ( source_registry_sources_get_values (registry), (GCompareFunc) e_source_compare_by_display_name); if (extension_name == NULL) return list; for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); if (!e_source_has_extension (source, extension_name)) { g_queue_push_tail (&trash, link); g_object_unref (source); } } /* We do want pop_head() here, not pop_head_link(). */ while ((link = g_queue_pop_head (&trash)) != NULL) list = g_list_delete_link (list, link); return list; } /** * e_source_registry_list_enabled: * @registry: an #ESourceRegistry * @extension_name: (allow-none): an extension name, or %NULL * * Similar to e_source_registry_list_sources(), but returns only enabled * sources according to e_source_registry_check_enabled(). * * The sources returned in the list are referenced for thread-safety. * They must each be unreferenced with g_object_unref() when finished * with them. Free the returned list itself with g_list_free(). * * An easy way to free the list properly in one step is as follows: * * |[ * g_list_free_full (list, g_object_unref); * ]| * * Returns: (element-type ESource) (transfer full): a sorted list of sources * * Since: 3.10 **/ GList * e_source_registry_list_enabled (ESourceRegistry *registry, const gchar *extension_name) { GList *list, *link; GQueue trash = G_QUEUE_INIT; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); list = e_source_registry_list_sources (registry, extension_name); for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); if (!e_source_registry_check_enabled (registry, source)) { g_queue_push_tail (&trash, link); g_object_unref (source); } } /* We do want pop_head() here, not pop_head_link(). */ while ((link = g_queue_pop_head (&trash)) != NULL) list = g_list_delete_link (list, link); return list; } /** * e_source_registry_find_extension: * @registry: an #ESourceRegistry * @source: an #ESource * @extension_name: the extension name to find * * Examines @source and its ancestors and returns the "deepest" #ESource * having an #ESourceExtension with the given @extension_name. If neither * @source nor any of its ancestors have such an extension, the function * returns %NULL. * * This function is useful in cases when an #ESourceExtension is meant to * apply to both the #ESource it belongs to and the #ESource's descendants. * * A common example is the #ESourceCollection extension, where descendants * of an #ESource having an #ESourceCollection extension are implied to be * members of that collection. In that example, this function can be used * to test whether @source is a member of a collection. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Note the function returns the #ESource containing the #ESourceExtension * instead of the #ESourceExtension itself because extension instances are * not to be referenced directly (see e_source_get_extension()). * * Returns: (transfer full): an #ESource, or %NULL if no match was found * * Since: 3.6 **/ ESource * e_source_registry_find_extension (ESourceRegistry *registry, ESource *source, const gchar *extension_name) { g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); g_return_val_if_fail (E_IS_SOURCE (source), NULL); g_return_val_if_fail (extension_name != NULL, NULL); g_object_ref (source); while (!e_source_has_extension (source, extension_name)) { gchar *uid; uid = e_source_dup_parent (source); g_object_unref (source); source = NULL; if (uid != NULL) { source = e_source_registry_ref_source (registry, uid); g_free (uid); } if (source == NULL) break; } return source; } /** * 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; } /* Helper for e_source_registry_build_display_tree() */ static gint source_registry_compare_nodes (GNode *node_a, GNode *node_b) { ESource *source_a = E_SOURCE (node_a->data); ESource *source_b = E_SOURCE (node_b->data); const gchar *uid_a, *uid_b; uid_a = e_source_get_uid (source_a); uid_b = e_source_get_uid (source_b); /* Sanity check, with runtime warnings. */ if (uid_a == NULL) { g_warn_if_reached (); uid_a = ""; } if (uid_b == NULL) { g_warn_if_reached (); uid_b = ""; } /* The built-in "local-stub" source comes first at depth 1. */ if (g_strcmp0 (uid_a, "local-stub") == 0) return -1; if (g_strcmp0 (uid_b, "local-stub") == 0) return 1; /* The built-in "system-*" sources come first at depth 2. */ if (g_str_has_prefix (uid_a, "system-")) return -1; if (g_str_has_prefix (uid_b, "system-")) return 1; return e_source_compare_by_display_name (source_a, source_b); } /* Helper for e_source_registry_build_display_tree() */ static gboolean source_registry_prune_nodes (GNode *node, const gchar *extension_name) { GQueue queue = G_QUEUE_INIT; GNode *child_node; /* Unlink all the child nodes and place them in a queue. */ 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. */ g_queue_sort ( &queue, (GCompareDataFunc) source_registry_compare_nodes, NULL); /* 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 and its descendants. */ while ((child_node = g_queue_pop_head (&queue)) != NULL) { ESource *child = E_SOURCE (child_node->data); gboolean append_child_node = FALSE; 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; } /** * e_source_registry_build_display_tree: * @registry: an #ESourceRegistry * @extension_name: (allow-none): an extension name, or %NULL * * Returns a single #GNode tree of registered sources that can be used to * populate a #GtkTreeModel. (The root #GNode is just an empty placeholder.) * * Similar to e_source_registry_list_sources(), an @extension_name can be * given to restrict the tree to sources having that extension name. Parents * of matched sources are included in the tree regardless of whether they have * an extension named @extension_name. * * Disabled leaf nodes are automatically excluded from the #GNode tree. * * The sources returned in the tree are referenced for thread-safety. * They must each be unreferenced with g_object_unref() when finished * with them. Free the returned tree itself with g_node_destroy(). * For convenience, e_source_registry_free_display_tree() does all * that in one step. * * Returns: (element-type ESource) (transfer full): a tree of sources, * arranged for display * * Since: 3.6 **/ GNode * e_source_registry_build_display_tree (ESourceRegistry *registry, const gchar *extension_name) { GNode *root; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); /* Assemble all data sources into a tree. */ root = source_registry_sources_build_tree (registry); /* Prune unwanted nodes from the copied source trees. * This must be done in "post" order (children first) * since it reorders and deletes child nodes. */ g_node_traverse ( root, G_POST_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc) source_registry_prune_nodes, (gpointer) extension_name); return root; } /* Helper for e_source_registry_free_display_tree() */ static void source_registry_unref_nodes (GNode *node) { while (node != NULL) { if (node->children != NULL) source_registry_unref_nodes (node->children); if (node->data != NULL) g_object_unref (node->data); node = node->next; } } /** * e_source_registry_free_display_tree: * @display_tree: a tree of sources, arranged for display * * Convenience function to free a #GNode tree of registered * sources created by e_source_registry_build_display_tree(). * * Since: 3.6 **/ void e_source_registry_free_display_tree (GNode *display_tree) { g_return_if_fail (display_tree != NULL); /* XXX This would be easier if GLib had something like * g_node_destroy_full() which took a GDestroyNotify. * Then the tree would not have to be traversed twice. */ source_registry_unref_nodes (display_tree); g_node_destroy (display_tree); } /** * e_source_registry_dup_unique_display_name: * @registry: an #ESourceRegistry * @source: an #ESource * @extension_name: (allow-none): an extension name, or %NULL * * Compares @source's #ESource:display-name against other sources having * an #ESourceExtension named @extension_name, if given, or else against * all other sources in the @registry. * * If @sources's #ESource:display-name is unique among these other sources, * the function will return the #ESource:display-name verbatim. Otherwise * the function will construct a string that includes the @sources's own * #ESource:display-name as well as those of its ancestors. * * The function's return value is intended to be used in messages shown to * the user to help clarify which source is being referred to. It assumes * @source's #ESource:display-name is at least unique among its siblings. * * Free the returned string with g_free() when finished with it. * * Returns: a unique display name for @source * * Since: 3.8 **/ gchar * e_source_registry_dup_unique_display_name (ESourceRegistry *registry, ESource *source, const gchar *extension_name) { GString *buffer; GList *list, *link; gchar *display_name; gboolean need_clarification = FALSE; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); g_return_val_if_fail (E_IS_SOURCE (source), NULL); list = e_source_registry_list_sources (registry, extension_name); /* Remove the input source from the list, if present. */ link = g_list_find (list, source); if (link != NULL) { g_object_unref (link->data); list = g_list_delete_link (list, link); } /* Now find another source with a matching display name. */ link = g_list_find_custom ( list, source, (GCompareFunc) e_source_compare_by_display_name); need_clarification = (link != NULL); g_list_free_full (list, (GDestroyNotify) g_object_unref); list = NULL; display_name = e_source_dup_display_name (source); buffer = g_string_new (display_name); g_free (display_name); if (need_clarification) { /* Build a list of ancestor sources. */ g_object_ref (source); while (source != NULL) { gchar *parent_uid; parent_uid = e_source_dup_parent (source); g_object_unref (source); source = NULL; if (parent_uid != NULL) { source = e_source_registry_ref_source ( registry, parent_uid); g_free (parent_uid); } if (source != NULL) { g_object_ref (source); list = g_list_prepend (list, source); } } /* Display the ancestor names from the most distant * ancestor to the input source's immediate parent. */ if (list != NULL) g_string_append (buffer, " ("); for (link = list; link != NULL; link = g_list_next (link)) { if (link != list) g_string_append (buffer, " / "); source = E_SOURCE (link->data); display_name = e_source_dup_display_name (source); g_string_append (buffer, display_name); g_free (display_name); } if (list != NULL) g_string_append (buffer, ")"); g_list_free_full (list, (GDestroyNotify) g_object_unref); } return g_string_free (buffer, FALSE); } /* Helper for e_source_registry_debug_dump() */ static gboolean source_registry_debug_dump_cb (GNode *node) { guint ii, depth; /* Root node is an empty placeholder. */ if (G_NODE_IS_ROOT (node)) return FALSE; depth = g_node_depth (node); for (ii = 2; ii < depth; ii++) g_print (" "); if (E_IS_SOURCE (node->data)) { ESource *source = E_SOURCE (node->data); g_print ("\"%s\" ", e_source_get_display_name (source)); g_print ("(%s)", e_source_get_uid (source)); } g_print ("\n"); return FALSE; } /** * e_source_registry_debug_dump: * @registry: an #ESourceRegistry * @extension_name: (allow-none): an extension name, or %NULL * * Handy debugging function that uses e_source_registry_build_display_tree() * to print a tree of registered sources to standard output. * * Since: 3.6 **/ void e_source_registry_debug_dump (ESourceRegistry *registry, const gchar *extension_name) { GNode *root; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); root = e_source_registry_build_display_tree (registry, extension_name); g_node_traverse ( root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc) source_registry_debug_dump_cb, NULL); e_source_registry_free_display_tree (root); } /** * e_source_registry_ref_builtin_address_book: * @registry: an #ESourceRegistry * * Returns the built-in address book #ESource. * * This #ESource is always present and makes for a safe fallback. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the built-in address book #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_builtin_address_book (ESourceRegistry *registry) { ESource *source; const gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); 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; } /** * e_source_registry_ref_builtin_calendar: * @registry: an #ESourceRegistry * * Returns the built-in calendar #ESource. * * This #ESource is always present and makes for a safe fallback. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the built-in calendar #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_builtin_calendar (ESourceRegistry *registry) { ESource *source; const gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); uid = E_SOURCE_BUILTIN_CALENDAR_UID; source = e_source_registry_ref_source (registry, uid); g_return_val_if_fail (source != NULL, NULL); return source; } /** * e_source_registry_ref_builtin_mail_account: * @registry: an #ESourceRegistry * * Returns the built-in mail account #ESource. * * This #ESource is always present and makes for a safe fallback. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the built-in mail account #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_builtin_mail_account (ESourceRegistry *registry) { ESource *source; const gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); uid = E_SOURCE_BUILTIN_MAIL_ACCOUNT_UID; source = e_source_registry_ref_source (registry, uid); g_return_val_if_fail (source != NULL, NULL); return source; } /** * e_source_registry_ref_builtin_memo_list: * @registry: an #ESourceRegistry * * Returns the built-in memo list #ESource. * * This #ESource is always present and makes for a safe fallback. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the built-in memo list #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_builtin_memo_list (ESourceRegistry *registry) { ESource *source; const gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); uid = E_SOURCE_BUILTIN_MEMO_LIST_UID; source = e_source_registry_ref_source (registry, uid); g_return_val_if_fail (source != NULL, NULL); return source; } /** * e_source_registry_ref_builtin_proxy: * @registry: an #ESourceRegistry * * Returns the built-in proxy profile #ESource. * * This #ESource is always present and makes for a safe fallback. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the built-in proxy profile #ESource * * Since: 3.12 **/ ESource * e_source_registry_ref_builtin_proxy (ESourceRegistry *registry) { ESource *source; const gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); uid = E_SOURCE_BUILTIN_PROXY_UID; source = e_source_registry_ref_source (registry, uid); g_return_val_if_fail (source != NULL, NULL); return source; } /** * e_source_registry_ref_builtin_task_list: * @registry: an #ESourceRegistry * * Returns the built-in task list #ESource. * * This #ESource is always present and makes for a safe fallback. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the built-in task list #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_builtin_task_list (ESourceRegistry *registry) { ESource *source; const gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); uid = E_SOURCE_BUILTIN_TASK_LIST_UID; source = e_source_registry_ref_source (registry, uid); g_return_val_if_fail (source != NULL, NULL); return source; } /** * e_source_registry_ref_default_address_book: * @registry: an #ESourceRegistry * * Returns the #ESource most recently passed to * e_source_registry_set_default_address_book() either in this session * or a previous session, or else falls back to the built-in address book. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the default address book #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_default_address_book (ESourceRegistry *registry) { const gchar *key; ESource *source; gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); key = E_SETTINGS_DEFAULT_ADDRESS_BOOK_KEY; uid = g_settings_get_string (registry->priv->settings, key); source = e_source_registry_ref_source (registry, uid); g_free (uid); /* The built-in source is always present. */ if (source == NULL) source = e_source_registry_ref_builtin_address_book (registry); g_return_val_if_fail (E_IS_SOURCE (source), NULL); return source; } /** * e_source_registry_set_default_address_book: * @registry: an #ESourceRegistry * @default_source: (allow-none): an address book #ESource, or %NULL * * Sets @default_source as the default address book. If @default_source * is %NULL, the default address book is reset to the built-in address book. * This setting will persist across sessions until changed. * * Since: 3.6 **/ void e_source_registry_set_default_address_book (ESourceRegistry *registry, ESource *default_source) { const gchar *key; const gchar *uid; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); if (default_source != NULL) { g_return_if_fail (E_IS_SOURCE (default_source)); uid = e_source_get_uid (default_source); } else { uid = E_SOURCE_BUILTIN_ADDRESS_BOOK_UID; } key = E_SETTINGS_DEFAULT_ADDRESS_BOOK_KEY; g_settings_set_string (registry->priv->settings, key, uid); /* The GSettings::changed signal will trigger a "notify" signal * from the registry, so no need to call g_object_notify() here. */ } /** * e_source_registry_ref_default_calendar: * @registry: an #ESourceRegistry * * Returns the #ESource most recently passed to * e_source_registry_set_default_calendar() either in this session * or a previous session, or else falls back to the built-in calendar. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the default calendar #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_default_calendar (ESourceRegistry *registry) { const gchar *key; ESource *source; gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); key = E_SETTINGS_DEFAULT_CALENDAR_KEY; uid = g_settings_get_string (registry->priv->settings, key); source = e_source_registry_ref_source (registry, uid); g_free (uid); /* The built-in source is always present. */ if (source == NULL) source = e_source_registry_ref_builtin_calendar (registry); g_return_val_if_fail (E_IS_SOURCE (source), NULL); return source; } /** * e_source_registry_set_default_calendar: * @registry: an #ESourceRegistry * @default_source: (allow-none): a calendar #ESource, or %NULL * * Sets @default_source as the default calendar. If @default_source * is %NULL, the default calendar is reset to the built-in calendar. * This setting will persist across sessions until changed. * * Since: 3.6 **/ void e_source_registry_set_default_calendar (ESourceRegistry *registry, ESource *default_source) { const gchar *key; const gchar *uid; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); if (default_source != NULL) { g_return_if_fail (E_IS_SOURCE (default_source)); uid = e_source_get_uid (default_source); } else { uid = E_SOURCE_BUILTIN_CALENDAR_UID; } key = E_SETTINGS_DEFAULT_CALENDAR_KEY; g_settings_set_string (registry->priv->settings, key, uid); /* The GSettings::changed signal will trigger a "notify" signal * from the registry, so no need to call g_object_notify() here. */ } /** * e_source_registry_ref_default_mail_account: * @registry: an #ESourceRegistry * * Returns the #ESource most recently passed to * e_source_registry_set_default_mail_account() either in this session * or a previous session, or else falls back to the built-in mail account. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the default mail account #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_default_mail_account (ESourceRegistry *registry) { const gchar *key; ESource *source; gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); key = E_SETTINGS_DEFAULT_MAIL_ACCOUNT_KEY; uid = g_settings_get_string (registry->priv->settings, key); source = e_source_registry_ref_source (registry, uid); g_free (uid); /* The built-in source is always present. */ if (source == NULL) source = e_source_registry_ref_builtin_mail_account (registry); g_return_val_if_fail (E_IS_SOURCE (source), NULL); return source; } /** * e_source_registry_set_default_mail_account: * @registry: an #ESourceRegistry * @default_source: (allow-none): a mail account #ESource, or %NULL * * Sets @default_source as the default mail account. If @default_source * is %NULL, the default mail account is reset to the built-in mail account. * This setting will persist across sessions until changed. * * Since: 3.6 **/ void e_source_registry_set_default_mail_account (ESourceRegistry *registry, ESource *default_source) { const gchar *key; const gchar *uid; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); if (default_source != NULL) { g_return_if_fail (E_IS_SOURCE (default_source)); uid = e_source_get_uid (default_source); } else { uid = E_SOURCE_BUILTIN_MAIL_ACCOUNT_UID; } key = E_SETTINGS_DEFAULT_MAIL_ACCOUNT_KEY; g_settings_set_string (registry->priv->settings, key, uid); /* The GSettings::changed signal will trigger a "notify" signal * from the registry, so no need to call g_object_notify() here. */ } /* Helper for e_source_registry_ref_default_mail_identity() */ static ESource * source_registry_ref_any_mail_identity (ESourceRegistry *registry) { ESource *source; GList *list, *link; const gchar *extension_name; gchar *uid = NULL; /* First fallback: Return the mail identity named * by the default mail account. */ source = e_source_registry_ref_default_mail_account (registry); /* This should never be NULL, but just to be safe. */ if (source != NULL) { ESourceMailAccount *extension; extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT; extension = e_source_get_extension (source, extension_name); uid = e_source_mail_account_dup_identity_uid (extension); g_object_unref (source); source = NULL; } if (uid != NULL) { source = e_source_registry_ref_source (registry, uid); g_free (uid); } if (source != NULL) return source; /* Second fallback: Pick any available mail identity, * preferring enabled identities. */ extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; list = e_source_registry_list_sources (registry, extension_name); for (link = list; link != NULL; link = g_list_next (link)) { ESource *candidate = E_SOURCE (link->data); if (e_source_registry_check_enabled (registry, candidate)) { source = g_object_ref (candidate); break; } } if (source == NULL && list != NULL) source = g_object_ref (list->data); g_list_free_full (list, (GDestroyNotify) g_object_unref); return source; } /** * e_source_registry_ref_default_mail_identity: * @registry: an #ESourceRegistry * * Returns the #ESource most recently passed to * e_source_registry_set_default_mail_identity() either in this session * or a previous session, or else falls back to the mail identity named * by the default mail account. If even that fails it returns any mail * identity from @registry, or %NULL if there are none. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the default mail identity #ESource, or %NULL * * Since: 3.6 **/ ESource * e_source_registry_ref_default_mail_identity (ESourceRegistry *registry) { const gchar *key; ESource *source; gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); key = E_SETTINGS_DEFAULT_MAIL_IDENTITY_KEY; uid = g_settings_get_string (registry->priv->settings, key); source = e_source_registry_ref_source (registry, uid); g_free (uid); if (source == NULL) source = source_registry_ref_any_mail_identity (registry); return source; } /** * e_source_registry_set_default_mail_identity: * @registry: an #ESourceRegistry * @default_source: (allow-none): a mail identity #ESource, or %NULL * * Sets @default_source as the default mail identity. If @default_source * is %NULL, the next request for the default mail identity will use the * fallbacks described in e_source_registry_ref_default_mail_identity(). * * Since: 3.6 **/ void e_source_registry_set_default_mail_identity (ESourceRegistry *registry, ESource *default_source) { const gchar *key; const gchar *uid; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); if (default_source != NULL) { g_return_if_fail (E_IS_SOURCE (default_source)); uid = e_source_get_uid (default_source); } else { uid = ""; /* no built-in mail identity */ } key = E_SETTINGS_DEFAULT_MAIL_IDENTITY_KEY; g_settings_set_string (registry->priv->settings, key, uid); /* The GSettings::changed signal will trigger a "notify" signal * from the registry, so no need to call g_object_notify() here. */ } /** * e_source_registry_ref_default_memo_list: * @registry: an #ESourceRegistry * * Returns the #ESource most recently passed to * e_source_registry_set_default_memo_list() either in this session * or a previous session, or else falls back to the built-in memo list. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the default memo list #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_default_memo_list (ESourceRegistry *registry) { const gchar *key; ESource *source; gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); key = E_SETTINGS_DEFAULT_MEMO_LIST_KEY; uid = g_settings_get_string (registry->priv->settings, key); source = e_source_registry_ref_source (registry, uid); g_free (uid); /* The built-in source is always present. */ if (source == NULL) source = e_source_registry_ref_builtin_memo_list (registry); g_return_val_if_fail (E_IS_SOURCE (source), NULL); return source; } /** * e_source_registry_set_default_memo_list: * @registry: an #ESourceRegistry * @default_source: (allow-none): a memo list #ESource, or %NULL * * Sets @default_source as the default memo list. If @default_source * is %NULL, the default memo list is reset to the built-in memo list. * This setting will persist across sessions until changed. * * Since: 3.6 **/ void e_source_registry_set_default_memo_list (ESourceRegistry *registry, ESource *default_source) { const gchar *key; const gchar *uid; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); if (default_source != NULL) { g_return_if_fail (E_IS_SOURCE (default_source)); uid = e_source_get_uid (default_source); } else { uid = E_SOURCE_BUILTIN_MEMO_LIST_UID; } key = E_SETTINGS_DEFAULT_MEMO_LIST_KEY; g_settings_set_string (registry->priv->settings, key, uid); /* The GSettings::changed signal will trigger a "notify" signal * from the registry, so no need to call g_object_notify() here. */ } /** * e_source_registry_ref_default_task_list: * @registry: an #ESourceRegistry * * Returns the #ESource most recently passed to * e_source_registry_set_default_task_list() either in this session * or a previous session, or else falls back to the built-in task list. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the default task list #ESource * * Since: 3.6 **/ ESource * e_source_registry_ref_default_task_list (ESourceRegistry *registry) { const gchar *key; ESource *source; gchar *uid; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); key = E_SETTINGS_DEFAULT_TASK_LIST_KEY; uid = g_settings_get_string (registry->priv->settings, key); source = e_source_registry_ref_source (registry, uid); g_free (uid); /* The built-in source is always present. */ if (source == NULL) source = e_source_registry_ref_builtin_task_list (registry); g_return_val_if_fail (E_IS_SOURCE (source), NULL); return source; } /** * e_source_registry_set_default_task_list: * @registry: an #ESourceRegistry * @default_source: (allow-none): a task list #ESource, or %NULL * * Sets @default_source as the default task list. If @default_source * is %NULL, the default task list is reset to the built-in task list. * This setting will persist across sessions until changed. * * Since: 3.6 **/ void e_source_registry_set_default_task_list (ESourceRegistry *registry, ESource *default_source) { const gchar *key; const gchar *uid; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); if (default_source != NULL) { g_return_if_fail (E_IS_SOURCE (default_source)); uid = e_source_get_uid (default_source); } else { uid = E_SOURCE_BUILTIN_TASK_LIST_UID; } key = E_SETTINGS_DEFAULT_TASK_LIST_KEY; g_settings_set_string (registry->priv->settings, key, uid); /* The GSettings::changed signal will trigger a "notify" signal * from the registry, so no need to call g_object_notify() here. */ } /** * e_source_registry_ref_default_for_extension_name: * @registry: an #ESourceRegistry * @extension_name: an extension_name * * This is a convenience function to return a default #ESource based on * @extension_name. This only works with a subset of extension names. * * If @extension_name is #E_SOURCE_EXTENSION_ADDRESS_BOOK, the function * returns the current default address book, or else falls back to the * built-in address book. * * If @extension_name is #E_SOURCE_EXTENSION_CALENDAR, the function returns * the current default calendar, or else falls back to the built-in calendar. * * If @extension_name is #E_SOURCE_EXTENSION_MAIL_ACCOUNT, the function * returns the current default mail account, or else falls back to the * built-in mail account. * * If @extension_name is #E_SOURCE_EXTENSION_MAIL_IDENTITY, the function * returns the current default mail identity, or else falls back to the * mail identity named by the current default mail account. * * If @extension_name is #E_SOURCE_EXTENSION_MEMO_LIST, the function returns * the current default memo list, or else falls back to the built-in memo list. * * If @extension_name is #E_SOURCE_EXTENSION_TASK_LIST, the function returns * the current default task list, or else falls back to the built-in task list. * * For all other values of @extension_name, the function returns %NULL. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: (transfer full): the default #ESource based on @extension_name * * Since: 3.6 **/ ESource * e_source_registry_ref_default_for_extension_name (ESourceRegistry *registry, const gchar *extension_name) { g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); g_return_val_if_fail (extension_name != NULL, NULL); if (strcmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) return e_source_registry_ref_default_address_book (registry); if (strcmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) return e_source_registry_ref_default_calendar (registry); if (strcmp (extension_name, E_SOURCE_EXTENSION_MAIL_ACCOUNT) == 0) return e_source_registry_ref_default_mail_account (registry); if (strcmp (extension_name, E_SOURCE_EXTENSION_MAIL_IDENTITY) == 0) return e_source_registry_ref_default_mail_identity (registry); if (strcmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) return e_source_registry_ref_default_memo_list (registry); if (strcmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) return e_source_registry_ref_default_task_list (registry); return NULL; } /** * e_source_registry_set_default_for_extension_name: * @registry: an #ESourceRegistry * @extension_name: an extension name * @default_source: (allow-none): an #ESource, or %NULL * * This is a convenience function to set a default #ESource based on * @extension_name. This only works with a subset of extension names. * * If @extension_name is #E_SOURCE_EXTENSION_ADDRESS_BOOK, the function * sets @default_source as the default address book. If @default_source * is %NULL, the default address book is reset to the built-in address book. * * If @extension_name is #E_SOURCE_EXTENSION_CALENDAR, the function sets * @default_source as the default calendar. If @default_source is %NULL, * the default calendar is reset to the built-in calendar. * * If @extension_name is #E_SOURCE_EXTENSION_MAIL_ACCOUNT, the function * sets @default_source as the default mail account. If @default_source * is %NULL, the default mail account is reset to the built-in mail account. * * If @extension_name is #E_SOURCE_EXTENSION_MAIL_IDENTITY, the function * sets @default_source as the default mail identity. If @default_source * is %NULL, the next request for the default mail identity will return * the mail identity named by the default mail account. * * If @extension_name is #E_SOURCE_EXTENSION_MEMO_LIST, the function sets * @default_source as the default memo list. If @default_source is %NULL, * the default memo list is reset to the built-in memo list. * * If @extension_name is #E_SOURCE_EXTENSION_TASK_LIST, the function sets * @default_source as the default task list. If @default_source is %NULL, * the default task list is reset to the built-in task list. * * For all other values of @extension_name, the function does nothing. * * Since: 3.6 **/ void e_source_registry_set_default_for_extension_name (ESourceRegistry *registry, const gchar *extension_name, ESource *default_source) { g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (extension_name != NULL); if (strcmp (extension_name, E_SOURCE_EXTENSION_ADDRESS_BOOK) == 0) e_source_registry_set_default_address_book ( registry, default_source); if (strcmp (extension_name, E_SOURCE_EXTENSION_CALENDAR) == 0) e_source_registry_set_default_calendar ( registry, default_source); if (strcmp (extension_name, E_SOURCE_EXTENSION_MAIL_ACCOUNT) == 0) e_source_registry_set_default_mail_account ( registry, default_source); if (strcmp (extension_name, E_SOURCE_EXTENSION_MAIL_IDENTITY) == 0) e_source_registry_set_default_mail_identity ( registry, default_source); if (strcmp (extension_name, E_SOURCE_EXTENSION_MEMO_LIST) == 0) e_source_registry_set_default_memo_list ( registry, default_source); if (strcmp (extension_name, E_SOURCE_EXTENSION_TASK_LIST) == 0) e_source_registry_set_default_task_list ( registry, default_source); }