summaryrefslogtreecommitdiffstats
path: root/shell/cc-object-storage.c
diff options
context:
space:
mode:
Diffstat (limited to 'shell/cc-object-storage.c')
-rw-r--r--shell/cc-object-storage.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/shell/cc-object-storage.c b/shell/cc-object-storage.c
new file mode 100644
index 0000000..f52ba59
--- /dev/null
+++ b/shell/cc-object-storage.c
@@ -0,0 +1,444 @@
+/* cc-object-storage.h
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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, 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);
+}