/* cc-object-storage.h * * Copyright 2018 Georges Basile Stavracas Neto * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define G_LOG_DOMAIN "cc-object-storage" #include "cc-object-storage.h" struct _CcObjectStorage { GObject parent_instance; GHashTable *id_to_object; }; G_DEFINE_TYPE (CcObjectStorage, cc_object_storage, G_TYPE_OBJECT) /* Singleton instance */ static CcObjectStorage *_instance = NULL; /* GTask API to create a new D-Bus proxy */ typedef struct { GBusType bus_type; GDBusProxyFlags flags; gchar *name; gchar *path; gchar *interface; gboolean cached; } TaskData; static TaskData* task_data_new (GBusType bus_type, GDBusProxyFlags flags, const gchar *name, const gchar *path, const gchar *interface) { TaskData *data = g_slice_new (TaskData); data->bus_type = bus_type; data->flags =flags; data->name = g_strdup (name); data->path = g_strdup (path); data->interface = g_strdup (interface); data->cached = FALSE; return data; } static void task_data_free (TaskData *data) { g_free (data->name); g_free (data->path); g_free (data->interface); g_slice_free (TaskData, data); } static void create_dbus_proxy_in_thread_cb (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GError) local_error = NULL; TaskData *data = task_data; proxy = g_dbus_proxy_new_for_bus_sync (data->bus_type, data->flags, NULL, data->name, data->path, data->interface, cancellable, &local_error); if (local_error) { g_task_return_error (task, g_steal_pointer (&local_error)); return; } g_task_return_pointer (task, g_object_ref (g_steal_pointer (&proxy)), g_object_unref); } static void cc_object_storage_finalize (GObject *object) { CcObjectStorage *self = (CcObjectStorage *)object; g_debug ("Destroying cached objects"); g_clear_pointer (&self->id_to_object, g_hash_table_destroy); G_OBJECT_CLASS (cc_object_storage_parent_class)->finalize (object); } static void cc_object_storage_class_init (CcObjectStorageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cc_object_storage_finalize; } static void cc_object_storage_init (CcObjectStorage *self) { self->id_to_object = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); } /** * cc_object_storage_has_object: * @key: the unique string identifier of the object * * Checks whether there is an object associated with @key. * * Returns: %TRUE if the object is stored, %FALSE otherwise. */ gboolean cc_object_storage_has_object (const gchar *key) { g_assert (CC_IS_OBJECT_STORAGE (_instance)); g_assert (key != NULL); return g_hash_table_contains (_instance->id_to_object, key); } /** * cc_object_storage_add_object: * @key: the unique string identifier of the object * @object: (type GObject): the object to be stored * * Adds @object to the object storage. It is a programming error to try to * add an object that was already added. * * @object must be a GObject. * * Always check if the object is stored with cc_object_storage_has_object() * before calling this function. */ void cc_object_storage_add_object (const gchar *key, gpointer object) { /* Trying to add an object that was already added is a hard error. Each * object must be added once, and only once, over the entire lifetime * of the application. */ g_assert (CC_IS_OBJECT_STORAGE (_instance)); g_assert (key != NULL); g_assert (G_IS_OBJECT (object)); g_assert (!g_hash_table_contains (_instance->id_to_object, key)); g_debug ("Adding object %s (%s → %p) to the storage", g_type_name (G_OBJECT_TYPE (object)), key, object); g_hash_table_insert (_instance->id_to_object, g_strdup (key), g_object_ref (object)); } /** * cc_object_storage_get_object: * @key: the unique string identifier of the object * * Retrieves the object associated with @key. It is a programming error to * try to retrieve an object before adding it. * * Always check if the object is stored with cc_object_storage_has_object() * before calling this function. * * Returns: (transfer full): the GObject associated with @key. */ gpointer cc_object_storage_get_object (const gchar *key) { /* Trying to peek an object that was not yet added is a hard error. Users * of this API need to first check if the object is available with * cc_object_storage_has_object(). */ g_assert (CC_IS_OBJECT_STORAGE (_instance)); g_assert (key != NULL); g_assert (g_hash_table_contains (_instance->id_to_object, key)); return g_object_ref (g_hash_table_lookup (_instance->id_to_object, key)); } /** * cc_object_storage_create_dbus_proxy_sync: * @name: the D-Bus name * @flags: the D-Bus proxy flags * @path: the D-Bus object path * @interface: the D-Bus interface name * @cancellable: (nullable): #GCancellable to cancel the operation * @error: (nullable): return location for a #GError * * Synchronously create a #GDBusProxy with @name, @path and @interface, * stores it in the cache, and returns the newly created proxy. * * If a proxy with that signature is already created, it will be used * instead of creating a new one. * * Returns: (transfer full)(nullable): the new #GDBusProxy. */ gpointer cc_object_storage_create_dbus_proxy_sync (GBusType bus_type, GDBusProxyFlags flags, const gchar *name, const gchar *path, const gchar *interface, GCancellable *cancellable, GError **error) { g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GError) local_error = NULL; g_autofree gchar *key = NULL; g_assert (CC_IS_OBJECT_STORAGE (_instance)); g_assert (name && *name); g_assert (path && *path); g_assert (interface && *interface); g_assert (!error || !*error); key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface); g_debug ("Creating D-Bus proxy for %s", key); /* Check if a DBus proxy with that signature is already available; if it is, * return that instead of a new one. */ if (g_hash_table_contains (_instance->id_to_object, key)) return cc_object_storage_get_object (key); proxy = g_dbus_proxy_new_for_bus_sync (bus_type, flags, NULL, name, path, interface, cancellable, &local_error); if (local_error) { g_propagate_error (error, g_steal_pointer (&local_error)); return NULL; } /* Store the newly created D-Bus proxy */ cc_object_storage_add_object (key, proxy); return g_steal_pointer (&proxy); } /** * cc_object_storage_create_dbus_proxy: * @name: the D-Bus name * @flags: the D-Bus proxy flags * @path: the D-Bus object path * @interface: the D-Bus interface name * @cancellable: (nullable): #GCancellable to cancel the operation * @callback: callback for when the async operation is finished * @user_data: user data for @callback * * Asynchronously create a #GDBusProxy with @name, @path and @interface. * * If a proxy with that signature is already created, it will be used instead of * creating a new one. * * It is a programming error to create the an identical proxy while asynchronously * creating one. Not cancelling this operation will result in an assertion failure * when calling cc_object_storage_create_dbus_proxy_finish(). */ void cc_object_storage_create_dbus_proxy (GBusType bus_type, GDBusProxyFlags flags, const gchar *name, const gchar *path, const gchar *interface, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GTask) task = NULL; g_autofree gchar *key = NULL; TaskData *data = NULL; g_assert (CC_IS_OBJECT_STORAGE (_instance)); g_assert (name && *name); g_assert (path && *path); g_assert (interface && *interface); g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); data = task_data_new (bus_type, flags, name, path, interface); task = g_task_new (_instance, cancellable, callback, user_data); g_task_set_source_tag (task, cc_object_storage_create_dbus_proxy); g_task_set_task_data (task, data, (GDestroyNotify) task_data_free); /* Check if the D-Bus proxy is already created */ key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface); g_debug ("Asynchronously creating D-Bus proxy for %s", key); if (g_hash_table_contains (_instance->id_to_object, key)) { /* Mark this GTask as already cached, so we can call the right assertions * on the callback * */ data->cached = TRUE; g_debug ("Found in cache the D-Bus proxy %s", key); g_task_return_pointer (task, cc_object_storage_get_object (key), g_object_unref); return; } g_task_run_in_thread (task, create_dbus_proxy_in_thread_cb); } /** * cc_object_storage_create_dbus_proxy_finish: * @result: * @error: (nullable): return location for a #GError * * Finishes a D-Bus proxy creation started by cc_object_storage_create_dbus_proxy(). * * Synchronously create a #GDBusProxy with @name, @path and @interface, * stores it in the cache, and returns the newly created proxy. * * If a proxy with that signature is already created, it will be used * instead of creating a new one. * * Returns: (transfer full)(nullable): the new #GDBusProxy. */ gpointer cc_object_storage_create_dbus_proxy_finish (GAsyncResult *result, GError **error) { g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GError) local_error = NULL; g_autofree gchar *key = NULL; TaskData *task_data; GTask *task; task = G_TASK (result); g_assert (task && G_TASK (result)); g_assert (!error || !*error); task_data = g_task_get_task_data (task); g_assert (task_data != NULL); key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", task_data->name, task_data->path, task_data->interface); g_debug ("Finished creating D-Bus proxy for %s", key); /* Retrieve the newly created proxy */ proxy = g_task_propagate_pointer (task, &local_error); /* If the proxy is not cached, do the normal caching routine */ if (local_error) { g_propagate_error (error, g_steal_pointer (&local_error)); return NULL; } /* Either we have the object stored right when trying to create it - in which case, * task_data->cached == TRUE and cc_object_storage_has_object (key) == TRUE - or we * didn't have a cached proxy before, and we shouldn't have it now. * * This is to force consumers of this code to *never* try to create the same D-Bus * proxy asynchronously multiple times. Trying to do so is considered a programming * error. */ g_assert (task_data->cached == cc_object_storage_has_object (key)); /* If the proxy is already cached, destroy the newly created and used the cached proxy * instead. */ if (cc_object_storage_has_object (key)) return cc_object_storage_get_object (key); /* Store the newly created D-Bus proxy */ cc_object_storage_add_object (key, proxy); return g_steal_pointer (&proxy); } /** * cc_object_storage_initialize: * * Initializes the single CcObjectStorage. This must be called only once, * and before every other method of this object. */ void cc_object_storage_initialize (void) { g_assert (_instance == NULL); if (g_once_init_enter (&_instance)) { CcObjectStorage *instance = g_object_new (CC_TYPE_OBJECT_STORAGE, NULL); g_debug ("Initializing object storage"); g_once_init_leave (&_instance, instance); } } /** * cc_object_storage_destroy: * * Destroys the instance of #CcObjectStorage. This must be called only * once during the application lifetime. It is a programming error to * call this function multiple times */ void cc_object_storage_destroy (void) { g_assert (_instance != NULL); g_clear_object (&_instance); }