/* * 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 "e-data-factory.h" #include #include #include #include #include #include #define E_DATA_FACTORY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_DATA_FACTORY, EDataFactoryPrivate)) 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; /* 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; }; enum { PROP_0, PROP_REGISTRY, PROP_RELOAD_SUPPORTED }; /* 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_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_new0 (DataFactorySpawnSubprocessBackendThreadData, 1); 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_free (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_new0 (DataFactorySubprocessHelper, 1); 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_free (helper); } } 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->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->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_new0 (DataFactorySubprocessData, 1); 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_free (sd); } } static gchar * data_factory_dup_subprocess_helper_hash_key (const gchar *factory_name, const gchar *extension_name, const gchar *uid, gboolean backend_factory_share_subprocess) { gchar *helper_hash_key; #ifdef ENABLE_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-bacend-per-process"); #endif return helper_hash_key; } 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); /* 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->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; EDataFactoryPrivate *priv; const gchar *sender; guint watched_id; sd = user_data; 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) 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); watched_id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->subprocess_watched_ids, sd->bus_name)); g_hash_table_remove (priv->subprocess_watched_ids, sd->bus_name); if (watched_id > 0) g_bus_unwatch_name (watched_id); g_mutex_unlock (&priv->subprocess_watched_ids_lock); } 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)); 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_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)); skeleton_interface = class->get_dbus_interface_skeleton (server); 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_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; } 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; } 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->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->watched_names); g_mutex_clear (&priv->watched_names_lock); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_data_factory_parent_class)->finalize (object); } static void data_factory_constructed (GObject *object) { EDataFactoryClass *class; EDataFactory *data_factory; GList *list, *link; data_factory = E_DATA_FACTORY (object); class = E_DATA_FACTORY_GET_CLASS (data_factory); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_data_factory_parent_class)->constructed (object); /* Collect all backend factories into a hash table. */ list = e_extensible_list_extensions ( E_EXTENSIBLE (object), class->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_insert ( data_factory->priv->backend_factories, g_strdup (hash_key), g_object_ref (backend_factory)); g_debug ( "Registering %s ('%s')\n", G_OBJECT_TYPE_NAME (backend_factory), hash_key); } } g_list_free (list); } 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; g_type_class_add_private (class, sizeof (EDataFactoryPrivate)); 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; object_class->constructed = data_factory_constructed; 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->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)); } static void e_data_factory_init (EDataFactory *data_factory) { data_factory->priv = E_DATA_FACTORY_GET_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) NULL); 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->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->spawn_subprocess_state = DATA_FACTORY_SPAWN_SUBPROCESS_NONE; data_factory->priv->reload_supported = FALSE; } /** * 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: 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: 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->data_object_path_prefix != NULL, NULL); return g_strdup_printf ( "%s/%d/%u", class->data_object_path_prefix, getpid (), counter); } 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; ESource *source; EDataFactoryPrivate *priv; GError *error = NULL; GSubprocess *subprocess; guint watched_id = 0; gchar *backend_name = NULL; gchar *subprocess_helpers_hash_key; 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'); priv = data_factory->priv; source = e_source_registry_ref_source (priv->registry, 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); } g_clear_object (&source); if (backend_name && *backend_name) { backend_factory = e_data_factory_ref_backend_factory ( data_factory, backend_name, extension_name); } g_free (backend_name); if (backend_factory) { type_name = G_OBJECT_TYPE_NAME (backend_factory); class = E_DATA_FACTORY_GET_CLASS (data_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, 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_free (subprocess_helpers_hash_key); 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 { 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); } if (error != NULL) { e_dbus_server_release (E_DBUS_SERVER (data_factory)); if (sd) { 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); } 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 #GDBusMethodInvcation * @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; }