/* * e-data-factory.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-data-factory * @include: libebackend/libebackend.h * @short_description: An abstract base class for a backend-based server **/ #include "evolution-data-server-config.h" #include #include #include #include #include #include "e-data-factory.h" typedef enum { DATA_FACTORY_SPAWN_SUBPROCESS_NONE = 0, DATA_FACTORY_SPAWN_SUBPROCESS_BLOCKED, DATA_FACTORY_SPAWN_SUBPROCESS_READY } DataFactorySpawnSubprocessStates; struct _EDataFactoryPrivate { ESourceRegistry *registry; /* The mutex guards the 'subprocess_helpers' hash table. * The 'backend_factories' hash table doesn't really need * guarding since it gets populated during construction * and is read-only thereafter. */ GMutex mutex; /* Factory Name -> DataFactorySubprocessFactoryHelper */ GHashTable *subprocess_helpers; /* Bus Name -> Watched Name */ GHashTable *subprocess_watched_ids; GMutex subprocess_watched_ids_lock; /* Hash Key -> EBackendFactory */ GHashTable *backend_factories; /* This is a hash table of client bus names to an array of * EDBusSubprocessBackend references; one for every connection * opened. */ GHashTable *connections; GRecMutex connections_lock; /* Holds array of opened backends for each client. Used only when not using backend-per-process. Reuses the 'connections_lock'. */ GHashTable *backend_clients; /* gchar *sender ~> GPtrArray { GWeakRef { EBackend }} */ /* This is a hash table of client bus names being watched. * The value is the watcher ID for g_bus_unwatch_name(). */ GHashTable *watched_names; GMutex watched_names_lock; /* This is a GCond used to guarantee that in case of two * instances of the same backend are created at the same * time, the second instance will wait the first one be * created and then will use that instance, instead of * creating it twice. */ GCond spawn_subprocess_cond; GMutex spawn_subprocess_lock; DataFactorySpawnSubprocessStates spawn_subprocess_state; gboolean reload_supported; gint backend_per_process; /* Used only when backend-per-process is disabled, guarded by 'mutex' */ GHashTable *opened_backends; /* backend-key ~> OpenedBackendData * */ }; enum { PROP_0, PROP_REGISTRY, PROP_RELOAD_SUPPORTED, PROP_BACKEND_PER_PROCESS }; /* Forward Declarations */ static void e_data_factory_initable_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE ( EDataFactory, e_data_factory, E_TYPE_DBUS_SERVER, G_ADD_PRIVATE (EDataFactory) G_IMPLEMENT_INTERFACE ( G_TYPE_INITABLE, e_data_factory_initable_init)) typedef struct _DataFactorySpawnSubprocessBackendThreadData DataFactorySpawnSubprocessBackendThreadData; struct _DataFactorySpawnSubprocessBackendThreadData { EDataFactory *data_factory; GDBusMethodInvocation *invocation; gchar *uid; gchar *extension_name; gchar *subprocess_path; }; static DataFactorySpawnSubprocessBackendThreadData * data_factory_spawn_subprocess_backend_thread_data_new (EDataFactory *data_factory, GDBusMethodInvocation *invocation, const gchar *uid, const gchar *extension_name, const gchar *subprocess_path) { DataFactorySpawnSubprocessBackendThreadData *data; data = g_slice_new0 (DataFactorySpawnSubprocessBackendThreadData); data->data_factory = g_object_ref (data_factory); data->invocation = g_object_ref (invocation); data->uid = g_strdup (uid); data->extension_name = g_strdup (extension_name); data->subprocess_path = g_strdup (subprocess_path); return data; } static void data_factory_spawn_subprocess_backend_thread_data_free (DataFactorySpawnSubprocessBackendThreadData *data) { if (data != NULL) { g_clear_object (&data->data_factory); g_clear_object (&data->invocation); g_free (data->uid); g_free (data->extension_name); g_free (data->subprocess_path); g_slice_free (DataFactorySpawnSubprocessBackendThreadData, data); } } typedef struct _DataFactorySubprocessHelper DataFactorySubprocessHelper; struct _DataFactorySubprocessHelper { EDBusSubprocessBackend *proxy; gchar *factory_name; gchar *bus_name; }; static DataFactorySubprocessHelper * data_factory_subprocess_helper_new (EDBusSubprocessBackend *proxy, const gchar *factory_name, const gchar *bus_name) { DataFactorySubprocessHelper *helper; helper = g_slice_new0 (DataFactorySubprocessHelper); helper->proxy = g_object_ref (proxy); helper->factory_name = g_strdup (factory_name); helper->bus_name = g_strdup (bus_name); return helper; } static void data_factory_subprocess_helper_free (DataFactorySubprocessHelper *helper) { if (helper != NULL) { g_clear_object (&helper->proxy); g_free (helper->factory_name); g_free (helper->bus_name); g_slice_free (DataFactorySubprocessHelper, helper); } } static void data_factory_backend_toggle_notify_cb (gpointer user_data, GObject *backend, gboolean is_last_ref); typedef struct _OpenedBackendData { EDataFactory *data_factory; EBackend *backend; gchar *object_path; } OpenedBackendData; static OpenedBackendData * opened_backend_data_new (EDataFactory *data_factory, EBackend *backend, /* assumes ownership of 'backend' */ const gchar *object_path) { OpenedBackendData *obd; g_object_add_toggle_ref ( G_OBJECT (backend), data_factory_backend_toggle_notify_cb, data_factory); obd = g_slice_new0 (OpenedBackendData); obd->data_factory = data_factory; obd->backend = backend; obd->object_path = g_strdup (object_path); return obd; } static void opened_backend_data_free (gpointer ptr) { OpenedBackendData *obd = ptr; if (obd) { if (obd->backend) g_object_remove_toggle_ref (G_OBJECT (obd->backend), data_factory_backend_toggle_notify_cb, obd->data_factory); g_clear_object (&obd->backend); g_free (obd->object_path); g_slice_free (OpenedBackendData, obd); } } static gchar * data_factory_construct_subprocess_path (EDataFactory *data_factory) { EDataFactoryClass *class; static volatile gint counter = 1; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_atomic_int_inc (&counter); class = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_val_if_fail (class != NULL, NULL); g_return_val_if_fail (class->subprocess_object_path_prefix != NULL, NULL); return g_strdup_printf ( "%s/%d/%u", class->subprocess_object_path_prefix, getpid (), counter); } static gchar * data_factory_construct_subprocess_bus_name (EDataFactory *data_factory) { EDataFactoryClass *class; static volatile gint counter = 1; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_atomic_int_inc (&counter); class = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_val_if_fail (class != NULL, NULL); g_return_val_if_fail (class->subprocess_bus_name_prefix != NULL, NULL); /* We use the format "%sx%d%u" because we want to be really safe about * the GDBus bus names used. When we create an object path we follow * the same pattern %s/%d/%u, but bus names are quite more restrictive * about its format, not allowing ".", "-", "/" or whatever that could * be used in a more descriptive way. And that's the reason the "x" is * being used here. */ return g_strdup_printf ( "%sx%dx%u", class->subprocess_bus_name_prefix, getpid(), counter); } typedef struct _DataFactorySubprocessData DataFactorySubprocessData; struct _DataFactorySubprocessData { EDataFactory *data_factory; GDBusMethodInvocation *invocation; gchar *bus_name; gchar *extension_name; gchar *factory_name; gchar *path; gchar *type_name; gchar *uid; gchar *module_filename; gchar *subprocess_helpers_hash_key; }; static DataFactorySubprocessData * data_factory_subprocess_data_new (EDataFactory *data_factory, GDBusMethodInvocation *invocation, const gchar *uid, const gchar *factory_name, const gchar *type_name, const gchar *extension_name, const gchar *module_filename, const gchar *subprocess_helpers_hash_key) { DataFactorySubprocessData *sd; sd = g_slice_new0 (DataFactorySubprocessData); sd->data_factory = g_object_ref (data_factory); sd->invocation = g_object_ref (invocation); sd->uid = g_strdup (uid); sd->factory_name = g_strdup (factory_name); sd->type_name = g_strdup (type_name); sd->extension_name = g_strdup (extension_name); sd->module_filename = g_strdup (module_filename); sd->subprocess_helpers_hash_key = g_strdup (subprocess_helpers_hash_key); sd->path = data_factory_construct_subprocess_path (data_factory); sd->bus_name = data_factory_construct_subprocess_bus_name (data_factory); return sd; } static void data_factory_subprocess_data_free (DataFactorySubprocessData *sd) { if (sd != NULL) { g_clear_object (&sd->data_factory); g_clear_object (&sd->invocation); g_free (sd->uid); g_free (sd->path); g_free (sd->bus_name); g_free (sd->type_name); g_free (sd->factory_name); g_free (sd->extension_name); g_free (sd->module_filename); g_free (sd->subprocess_helpers_hash_key); g_slice_free (DataFactorySubprocessData, sd); } } static gchar * data_factory_dup_subprocess_helper_hash_key (const gchar *factory_name, const gchar *extension_name, const gchar *uid, gboolean backend_per_process, gboolean backend_factory_share_subprocess) { gchar *helper_hash_key; if (backend_per_process) { helper_hash_key = backend_factory_share_subprocess ? g_strdup (factory_name) : g_strdup_printf ("%s:%s:%s", factory_name, extension_name, uid); } else { helper_hash_key = g_strdup ("not-using-backend-per-process"); } return helper_hash_key; } static void data_factory_add_backend_client (EDataFactory *data_factory, EBackend *backend, const gchar *sender) { GPtrArray *array; g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); g_return_if_fail (E_IS_BACKEND (backend)); g_return_if_fail (sender != NULL); g_rec_mutex_lock (&data_factory->priv->connections_lock); array = g_hash_table_lookup (data_factory->priv->backend_clients, sender); if (!array) { array = g_ptr_array_new_with_free_func ((GDestroyNotify) e_weak_ref_free); g_hash_table_insert (data_factory->priv->backend_clients, g_strdup (sender), array); } g_ptr_array_add (array, e_weak_ref_new (backend)); g_rec_mutex_unlock (&data_factory->priv->connections_lock); } static void data_factory_remove_backend_client (EDataFactory *data_factory, EBackend *backend, const gchar *sender) { GPtrArray *array; guint ii; g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); if (backend) g_return_if_fail (E_IS_BACKEND (backend)); g_return_if_fail (sender != NULL); g_rec_mutex_lock (&data_factory->priv->connections_lock); array = g_hash_table_lookup (data_factory->priv->backend_clients, sender); if (array) { for (ii = 0; ii < array->len; ii++) { GWeakRef *weakref; EBackend *stored_backend; weakref = g_ptr_array_index (array, ii); stored_backend = g_weak_ref_get (weakref); if (backend) { if (stored_backend == backend) { g_ptr_array_remove_index (array, ii); /* One client can connect to one backend multiple times, thus remove only one item from the array. */ g_clear_object (&stored_backend); break; } } else if (stored_backend) { /* Do not provide 'sender', because it would call this function again, causing unnecessary recursion. */ e_data_factory_backend_closed_by_sender (data_factory, stored_backend, NULL); } g_clear_object (&stored_backend); } if (!array->len) g_hash_table_remove (data_factory->priv->backend_clients, sender); } g_rec_mutex_unlock (&data_factory->priv->connections_lock); } static gboolean data_factory_verify_subprocess_backend_proxy_is_used (EDataFactory *data_factory, const gchar *except_bus_name, EDBusSubprocessBackend *proxy) { GHashTable *connections; GList *names, *l; GPtrArray *array; gboolean is_used = FALSE; g_return_val_if_fail (except_bus_name != NULL, TRUE); g_rec_mutex_lock (&data_factory->priv->connections_lock); connections = data_factory->priv->connections; names = g_hash_table_get_keys (connections); for (l = names; l != NULL && !is_used; l = g_list_next (l)) { const gchar *client_bus_name = l->data; gint ii; /* Check if we don't have more connections from the same client */ if (g_strcmp0 (client_bus_name, except_bus_name) == 0) { guint used = 0; array = g_hash_table_lookup (connections, client_bus_name); for (ii = 0; ii < array->len; ii++) { EDBusSubprocessBackend *proxy_in_use; proxy_in_use = g_ptr_array_index (array, ii); if (proxy == proxy_in_use) used++; if (used > 1) { is_used = TRUE; break; } } continue; } array = g_hash_table_lookup (connections, client_bus_name); for (ii = 0; ii < array->len; ii++) { EDBusSubprocessBackend *proxy_in_use; proxy_in_use = g_ptr_array_index (array, ii); if (proxy == proxy_in_use) { is_used = TRUE; break; } } } g_list_free (names); g_rec_mutex_unlock (&data_factory->priv->connections_lock); return is_used; } static gboolean data_factory_connections_remove (EDataFactory *data_factory, const gchar *name, EDBusSubprocessBackend *proxy) { GHashTable *connections; GPtrArray *array; gboolean removed = FALSE; /* If proxy is NULL, we remove all proxies for name. */ g_return_val_if_fail (name != NULL, FALSE); g_rec_mutex_lock (&data_factory->priv->connections_lock); connections = data_factory->priv->connections; array = g_hash_table_lookup (connections, name); if (array != NULL) { if (proxy != NULL) { if (!data_factory_verify_subprocess_backend_proxy_is_used (data_factory, name, proxy)) e_dbus_subprocess_backend_call_close_sync (proxy, NULL, NULL); removed = g_ptr_array_remove_fast (array, proxy); } else if (array->len > 0) { /* * As we can have more than one proxy being used by the same name, * in the same array, we must remove the connections one by one * and then notify the subprocess to quit itself when it's the last * one being used. AKA: do *not* try to optimize this code to use * g_ptr_array_set_size (array, 0). */ while (array->len != 0) { EDBusSubprocessBackend *proxy1; proxy1 = g_ptr_array_index (array, 0); if (!data_factory_verify_subprocess_backend_proxy_is_used (data_factory, name, proxy1)) e_dbus_subprocess_backend_call_close_sync (proxy1, NULL, NULL); g_ptr_array_remove_fast (array, proxy1); } removed = TRUE; } if (array->len == 0) { g_hash_table_remove (connections, name); e_dbus_server_release (E_DBUS_SERVER (data_factory)); } } g_rec_mutex_unlock (&data_factory->priv->connections_lock); return removed; } static void data_factory_name_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { GWeakRef *weak_ref = user_data; EDataFactory *data_factory; data_factory = g_weak_ref_get (weak_ref); if (data_factory != NULL) { data_factory_connections_remove (data_factory, name, NULL); data_factory_remove_backend_client (data_factory, NULL, name); /* Unwatching the bus name from here will corrupt the * 'name' argument, and possibly also the 'user_data'. * * This is a GDBus bug. Work around it by unwatching * the bus name last. * * See: https://bugzilla.gnome.org/706088 */ g_mutex_lock (&data_factory->priv->watched_names_lock); g_hash_table_remove (data_factory->priv->watched_names, name); g_mutex_unlock (&data_factory->priv->watched_names_lock); g_object_unref (data_factory); } } static void data_factory_watched_names_add (EDataFactory *data_factory, GDBusConnection *connection, const gchar *name) { GHashTable *watched_names; g_return_if_fail (name != NULL); g_mutex_lock (&data_factory->priv->watched_names_lock); watched_names = data_factory->priv->watched_names; if (!g_hash_table_contains (watched_names, name)) { guint watcher_id; /* The g_bus_watch_name() documentation says one of the two * callbacks are guaranteed to be invoked after calling the * function. But which one is determined asynchronously so * there should be no chance of the name vanished callback * deadlocking with us when it tries to acquire the lock. */ watcher_id = g_bus_watch_name_on_connection ( connection, name, G_BUS_NAME_WATCHER_FLAGS_NONE, (GBusNameAppearedCallback) NULL, data_factory_name_vanished_cb, e_weak_ref_new (data_factory), (GDestroyNotify) e_weak_ref_free); g_hash_table_insert ( watched_names, g_strdup (name), GUINT_TO_POINTER (watcher_id)); } g_mutex_unlock (&data_factory->priv->watched_names_lock); } static void data_factory_connections_add (EDataFactory *data_factory, const gchar *name, EDBusSubprocessBackend *proxy) { GHashTable *connections; GPtrArray *array; g_return_if_fail (name != NULL); g_return_if_fail (proxy != NULL); g_rec_mutex_lock (&data_factory->priv->connections_lock); connections = data_factory->priv->connections; array = g_hash_table_lookup (connections, name); if (array == NULL) { array = g_ptr_array_new_with_free_func ( (GDestroyNotify) g_object_unref); g_hash_table_insert ( connections, g_strdup (name), array); e_dbus_server_hold (E_DBUS_SERVER (data_factory)); } g_ptr_array_add (array, g_object_ref (proxy)); g_rec_mutex_unlock (&data_factory->priv->connections_lock); } static void data_factory_call_subprocess_backend_create_sync (EDataFactory *data_factory, EDBusSubprocessBackend *proxy, GDBusMethodInvocation *invocation, const gchar *uid, const gchar *bus_name, const gchar *type_name, const gchar *extension_name, const gchar *module_filename) { GError *error = NULL; gchar *object_path = NULL; e_dbus_subprocess_backend_call_create_sync ( proxy, uid, type_name, module_filename, &object_path, NULL, &error); if (object_path != NULL) { EDataFactoryClass *class; GDBusConnection *connection; const gchar *sender; connection = g_dbus_method_invocation_get_connection (invocation); sender = g_dbus_method_invocation_get_sender (invocation); data_factory_watched_names_add ( data_factory, connection, sender); data_factory_connections_add ( data_factory, sender, proxy); class = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_if_fail (class != NULL); g_return_if_fail (class->complete_open != NULL); class->complete_open (data_factory, invocation, object_path, bus_name, extension_name); g_free (object_path); } else { g_return_if_fail (error != NULL); g_dbus_method_invocation_take_error (invocation, error); } e_dbus_server_release (E_DBUS_SERVER (data_factory)); g_mutex_lock (&data_factory->priv->spawn_subprocess_lock); if (data_factory->priv->spawn_subprocess_state == DATA_FACTORY_SPAWN_SUBPROCESS_BLOCKED) data_factory->priv->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_NONE; g_cond_signal (&data_factory->priv->spawn_subprocess_cond); g_mutex_unlock (&data_factory->priv->spawn_subprocess_lock); } static void data_factory_backend_closed_cb (EDBusSubprocessBackend *proxy, const gchar *sender, GWeakRef *factory_weak_ref) { EDataFactory *data_factory; data_factory = g_weak_ref_get (factory_weak_ref); data_factory_connections_remove (data_factory, sender, proxy); g_object_unref (data_factory); } static void data_factory_subprocess_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { EDBusSubprocessBackend *proxy; EDataFactoryPrivate *priv; DataFactorySubprocessData *sd; DataFactorySubprocessHelper *helper; sd = user_data; proxy = e_dbus_subprocess_backend_proxy_new_sync ( connection, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, sd->bus_name, sd->path, NULL, NULL); g_signal_connect_data ( proxy, "backend-closed", G_CALLBACK (data_factory_backend_closed_cb), e_weak_ref_new (sd->data_factory), (GClosureNotify) e_weak_ref_free, 0); data_factory_call_subprocess_backend_create_sync ( sd->data_factory, proxy, sd->invocation, sd->uid, sd->bus_name, sd->type_name, sd->extension_name, sd->module_filename); helper = data_factory_subprocess_helper_new (proxy, sd->factory_name, sd->bus_name); priv = sd->data_factory->priv; g_mutex_lock (&priv->mutex); g_hash_table_insert ( priv->subprocess_helpers, g_strdup (sd->subprocess_helpers_hash_key), helper); g_mutex_unlock (&priv->mutex); g_mutex_lock (&priv->spawn_subprocess_lock); priv->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_READY; g_cond_signal (&priv->spawn_subprocess_cond); g_mutex_unlock (&priv->spawn_subprocess_lock); g_object_unref (proxy); } static void data_factory_subprocess_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { DataFactorySubprocessData *sd; DataFactorySubprocessHelper *helper; EDataFactory *data_factory; EDataFactoryPrivate *priv; const gchar *sender; sd = user_data; data_factory = g_object_ref (sd->data_factory); priv = sd->data_factory->priv; g_mutex_lock (&priv->mutex); helper = g_hash_table_lookup ( priv->subprocess_helpers, sd->subprocess_helpers_hash_key); g_mutex_unlock (&priv->mutex); if (helper == NULL) { g_clear_object (&data_factory); return; } sender = g_dbus_method_invocation_get_sender (sd->invocation); data_factory_connections_remove (sd->data_factory, sender, helper->proxy); g_mutex_lock (&priv->mutex); g_hash_table_remove ( priv->subprocess_helpers, sd->subprocess_helpers_hash_key); g_mutex_unlock (&priv->mutex); g_mutex_lock (&priv->subprocess_watched_ids_lock); g_hash_table_remove (priv->subprocess_watched_ids, sd->bus_name); g_mutex_unlock (&priv->subprocess_watched_ids_lock); g_clear_object (&data_factory); } static void watched_names_value_free (gpointer value) { g_bus_unwatch_name (GPOINTER_TO_UINT (value)); } static void data_factory_bus_acquired (EDBusServer *server, GDBusConnection *connection) { GDBusInterfaceSkeleton *skeleton_interface; EDataFactoryClass *class; GError *error = NULL; class = E_DATA_FACTORY_GET_CLASS (E_DATA_FACTORY (server)); g_return_if_fail (class != NULL); g_return_if_fail (class->get_dbus_interface_skeleton != NULL); skeleton_interface = class->get_dbus_interface_skeleton (server); g_dbus_interface_skeleton_export ( skeleton_interface, connection, class->factory_object_path, &error); if (error != NULL) { g_warning ("%s: %s", G_STRFUNC, error->message); e_dbus_server_quit (server, E_DBUS_SERVER_EXIT_NORMAL); g_error_free (error); return; } /* Chain up to parent's bus_acquired() method. */ E_DBUS_SERVER_CLASS (e_data_factory_parent_class)-> bus_acquired (server, connection); } static GList * data_factory_list_proxies (EDataFactory *data_factory) { GList *proxies = NULL; GHashTable *connections; GHashTable *proxies_hash; GHashTableIter iter; gpointer key, value; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_rec_mutex_lock (&data_factory->priv->connections_lock); connections = data_factory->priv->connections; proxies_hash = g_hash_table_new (g_direct_hash, g_direct_equal); g_hash_table_iter_init (&iter, connections); while (g_hash_table_iter_next (&iter, &key, &value)) { GPtrArray *array = value; gint ii; for (ii = 0; ii < array->len; ii++) { EDBusSubprocessBackend *proxy = g_ptr_array_index (array, ii); if (!g_hash_table_contains (proxies_hash, proxy)) { g_hash_table_insert (proxies_hash, proxy, GINT_TO_POINTER (1)); proxies = g_list_prepend (proxies, g_object_ref (proxy)); } } } g_hash_table_destroy (proxies_hash); proxies = g_list_reverse (proxies); g_rec_mutex_unlock (&data_factory->priv->connections_lock); return proxies; } static void data_factory_connections_remove_all (EDataFactory *data_factory) { GHashTable *connections; g_rec_mutex_lock (&data_factory->priv->connections_lock); connections = data_factory->priv->connections; if (g_hash_table_size (connections) > 0) { GList *proxies, *l; gint ii; proxies = data_factory_list_proxies (data_factory); for (l = proxies; l != NULL; l = g_list_next (l)) { EDBusSubprocessBackend *proxy = l->data; e_dbus_subprocess_backend_call_close_sync (proxy, NULL, NULL); } g_list_free_full (proxies, g_object_unref); for (ii = 0; ii < g_hash_table_size (connections); ii++) { e_dbus_server_release (E_DBUS_SERVER (data_factory)); } g_hash_table_remove_all (connections); } g_rec_mutex_unlock (&data_factory->priv->connections_lock); } static void data_factory_bus_name_lost (EDBusServer *server, GDBusConnection *connection) { EDataFactory *data_factory; data_factory = E_DATA_FACTORY (server); data_factory_connections_remove_all (data_factory); /* Chain up to parent's bus_name_lost() method. */ E_DBUS_SERVER_CLASS (e_data_factory_parent_class)-> bus_name_lost (server, connection); } static void data_factory_register_extensions (EDataFactory *data_factory) { EDataFactoryClass *klass; GList *list, *link; klass = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_if_fail (klass != NULL); /* Collect all backend factories into a hash table. */ e_extensible_reload_extensions (E_EXTENSIBLE (data_factory)); list = e_extensible_list_extensions (E_EXTENSIBLE (data_factory), klass->backend_factory_type); for (link = list; link != NULL; link = g_list_next (link)) { EBackendFactory *backend_factory; const gchar *hash_key; backend_factory = E_BACKEND_FACTORY (link->data); hash_key = e_backend_factory_get_hash_key (backend_factory); if (hash_key != NULL && !g_hash_table_contains (data_factory->priv->backend_factories, hash_key)) { g_hash_table_insert ( data_factory->priv->backend_factories, g_strdup (hash_key), g_object_ref (backend_factory)); e_source_registry_debug_print ( "Registering %s ('%s')\n", G_OBJECT_TYPE_NAME (backend_factory), hash_key); } } g_list_free (list); } static EDBusServerExitCode data_factory_run_server (EDBusServer *server) { data_factory_register_extensions (E_DATA_FACTORY (server)); /* Chain up to parent's method. */ return E_DBUS_SERVER_CLASS (e_data_factory_parent_class)->run_server (server); } static void data_factory_quit_server (EDBusServer *server, EDBusServerExitCode exit_code) { GDBusInterfaceSkeleton *skeleton_interface; EDataFactoryClass *class; /* If the factory does not support reloading, stop the signal * emission and return without chaining up to prevent quitting. */ if (exit_code == E_DBUS_SERVER_EXIT_RELOAD && !e_data_factory_get_reload_supported (E_DATA_FACTORY (server))) { g_signal_stop_emission_by_name (server, "quit-server"); return; } class = E_DATA_FACTORY_GET_CLASS (E_DATA_FACTORY (server)); g_return_if_fail (class != NULL); g_return_if_fail (class->get_dbus_interface_skeleton != NULL); skeleton_interface = class->get_dbus_interface_skeleton (server); if (skeleton_interface && g_dbus_interface_skeleton_get_connection (skeleton_interface)) g_dbus_interface_skeleton_unexport (skeleton_interface); /* Chain up to parent's quit_server() method. */ E_DBUS_SERVER_CLASS (e_data_factory_parent_class)-> quit_server (server, exit_code); } static void e_data_factory_set_reload_supported (EDataFactory *data_factory, gboolean is_supported) { g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); data_factory->priv->reload_supported = is_supported; } static void e_data_factory_set_backend_per_process (EDataFactory *data_factory, gint backend_per_process) { g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); data_factory->priv->backend_per_process = backend_per_process; } static void e_data_factory_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_REGISTRY: g_value_set_object ( value, e_data_factory_get_registry ( E_DATA_FACTORY (object))); return; case PROP_RELOAD_SUPPORTED: g_value_set_boolean ( value, e_data_factory_get_reload_supported ( E_DATA_FACTORY (object))); return; case PROP_BACKEND_PER_PROCESS: g_value_set_int ( value, e_data_factory_get_backend_per_process ( E_DATA_FACTORY (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_data_factory_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_RELOAD_SUPPORTED: e_data_factory_set_reload_supported ( E_DATA_FACTORY (object), g_value_get_boolean (value)); return; case PROP_BACKEND_PER_PROCESS: e_data_factory_set_backend_per_process ( E_DATA_FACTORY (object), g_value_get_int (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void data_factory_dispose (GObject *object) { EDataFactory *data_factory; EDataFactoryPrivate *priv; data_factory = E_DATA_FACTORY (object); priv = data_factory->priv; g_hash_table_remove_all (priv->backend_factories); g_hash_table_remove_all (priv->subprocess_helpers); g_hash_table_remove_all (priv->subprocess_watched_ids); g_clear_object (&priv->registry); g_hash_table_remove_all (priv->connections); g_hash_table_remove_all (priv->backend_clients); g_hash_table_remove_all (priv->watched_names); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_data_factory_parent_class)->dispose (object); } static void data_factory_finalize (GObject *object) { EDataFactory *data_factory; EDataFactoryPrivate *priv; data_factory = E_DATA_FACTORY (object); priv = data_factory->priv; g_mutex_clear (&priv->mutex); g_hash_table_destroy (priv->backend_factories); g_hash_table_destroy (priv->subprocess_helpers); g_hash_table_destroy (priv->subprocess_watched_ids); g_mutex_clear (&priv->subprocess_watched_ids_lock); g_hash_table_destroy (priv->connections); g_rec_mutex_clear (&priv->connections_lock); g_hash_table_destroy (priv->backend_clients); g_hash_table_destroy (priv->watched_names); g_mutex_clear (&priv->watched_names_lock); g_hash_table_destroy (priv->opened_backends); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_data_factory_parent_class)->finalize (object); } static gboolean data_factory_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { EDataFactory *data_factory; data_factory = E_DATA_FACTORY (initable); data_factory->priv->registry = e_source_registry_new_sync ( cancellable, error); return (data_factory->priv->registry != NULL); } static void e_data_factory_initable_init (GInitableIface *iface) { iface->init = data_factory_initable_init; } static void e_data_factory_class_init (EDataFactoryClass *class) { GObjectClass *object_class; EDBusServerClass *dbus_server_class; object_class = G_OBJECT_CLASS (class); object_class->get_property = e_data_factory_get_property; object_class->set_property = e_data_factory_set_property; object_class->dispose = data_factory_dispose; object_class->finalize = data_factory_finalize; class->backend_factory_type = E_TYPE_BACKEND_FACTORY; dbus_server_class = E_DBUS_SERVER_CLASS (class); dbus_server_class->bus_acquired = data_factory_bus_acquired; dbus_server_class->bus_name_lost = data_factory_bus_name_lost; dbus_server_class->run_server = data_factory_run_server; dbus_server_class->quit_server = data_factory_quit_server; g_object_class_install_property ( object_class, PROP_REGISTRY, g_param_spec_object ( "registry", "Registry", "Data source registry", E_TYPE_SOURCE_REGISTRY, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_RELOAD_SUPPORTED, g_param_spec_boolean ( "reload-supported", "Reload Supported", "Whether the data factory supports Reload", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_BACKEND_PER_PROCESS, g_param_spec_int ( "backend-per-process", "Backend Per Process", "Override backend-per-process compile-time option", G_MININT, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } static void e_data_factory_init (EDataFactory *data_factory) { data_factory->priv = e_data_factory_get_instance_private (data_factory); g_mutex_init (&data_factory->priv->mutex); g_mutex_init (&data_factory->priv->subprocess_watched_ids_lock); g_rec_mutex_init (&data_factory->priv->connections_lock); g_mutex_init (&data_factory->priv->watched_names_lock); g_mutex_init (&data_factory->priv->spawn_subprocess_lock); data_factory->priv->backend_factories = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); data_factory->priv->subprocess_helpers = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) data_factory_subprocess_helper_free); data_factory->priv->subprocess_watched_ids = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) watched_names_value_free); data_factory->priv->connections = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_ptr_array_unref); data_factory->priv->backend_clients = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); data_factory->priv->watched_names = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) watched_names_value_free); data_factory->priv->opened_backends = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, opened_backend_data_free); data_factory->priv->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_NONE; data_factory->priv->reload_supported = FALSE; data_factory->priv->backend_per_process = -1; } /** * e_data_factory_ref_backend_factory: * @data_factory: an #EDataFactory * @backend_name: a backend name * @extension_name: an extension name * * Returns the #EBackendFactory for "@backend_name:@extension_name", or * %NULL if no such factory is registered. * * The returned #EBackendFactory is referenced for thread-safety. * Unreference the #EBackendFactory with g_object_unref() when finished * with it. * * Returns: (transfer full) (nullable): the #EBackendFactory for @hash_key, * or %NULL * * Since: 3.6 **/ EBackendFactory * e_data_factory_ref_backend_factory (EDataFactory *data_factory, const gchar *backend_name, const gchar *extension_name) { GHashTable *backend_factories; EBackendFactory *backend_factory; gchar *hash_key; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_return_val_if_fail (backend_name != NULL && *backend_name != '\0', NULL); g_return_val_if_fail (extension_name != NULL && *extension_name != '\0', NULL); /* It should be safe to lookup backend factories without a mutex * because once initially populated the hash table remains fixed. * * XXX Which might imply the returned factory doesn't *really* need * to be referenced for thread-safety, but better to do it when * not really needed than wish we had in the future. */ hash_key = g_strdup_printf ("%s:%s", backend_name, extension_name); backend_factories = data_factory->priv->backend_factories; backend_factory = g_hash_table_lookup (backend_factories, hash_key); g_free (hash_key); if (backend_factory != NULL) g_object_ref (backend_factory); return backend_factory; } /** * e_data_factory_get_registry: * @data_factory: an #EDataFactory * * Returns the #ESourceRegistry owned by @data_factory. * * Returns: (transfer none): the #ESourceRegistry * * Since: 3.16 **/ ESourceRegistry * e_data_factory_get_registry (EDataFactory *data_factory) { g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); return data_factory->priv->registry; } /** * e_data_factory_construct_path: * @data_factory: an #EDataFactory * * Returns a new and unique object path for a D-Bus interface based * in the data object path prefix of the @data_factory * * Returns: a newly allocated string, representing the object path for * the D-Bus interface. * * Since: 3.16 **/ gchar * e_data_factory_construct_path (EDataFactory *data_factory) { EDataFactoryClass *class; static volatile gint counter = 1; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_atomic_int_inc (&counter); class = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_val_if_fail (class != NULL, NULL); g_return_val_if_fail (class->data_object_path_prefix != NULL, NULL); return g_strdup_printf ( "%s/%d/%u", class->data_object_path_prefix, getpid (), counter); } static void data_factory_backend_toggle_notify_cb (gpointer user_data, GObject *backend, gboolean is_last_ref) { if (is_last_ref) { EDataFactory *data_factory = E_DATA_FACTORY (user_data); gboolean found = FALSE; g_object_ref (backend); g_signal_emit_by_name (backend, "shutdown"); g_mutex_lock (&data_factory->priv->mutex); if (data_factory->priv->opened_backends) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, data_factory->priv->opened_backends); while (g_hash_table_iter_next (&iter, &key, &value)) { OpenedBackendData *obd = value; if (obd && obd->backend == (EBackend *) backend) { g_hash_table_remove (data_factory->priv->opened_backends, key); found = TRUE; break; } } } g_mutex_unlock (&data_factory->priv->mutex); if (!found) { g_warn_if_reached (); g_object_unref (backend); } /* Also drop the "reference" on the data_factory */ e_dbus_server_release (E_DBUS_SERVER (data_factory)); } } static void data_factory_spawn_subprocess_backend (EDataFactory *data_factory, GDBusMethodInvocation *invocation, const gchar *uid, const gchar *extension_name, const gchar *subprocess_path) { DataFactorySubprocessHelper *helper; DataFactorySubprocessData *sd = NULL; EBackendFactory *backend_factory = NULL; EDataFactoryClass *class; EDBusServerClass *server_class; ESource *source; EDataFactoryPrivate *priv; GError *error = NULL; GSubprocess *subprocess; guint watched_id = 0; gchar *backend_name = NULL; gchar *subprocess_helpers_hash_key; gboolean backend_per_process; const gchar *factory_name; const gchar *filename; const gchar *type_name; g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); g_return_if_fail (invocation != NULL); g_return_if_fail (uid != NULL && *uid != '\0'); g_return_if_fail (extension_name != NULL && *extension_name != '\0'); g_return_if_fail (subprocess_path != NULL && *subprocess_path != '\0'); class = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_if_fail (class != NULL); g_return_if_fail (class->complete_open != NULL); g_return_if_fail (class->get_factory_name != NULL); server_class = E_DBUS_SERVER_GET_CLASS (data_factory); g_return_if_fail (server_class != NULL); priv = data_factory->priv; source = e_source_registry_ref_source (priv->registry, uid); if (!source) { g_set_error ( &error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such source for UID “%s”"), uid); } if (source && e_source_has_extension (source, extension_name)) { ESourceBackend *extension; extension = e_source_get_extension (source, extension_name); backend_name = e_source_backend_dup_backend_name (extension); } if (backend_name && *backend_name) { backend_factory = e_data_factory_ref_backend_factory ( data_factory, backend_name, extension_name); } backend_per_process = e_data_factory_use_backend_per_process (data_factory); if (!backend_per_process && backend_factory) { gchar *object_path = NULL, *backend_key; OpenedBackendData *obd; EBackend *backend = NULL; backend_key = g_strconcat (backend_name, "\n", uid, "\n", extension_name, NULL); g_mutex_lock (&data_factory->priv->mutex); obd = g_hash_table_lookup (data_factory->priv->opened_backends, backend_key); if (obd) { object_path = g_strdup (obd->object_path); g_object_ref (obd->backend); backend = obd->backend; /* Also drop the "reference" on the data_factory, it's held by the obj->backend already */ e_dbus_server_release (E_DBUS_SERVER (data_factory)); } g_mutex_unlock (&data_factory->priv->mutex); if (!object_path) { backend = e_data_factory_create_backend (data_factory, backend_factory, source); object_path = e_data_factory_open_backend (data_factory, backend, g_dbus_method_invocation_get_connection (invocation), NULL, &error); if (object_path) { g_mutex_lock (&data_factory->priv->mutex); g_hash_table_insert (data_factory->priv->opened_backends, backend_key, opened_backend_data_new (data_factory, backend, object_path)); g_mutex_unlock (&data_factory->priv->mutex); backend_key = NULL; /* Do not call e_dbus_server_release() here, otherwise the factory will close shortly afterwards. The backend holds the "reference" to the factory. */ /* e_dbus_server_release (E_DBUS_SERVER (data_factory)); */ } else { g_clear_object (&backend); } } if (object_path) { GDBusConnection *connection; const gchar *sender; class->complete_open (data_factory, invocation, object_path, server_class->bus_name, extension_name); connection = g_dbus_method_invocation_get_connection (invocation); sender = g_dbus_method_invocation_get_sender (invocation); data_factory_watched_names_add (data_factory, connection, sender); data_factory_add_backend_client (data_factory, backend, sender); g_mutex_lock (&priv->spawn_subprocess_lock); priv->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_NONE; g_cond_signal (&priv->spawn_subprocess_cond); g_mutex_unlock (&priv->spawn_subprocess_lock); } g_free (object_path); g_free (backend_key); } else if (backend_factory) { type_name = G_OBJECT_TYPE_NAME (backend_factory); factory_name = class->get_factory_name (backend_factory); subprocess_helpers_hash_key = data_factory_dup_subprocess_helper_hash_key ( factory_name, extension_name, uid, backend_per_process, e_backend_factory_share_subprocess (backend_factory)); g_mutex_lock (&priv->mutex); helper = g_hash_table_lookup ( priv->subprocess_helpers, subprocess_helpers_hash_key); g_mutex_unlock (&priv->mutex); filename = e_backend_factory_get_module_filename (backend_factory); if (helper != NULL) { data_factory_call_subprocess_backend_create_sync ( data_factory, helper->proxy, invocation, uid, helper->bus_name, type_name, extension_name, filename); g_object_unref (backend_factory); g_clear_object (&source); g_free (subprocess_helpers_hash_key); g_free (backend_name); return; } g_mutex_lock (&priv->spawn_subprocess_lock); if (priv->spawn_subprocess_state != DATA_FACTORY_SPAWN_SUBPROCESS_BLOCKED) priv->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_BLOCKED; g_mutex_unlock (&priv->spawn_subprocess_lock); sd = data_factory_subprocess_data_new ( data_factory, invocation, uid, factory_name, type_name, extension_name, filename, subprocess_helpers_hash_key); g_object_unref (backend_factory); g_free (subprocess_helpers_hash_key); watched_id = g_bus_watch_name ( G_BUS_TYPE_SESSION, sd->bus_name, G_BUS_NAME_WATCHER_FLAGS_NONE, data_factory_subprocess_appeared_cb, data_factory_subprocess_vanished_cb, sd, (GDestroyNotify) data_factory_subprocess_data_free); g_mutex_lock (&priv->subprocess_watched_ids_lock); g_hash_table_insert (priv->subprocess_watched_ids, g_strdup (sd->bus_name), GUINT_TO_POINTER (watched_id)); g_mutex_unlock (&priv->subprocess_watched_ids_lock); subprocess = g_subprocess_new ( G_SUBPROCESS_FLAGS_NONE, &error, subprocess_path, "--factory", sd->factory_name, "--bus-name", sd->bus_name, "--own-path", sd->path, NULL); g_object_unref (subprocess); } else if (!error) { error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Backend factory for source “%s” and extension “%s” cannot be found."), uid, extension_name); } g_clear_object (&source); g_free (backend_name); if (error != NULL) { e_dbus_server_release (E_DBUS_SERVER (data_factory)); if (sd) { g_mutex_lock (&priv->subprocess_watched_ids_lock); if (g_hash_table_remove (priv->subprocess_watched_ids, sd->bus_name)) watched_id = 0; g_mutex_unlock (&priv->subprocess_watched_ids_lock); } if (watched_id) g_bus_unwatch_name (watched_id); g_dbus_method_invocation_take_error (invocation, error); g_mutex_lock (&priv->spawn_subprocess_lock); priv->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_NONE; g_cond_signal (&priv->spawn_subprocess_cond); g_mutex_unlock (&priv->spawn_subprocess_lock); } } static gpointer data_factory_spawn_subprocess_backend_in_thread (gpointer user_data) { DataFactorySpawnSubprocessBackendThreadData *data = user_data; EDataFactory *data_factory = data->data_factory; GDBusMethodInvocation *invocation = data->invocation; const gchar *uid = data->uid; const gchar *extension_name = data->extension_name; const gchar *subprocess_path = data->subprocess_path; g_mutex_lock (&data_factory->priv->spawn_subprocess_lock); while (data_factory->priv->spawn_subprocess_state == DATA_FACTORY_SPAWN_SUBPROCESS_BLOCKED) { g_cond_wait ( &data_factory->priv->spawn_subprocess_cond, &data_factory->priv->spawn_subprocess_lock); } data_factory->priv->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_BLOCKED; g_mutex_unlock (&data_factory->priv->spawn_subprocess_lock); data_factory_spawn_subprocess_backend ( data_factory, invocation, uid, extension_name, subprocess_path); data_factory_spawn_subprocess_backend_thread_data_free (data); return NULL; } /** * e_data_factory_spawn_subprocess_backend: * @data_factory: an #EDataFactory * @invocation: a #GDBusMethodInvocation * @uid: an #ESource UID * @extension_name: an extension name * @subprocess_path: a path of an executable responsible for running the subprocess * * Spawns a new subprocess for a backend type and returns the object path * of the new subprocess to the client, in the way the client can talk * directly to the running backend. If the backend already has a subprocess * running, the used object path is returned to the client. * * Since: 3.16 **/ void e_data_factory_spawn_subprocess_backend (EDataFactory *data_factory, GDBusMethodInvocation *invocation, const gchar *uid, const gchar *extension_name, const gchar *subprocess_path) { GThread *thread; DataFactorySpawnSubprocessBackendThreadData *data; g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); /* Make sure the server will not quit due to inactivity while the subprocess is opening */ e_dbus_server_hold (E_DBUS_SERVER (data_factory)); data = data_factory_spawn_subprocess_backend_thread_data_new ( data_factory, invocation, uid, extension_name, subprocess_path); thread = g_thread_new ("Spawn-Subprocess-Backend", data_factory_spawn_subprocess_backend_in_thread, data); g_thread_unref (thread); } gboolean e_data_factory_get_reload_supported (EDataFactory *data_factory) { g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), FALSE); return data_factory->priv->reload_supported; } gint e_data_factory_get_backend_per_process (EDataFactory *data_factory) { g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), -1); return data_factory->priv->backend_per_process; } gboolean e_data_factory_use_backend_per_process (EDataFactory *data_factory) { gboolean backend_per_process; gint override_backend_per_process; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), FALSE); override_backend_per_process = e_data_factory_get_backend_per_process (data_factory); if (override_backend_per_process == 0 || override_backend_per_process == 1) { backend_per_process = override_backend_per_process == 1; } else { #ifdef ENABLE_BACKEND_PER_PROCESS backend_per_process = TRUE; #else backend_per_process = FALSE; #endif } return backend_per_process; } /** * e_data_factory_create_backend: * @data_factory: an #EDataFactory * * Used only when backend-per-process is off. * * Free the returned pointer with g_object_unref(), if not NULL and no longer * needed. * * Returns: (transfer full) (nullable): a newly-created #EBackend **/ EBackend * e_data_factory_create_backend (EDataFactory *data_factory, EBackendFactory *backend_factory, ESource *source) { EDataFactoryClass *klass; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_return_val_if_fail (E_IS_BACKEND_FACTORY (backend_factory), NULL); g_return_val_if_fail (E_IS_SOURCE (source), NULL); klass = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_val_if_fail (klass != NULL, NULL); g_return_val_if_fail (klass->create_backend != NULL, NULL); return klass->create_backend (data_factory, backend_factory, source); } /* Used only when backend-per-process is off. Free the returned string with g_free(), when no longer needed */ gchar * e_data_factory_open_backend (EDataFactory *data_factory, EBackend *backend, GDBusConnection *connection, GCancellable *cancellable, GError **error) { EDataFactoryClass *klass; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_return_val_if_fail (E_IS_BACKEND (backend), NULL); g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); klass = E_DATA_FACTORY_GET_CLASS (data_factory); g_return_val_if_fail (klass != NULL, NULL); g_return_val_if_fail (klass->open_backend != NULL, NULL); return klass->open_backend (data_factory, backend, connection, cancellable, error); } /* Whenever the backend is closed by any of its clients it should call this function, thus the data_factory knows about it */ void e_data_factory_backend_closed (EDataFactory *data_factory, EBackend *backend) { g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); g_return_if_fail (E_IS_BACKEND (backend)); e_data_factory_backend_closed_by_sender (data_factory, backend, NULL); } void e_data_factory_backend_closed_by_sender (EDataFactory *data_factory, EBackend *backend, const gchar *sender) { g_return_if_fail (E_IS_DATA_FACTORY (data_factory)); g_return_if_fail (E_IS_BACKEND (backend)); /* Call only when 'sender' is not NULL, to avoid recursion when data_factory_remove_backend_client() calls this function on its own. */ if (sender) data_factory_remove_backend_client (data_factory, backend, sender); g_object_unref (backend); } /** * e_data_factory_list_opened_backends: * @data_factory: an #EDataFactory * * Lists the currently opened backends. * * 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 #GSList itself with g_slist_free(). * * An easy way to free the list properly in one step is as follows: * * |[ * g_slist_free_full (list, g_object_unref); * ]| * * Returns: (element-type EBackend) (transfer full): a #GSList of #EBackend **/ GSList * e_data_factory_list_opened_backends (EDataFactory *data_factory) { GSList *backends = NULL; GHashTableIter iter; gpointer value; g_return_val_if_fail (E_IS_DATA_FACTORY (data_factory), NULL); g_mutex_lock (&data_factory->priv->mutex); g_hash_table_iter_init (&iter, data_factory->priv->opened_backends); while (g_hash_table_iter_next (&iter, NULL, &value)) { OpenedBackendData *obd = value; if (obd && obd->backend) backends = g_slist_prepend (backends, g_object_ref (obd->backend)); } g_mutex_unlock (&data_factory->priv->mutex); return backends; }