/* * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com) * * 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 . * * Authors: Fabiano FidĂȘncio */ /** * SECTION: e-subprocess-factory * @include: libebackend/libebackend.h * @short_description: An abstract base class for a backend-subprocess server **/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "e-subprocess-factory.h" #define E_SUBPROCESS_FACTORY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_SUBPROCESS_FACTORY, ESubprocessFactoryPrivate)) struct _ESubprocessFactoryPrivate { ESourceRegistry *registry; /* * As backends and modules HashTables are used basically * in the same places, we use the same mutex to guard * both of them. */ GMutex mutex; /* ESource UID -> EBackend */ GHashTable *backends; /* Module filename -> EModule */ GHashTable *modules; }; enum { PROP_0, PROP_REGISTRY, }; /* Forward Declarations */ static void e_subprocess_factory_initable_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE ( ESubprocessFactory, e_subprocess_factory, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE ( G_TYPE_INITABLE, e_subprocess_factory_initable_init)) static void subprocess_factory_toggle_notify_cb (gpointer data, GObject *backend, gboolean is_last_ref) { if (is_last_ref) { g_object_ref (backend); g_object_remove_toggle_ref ( backend, subprocess_factory_toggle_notify_cb, data); g_signal_emit_by_name (backend, "shutdown"); g_object_unref (backend); } } static void subprocess_factory_closed_cb (EBackend *backend, const gchar *sender, EDBusSubprocessBackend *proxy) { e_dbus_subprocess_backend_emit_backend_closed (proxy, sender); } static void e_subprocess_factory_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_REGISTRY: g_value_set_object ( value, e_subprocess_factory_get_registry ( E_SUBPROCESS_FACTORY (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void subprocess_factory_dispose (GObject *object) { ESubprocessFactory *subprocess_factory; ESubprocessFactoryPrivate *priv; subprocess_factory = E_SUBPROCESS_FACTORY (object); priv = subprocess_factory->priv; g_hash_table_remove_all (priv->backends); g_hash_table_remove_all (priv->modules); g_clear_object (&priv->registry); /* Chain up to parent's dispose() method */ G_OBJECT_CLASS (e_subprocess_factory_parent_class)->dispose (object); } static void subprocess_factory_finalize (GObject *object) { ESubprocessFactory *subprocess_factory; ESubprocessFactoryPrivate *priv; subprocess_factory = E_SUBPROCESS_FACTORY (object); priv = subprocess_factory->priv; g_mutex_clear (&priv->mutex); g_hash_table_destroy (priv->backends); g_hash_table_destroy (priv->modules); /* Chain up to parent's finalize() method */ G_OBJECT_CLASS (e_subprocess_factory_parent_class)->finalize (object); } static gboolean subprocess_factory_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { ESubprocessFactory *subprocess_factory; subprocess_factory = E_SUBPROCESS_FACTORY (initable); subprocess_factory->priv->registry = e_source_registry_new_sync ( cancellable, error); return (subprocess_factory->priv->registry != NULL); } static void e_subprocess_factory_initable_init (GInitableIface *iface) { iface->init = subprocess_factory_initable_init; } static void e_subprocess_factory_class_init (ESubprocessFactoryClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (ESubprocessFactoryPrivate)); object_class = G_OBJECT_CLASS (class); object_class->get_property = e_subprocess_factory_get_property; object_class->dispose = subprocess_factory_dispose; object_class->finalize = subprocess_factory_finalize; 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)); } static void e_subprocess_factory_init (ESubprocessFactory *subprocess_factory) { subprocess_factory->priv = E_SUBPROCESS_FACTORY_GET_PRIVATE (subprocess_factory); g_mutex_init (&subprocess_factory->priv->mutex); subprocess_factory->priv->backends = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); subprocess_factory->priv->modules = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_type_module_unuse); } /** * e_subprocess_factory_ref_initable_backend: * @subprocess_factory: an #ESubprocessFactory * @uid: UID of an #ESource to open * @backend_factory_type_name: the name of the backend factory type * @module_filename: the name (full-path) of the backend module to be loaded * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Returns either a newly-created or existing #EBackend for #ESource. * The returned #EBackend is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * If the newly-created backend implements the #GInitable interface, then * g_initable_init() is also called on it using @cancellable and @error. * * The @subprocess_factory retains a strong reference to @backend. * * If no suitable #EBackendFactory exists, or if the #EBackend fails to * initialize, the function sets @error and returns %NULL. * * Returns: an #EBackend for @source, or %NULL * * Since: 3.16 **/ EBackend * e_subprocess_factory_ref_initable_backend (ESubprocessFactory *subprocess_factory, const gchar *uid, const gchar *backend_factory_type_name, const gchar *module_filename, GCancellable *cancellable, GError **error) { EBackend *backend; EModule *module; ESource *source; ESourceRegistry *registry; ESubprocessFactoryPrivate *priv; ESubprocessFactoryClass *class; g_return_val_if_fail (E_IS_SUBPROCESS_FACTORY (subprocess_factory), NULL); g_return_val_if_fail (uid != NULL && *uid != '\0', NULL); g_return_val_if_fail (backend_factory_type_name != NULL && *backend_factory_type_name != '\0', NULL); g_return_val_if_fail (module_filename != NULL && *module_filename != '\0', NULL); priv = subprocess_factory->priv; g_mutex_lock (&priv->mutex); backend = g_hash_table_lookup (priv->backends, uid); if (backend != NULL) { g_object_ref (backend); goto exit; } module = g_hash_table_lookup (priv->modules, module_filename); if (module == NULL) { module = e_module_load_file (module_filename); g_hash_table_insert (priv->modules, g_strdup (module_filename), module); } registry = e_subprocess_factory_get_registry (subprocess_factory); source = e_source_registry_ref_source (registry, uid); if (source == NULL) { g_set_error ( error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such source for UID '%s'"), uid); goto exit; } class = E_SUBPROCESS_FACTORY_GET_CLASS (subprocess_factory); backend = class->ref_backend ( registry, source, backend_factory_type_name); if (backend == NULL) goto exit; if (G_IS_INITABLE (backend)) { GInitable *initable = G_INITABLE (backend); if (!g_initable_init (initable, cancellable, error)) g_clear_object (&backend); } if (backend != NULL) g_hash_table_insert (priv->backends, g_strdup (uid), g_object_ref (backend)); exit: g_mutex_unlock (&priv->mutex); return backend; } /** * e_subprocess_factory_get_registry: * @subprocess_factory: an #ESubprocessFactory * * Returns the #ESourceRegistry owned by @subprocess_factory. * * Returns: the #ESourceRegistry * * Since: 3.16 **/ ESourceRegistry * e_subprocess_factory_get_registry (ESubprocessFactory *subprocess_factory) { g_return_val_if_fail (E_IS_SUBPROCESS_FACTORY (subprocess_factory), NULL); return subprocess_factory->priv->registry; } /** * e_subprocess_factory_construct_path: * * Returns a new and unique object path for a D-Bus interface based * in the data object path prefix of the @subprocess_factory * * Returns: a newly allocated string, representing the object path for * the D-Bus interface. * * This function is here for a lack of a better place * * Since: 3.16 **/ gchar * e_subprocess_factory_construct_path (void) { static volatile gint counter = 1; g_atomic_int_inc (&counter); return g_strdup_printf ( "/org/gnome/evolution/dataserver/Subprocess/%d/%u", getpid (), counter); } /** * e_subprocess_factory_open_backend: * @subprocess_factory: an #ESubprocessFactory * @connection: a #GDBusConnection * @uid: UID of an #ESource to open * @backend_factory_type_name: the name of the backend factory type * @module_filename: the name (full-path) of the backend module to be loaded * @proxy: a #GDBusInterfaceSkeleton, used to communicate to the subprocess backend * @cancellable: a #GCancellable * @error: return location for a #GError, or %NULL * * Returns the #EBackend data D-Bus object path * * Returns: a newly allocated string that represents the #EBackend * data D-Bus object path. * * Since: 3.16 **/ gchar * e_subprocess_factory_open_backend (ESubprocessFactory *subprocess_factory, GDBusConnection *connection, const gchar *uid, const gchar *backend_factory_type_name, const gchar *module_filename, GDBusInterfaceSkeleton *proxy, GCancellable *cancellable, GError **error) { ESubprocessFactoryClass *class; EBackend *backend; gchar *object_path = NULL; g_return_val_if_fail (E_IS_SUBPROCESS_FACTORY (subprocess_factory), NULL); g_return_val_if_fail (connection != NULL, NULL); g_return_val_if_fail (uid != NULL && *uid != '\0', NULL); g_return_val_if_fail (backend_factory_type_name != NULL && *backend_factory_type_name != '\0', NULL); g_return_val_if_fail (module_filename != NULL && *module_filename != '\0', NULL); g_return_val_if_fail (E_DBUS_SUBPROCESS_IS_BACKEND (proxy), NULL); backend = e_subprocess_factory_ref_initable_backend ( subprocess_factory, uid, backend_factory_type_name, module_filename, cancellable, error); if (backend == NULL) return NULL; class = E_SUBPROCESS_FACTORY_GET_CLASS (subprocess_factory); object_path = class->open_data ( subprocess_factory, backend, connection, proxy, cancellable, error); g_clear_object (&backend); return object_path; } /** * e_subprocess_factory_get_backends_list: * @subprocess_factory: an #ESubprocessFactory * * Returns a list of used backends. * * Returns: A #GList that contains a list of used backends. The list should be freed * by the caller using: g_list_free_full (backends, g_object_unref). * * Since: 3.16 **/ GList * e_subprocess_factory_get_backends_list (ESubprocessFactory *subprocess_factory) { GList *backends; ESubprocessFactoryPrivate *priv; g_return_val_if_fail (E_IS_SUBPROCESS_FACTORY (subprocess_factory), NULL); priv = subprocess_factory->priv; g_mutex_lock (&priv->mutex); backends = g_hash_table_get_values (subprocess_factory->priv->backends); g_list_foreach (backends, (GFunc) g_object_ref, NULL); g_mutex_unlock (&priv->mutex); return backends; } /** * e_subprocess_factory_call_backends_prepare_shutdown: * @subprocess_factory: an #ESubprocessFactory * * Calls e_backend_prepare_shutdown() for the list of used backends. * * Since: 3.16 */ void e_subprocess_factory_call_backends_prepare_shutdown (ESubprocessFactory *subprocess_factory) { GList *backends, *l; g_return_if_fail (E_IS_SUBPROCESS_FACTORY (subprocess_factory)); backends = e_subprocess_factory_get_backends_list (subprocess_factory); for (l = backends; l != NULL; l = g_list_next (l)) { EBackend *backend = l->data; e_backend_prepare_shutdown (backend); } g_list_free_full (backends, g_object_unref); } /** * e_subprocess_factory_set_backend_callbacks: * @subprocess_factory: an #ESubprocessFactory * @backend: an #EBackend * @proxy: a #GDBusInterfaceSkeleton, used to communicate to the subprocess backend * * Installs a toggle reference on the backend, that can receive a signal to * shutdown once all client connections are closed. * * Since: 3.16 **/ void e_subprocess_factory_set_backend_callbacks (ESubprocessFactory *subprocess_factory, EBackend *backend, GDBusInterfaceSkeleton *proxy) { g_return_if_fail (E_IS_SUBPROCESS_FACTORY (subprocess_factory)); g_return_if_fail (backend != NULL); g_return_if_fail (E_DBUS_SUBPROCESS_IS_BACKEND (proxy)); /* * Install a toggle reference on the backend * so we can signal it to shutdown once all * client connections are closed */ g_object_add_toggle_ref ( G_OBJECT (backend), subprocess_factory_toggle_notify_cb, NULL); g_signal_connect_object ( backend, "closed", G_CALLBACK (subprocess_factory_closed_cb), proxy, 0); }