summaryrefslogtreecommitdiff
path: root/libsecret/secret-service.c
diff options
context:
space:
mode:
authorStef Walter <stefw@gnome.org>2012-07-13 10:32:36 +0200
committerStef Walter <stefw@gnome.org>2012-07-13 10:40:25 +0200
commit5cc30b2b68f551506bd9bbbe1306a24009e14eca (patch)
treec1695c2920e4cf8645ab6b4f7dc05a9514bb8356 /libsecret/secret-service.c
parent931f677c7ac8af0a6ebed2e3ef7472a7076bc8be (diff)
downloadlibsecret-5cc30b2b68f551506bd9bbbe1306a24009e14eca.tar.gz
Rename the library subdirectory to libsecret
* Death by a thousand paper cuts from gir and vapi not liking the fact that the secret.h file was not usable uninstalled and installed in the same way.
Diffstat (limited to 'libsecret/secret-service.c')
-rw-r--r--libsecret/secret-service.c1724
1 files changed, 1724 insertions, 0 deletions
diff --git a/libsecret/secret-service.c b/libsecret/secret-service.c
new file mode 100644
index 0000000..f58f2ff
--- /dev/null
+++ b/libsecret/secret-service.c
@@ -0,0 +1,1724 @@
+/* libsecret - GLib wrapper for Secret Service
+ *
+ * Copyright 2011 Collabora Ltd.
+ * Copyright 2012 Red Hat Inc.
+ *
+ * This program 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; either version 2.1 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: Stef Walter <stefw@gnome.org>
+ */
+
+#include "config.h"
+
+#include "secret-collection.h"
+#include "secret-dbus-generated.h"
+#include "secret-enum-types.h"
+#include "secret-item.h"
+#include "secret-paths.h"
+#include "secret-private.h"
+#include "secret-service.h"
+#include "secret-types.h"
+#include "secret-value.h"
+
+#include "egg/egg-secure-memory.h"
+
+/**
+ * SECTION:secret-service
+ * @title: SecretService
+ * @short_description: the Secret Service
+ *
+ * A #SecretService object represents the Secret Service implementation which
+ * runs as a D-Bus service.
+ *
+ * Normally a single #SecretService object can be shared between multiple
+ * callers. The secret_service_get() method is used to access this #SecretService
+ * object. If a new independent #SecretService object is required, use
+ * secret_service_new().
+ *
+ * In order to securely transfer secrets to the Sercret Service, an session
+ * is established. This session can be established while initializing a
+ * #SecretService object by passing the %SECRET_SERVICE_OPEN_SESSION flag
+ * to the secret_service_get() or secret_service_new() functions. In order to
+ * establish a session on an already existing #SecretService, use the
+ * secret_service_ensure_session() function.
+ *
+ * To search for items, use the secret_service_search() method.
+ *
+ * Multiple collections can exist in the Secret Service, each of which contains
+ * secret items. In order to instantiate #SecretCollection objects which
+ * represent those collections while initializing a #SecretService then pass
+ * the %SECRET_SERVICE_LOAD_COLLECTIONS flag to the secret_service_get() or
+ * secret_service_new() functions. In order to establish a session on an already
+ * existing #SecretService, use the secret_service_load_collections() function.
+ * To access the list of collections use secret_service_get_collections().
+ *
+ * Certain actions on the Secret Service require user prompting to complete,
+ * such as creating a collection, or unlocking a collection. When such a prompt
+ * is necessary, then a #SecretPrompt object is created by this library, and
+ * passed to the secret_service_prompt() method. In this way it is handled
+ * automatically.
+ *
+ * In order to customize prompt handling, override the <literal>prompt_async</literal>
+ * and <literal>prompt_finish</literal> virtual methods of the #SecretService class.
+ *
+ * Stability: Unstable
+ */
+
+/**
+ * SecretService:
+ *
+ * A proxy object representing the Secret Service.
+ */
+
+/**
+ * SecretServiceClass:
+ * @parent_class: the parent class
+ * @collection_gtype: the #GType of the #SecretCollection objects instantiated
+ * by the #SecretService proxy
+ * @item_gtype: the #GType of the #SecretItem objects instantiated by the
+ * #SecretService proxy
+ * @prompt_async: called to perform asynchronous prompting when necessary
+ * @prompt_finish: called to complete an asynchronous prompt operation
+ * @prompt_sync: called to perform synchronous prompting when necessary
+ *
+ * The class for #SecretService.
+ */
+
+/**
+ * SecretServiceFlags:
+ * @SECRET_SERVICE_NONE: no flags for initializing the #SecretService
+ * @SECRET_SERVICE_OPEN_SESSION: establish a session for transfer of secrets
+ * while initializing the #SecretService
+ * @SECRET_SERVICE_LOAD_COLLECTIONS: load collections while initializing the
+ * #SecretService
+ *
+ * Flags which determine which parts of the #SecretService proxy are initialized
+ * during a secret_service_get() or secret_service_new() operation.
+ */
+
+EGG_SECURE_GLIB_DEFINITIONS ();
+
+GQuark _secret_error_quark = 0;
+static const gchar *default_bus_name = SECRET_SERVICE_BUS_NAME;
+
+enum {
+ PROP_0,
+ PROP_FLAGS,
+ PROP_COLLECTIONS
+};
+
+struct _SecretServicePrivate {
+ /* No change between construct and finalize */
+ GCancellable *cancellable;
+ SecretServiceFlags init_flags;
+
+ /* Locked by mutex */
+ GMutex mutex;
+ gpointer session;
+ GHashTable *collections;
+};
+
+G_LOCK_DEFINE (service_instance);
+static gpointer service_instance = NULL;
+static guint service_watch = 0;
+
+static GInitableIface *secret_service_initable_parent_iface = NULL;
+
+static GAsyncInitableIface *secret_service_async_initable_parent_iface = NULL;
+
+static void secret_service_initable_iface (GInitableIface *iface);
+
+static void secret_service_async_initable_iface (GAsyncInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SecretService, secret_service, G_TYPE_DBUS_PROXY,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, secret_service_initable_iface);
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, secret_service_async_initable_iface);
+);
+
+static SecretService *
+service_get_instance (void)
+{
+ SecretService *instance = NULL;
+
+ G_LOCK (service_instance);
+ if (service_instance != NULL)
+ instance = g_object_ref (service_instance);
+ G_UNLOCK (service_instance);
+
+ return instance;
+}
+
+static gboolean
+service_uncache_instance (SecretService *which)
+{
+ SecretService *instance = NULL;
+ guint watch = 0;
+ gboolean matched = FALSE;
+
+ G_LOCK (service_instance);
+ if (which == NULL || service_instance == which) {
+ instance = service_instance;
+ service_instance = NULL;
+ watch = service_watch;
+ service_watch = 0;
+ matched = TRUE;
+ }
+ G_UNLOCK (service_instance);
+
+ if (instance != NULL)
+ g_object_unref (instance);
+ if (watch != 0)
+ g_bus_unwatch_name (watch);
+
+ return matched;
+}
+
+static void
+on_service_instance_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ if (!service_uncache_instance (user_data)) {
+ g_warning ("Global default SecretService instance out of sync "
+ "with the watch for its DBus name");
+ }
+}
+
+static void
+service_cache_instance (SecretService *instance)
+{
+ GDBusProxy *proxy;
+ guint watch;
+
+ g_object_ref (instance);
+ proxy = G_DBUS_PROXY (instance);
+ watch = g_bus_watch_name_on_connection (g_dbus_proxy_get_connection (proxy),
+ g_dbus_proxy_get_name (proxy),
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+ NULL, on_service_instance_vanished,
+ instance, NULL);
+
+ G_LOCK (service_instance);
+ if (service_instance == NULL) {
+ service_instance = instance;
+ instance = NULL;
+ service_watch = watch;
+ watch = 0;
+ }
+ G_UNLOCK (service_instance);
+
+ if (instance != NULL)
+ g_object_unref (instance);
+ if (watch != 0)
+ g_bus_unwatch_name (watch);
+}
+
+static void
+secret_service_init (SecretService *self)
+{
+ self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, SECRET_TYPE_SERVICE,
+ SecretServicePrivate);
+
+ g_mutex_init (&self->pv->mutex);
+ self->pv->cancellable = g_cancellable_new ();
+}
+
+static void
+secret_service_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SecretService *self = SECRET_SERVICE (obj);
+
+ switch (prop_id) {
+ case PROP_FLAGS:
+ g_value_set_flags (value, secret_service_get_flags (self));
+ break;
+ case PROP_COLLECTIONS:
+ g_value_take_boxed (value, secret_service_get_collections (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+secret_service_set_property (GObject *obj,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SecretService *self = SECRET_SERVICE (obj);
+
+ switch (prop_id) {
+ case PROP_FLAGS:
+ self->pv->init_flags = g_value_get_flags (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+secret_service_dispose (GObject *obj)
+{
+ SecretService *self = SECRET_SERVICE (obj);
+
+ g_cancellable_cancel (self->pv->cancellable);
+
+ G_OBJECT_CLASS (secret_service_parent_class)->dispose (obj);
+}
+
+static void
+secret_service_finalize (GObject *obj)
+{
+ SecretService *self = SECRET_SERVICE (obj);
+
+ _secret_session_free (self->pv->session);
+ if (self->pv->collections)
+ g_hash_table_destroy (self->pv->collections);
+ g_clear_object (&self->pv->cancellable);
+ g_mutex_clear (&self->pv->mutex);
+
+ G_OBJECT_CLASS (secret_service_parent_class)->finalize (obj);
+}
+
+static GVariant *
+secret_service_real_prompt_sync (SecretService *self,
+ SecretPrompt *prompt,
+ GCancellable *cancellable,
+ const GVariantType *return_type,
+ GError **error)
+{
+ return secret_prompt_perform_sync (prompt, 0, cancellable, return_type, error);
+}
+
+static void
+on_real_prompt_completed (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+ GError *error = NULL;
+ GVariant *retval;
+
+ retval = secret_prompt_perform_finish (SECRET_PROMPT (source), result, NULL, &error);
+ if (retval != NULL)
+ g_simple_async_result_set_op_res_gpointer (res, retval, (GDestroyNotify)g_variant_unref);
+ if (error != NULL)
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+static void
+secret_service_real_prompt_async (SecretService *self,
+ SecretPrompt *prompt,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ secret_service_real_prompt_async);
+
+ secret_prompt_perform (prompt, 0, cancellable,
+ on_real_prompt_completed,
+ g_object_ref (res));
+
+ g_object_unref (res);
+}
+
+static GVariant *
+secret_service_real_prompt_finish (SecretService *self,
+ GAsyncResult *result,
+ const GVariantType *return_type,
+ GError **error)
+{
+ GSimpleAsyncResult *res;
+ GVariant *retval;
+ gchar *string;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+ secret_service_real_prompt_async), NULL);
+
+ res = G_SIMPLE_ASYNC_RESULT (result);
+ if (g_simple_async_result_propagate_error (res, error))
+ return NULL;
+
+ retval = g_simple_async_result_get_op_res_gpointer (res);
+ if (retval == NULL)
+ return NULL;
+
+ if (return_type != NULL && !g_variant_is_of_type (retval, return_type)) {
+ string = g_variant_type_dup_string (return_type);
+ g_warning ("received unexpected result type %s from prompt's Completed signal instead of expected %s",
+ g_variant_get_type_string (retval), string);
+ g_free (string);
+ return NULL;
+ }
+
+ return g_variant_ref (retval);
+}
+
+static void
+handle_property_changed (SecretService *self,
+ const gchar *property_name,
+ GVariant *value)
+{
+ gboolean perform;
+
+ g_variant_ref_sink (value);
+
+ if (g_str_equal (property_name, "Collections")) {
+
+ g_mutex_lock (&self->pv->mutex);
+ perform = self->pv->collections != NULL;
+ g_mutex_unlock (&self->pv->mutex);
+
+ if (perform)
+ secret_service_load_collections (self, self->pv->cancellable, NULL, NULL);
+ }
+
+ g_variant_unref (value);
+}
+
+static void
+secret_service_properties_changed (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ const gchar* const *invalidated_properties)
+{
+ SecretService *self = SECRET_SERVICE (proxy);
+ gchar *property_name;
+ GVariantIter iter;
+ GVariant *value;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ g_variant_iter_init (&iter, changed_properties);
+ while (g_variant_iter_loop (&iter, "{sv}", &property_name, &value))
+ handle_property_changed (self, property_name, value);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+secret_service_signal (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters)
+{
+ SecretService *self = SECRET_SERVICE (proxy);
+ SecretCollection *collection;
+ const gchar *collection_path;
+ GVariantBuilder builder;
+ gboolean found = FALSE;
+ GVariantIter iter;
+ GVariant *value;
+ GVariant *paths;
+ GVariant *path;
+
+ /*
+ * Remember that these signals come from a time before PropertiesChanged.
+ * We support them because they're in the spec, and ksecretservice uses them.
+ */
+
+ paths = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (self), "Collections");
+
+ /* A new collection was added, add it to the Collections property */
+ if (g_str_equal (signal_name, SECRET_SIGNAL_COLLECTION_CREATED)) {
+ g_variant_get (parameters, "(@o)", &value);
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("ao"));
+ g_variant_iter_init (&iter, paths);
+ while ((path = g_variant_iter_next_value (&iter)) != NULL) {
+ if (g_variant_equal (path, value)) {
+ found = TRUE;
+ break;
+ }
+ g_variant_builder_add_value (&builder, path);
+ g_variant_unref (path);
+ }
+ if (!found) {
+ g_variant_builder_add_value (&builder, value);
+ handle_property_changed (self, "Collections", g_variant_builder_end (&builder));
+ }
+ g_variant_builder_clear (&builder);
+ g_variant_unref (value);
+
+ /* A collection was deleted, remove it from the Collections property */
+ } else if (g_str_equal (signal_name, SECRET_SIGNAL_COLLECTION_DELETED)) {
+ g_variant_get (parameters, "(@o)", &value);
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("ao"));
+ g_variant_iter_init (&iter, paths);
+ while ((path = g_variant_iter_next_value (&iter)) != NULL) {
+ if (g_variant_equal (path, value))
+ found = TRUE;
+ else
+ g_variant_builder_add_value (&builder, path);
+ g_variant_unref (path);
+ }
+ if (found)
+ handle_property_changed (self, "Collections", g_variant_builder_end (&builder));
+ g_variant_unref (value);
+
+ /* The collection changed, update it */
+ } else if (g_str_equal (signal_name, SECRET_SIGNAL_COLLECTION_CHANGED)) {
+ g_variant_get (parameters, "(&o)", &collection_path);
+
+ g_mutex_lock (&self->pv->mutex);
+
+ if (self->pv->collections)
+ collection = g_hash_table_lookup (self->pv->collections, collection_path);
+ else
+ collection = NULL;
+ if (collection)
+ g_object_ref (collection);
+
+ g_mutex_unlock (&self->pv->mutex);
+
+ if (collection) {
+ secret_collection_refresh (collection);
+ g_object_unref (collection);
+ }
+ }
+
+ g_variant_unref (paths);
+}
+
+static void
+secret_service_class_init (SecretServiceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GDBusProxyClass *proxy_class = G_DBUS_PROXY_CLASS (klass);
+
+ object_class->get_property = secret_service_get_property;
+ object_class->set_property = secret_service_set_property;
+ object_class->dispose = secret_service_dispose;
+ object_class->finalize = secret_service_finalize;
+
+ proxy_class->g_properties_changed = secret_service_properties_changed;
+ proxy_class->g_signal = secret_service_signal;
+
+ klass->prompt_sync = secret_service_real_prompt_sync;
+ klass->prompt_async = secret_service_real_prompt_async;
+ klass->prompt_finish = secret_service_real_prompt_finish;
+
+ klass->item_gtype = SECRET_TYPE_ITEM;
+ klass->collection_gtype = SECRET_TYPE_COLLECTION;
+
+ /**
+ * SecretService:flags:
+ *
+ * A set of flags describing which parts of the secret service have
+ * been initialized.
+ */
+ g_object_class_install_property (object_class, PROP_FLAGS,
+ g_param_spec_flags ("flags", "Flags", "Service flags",
+ secret_service_flags_get_type (), SECRET_SERVICE_NONE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SecretService:collections:
+ *
+ * A list of #SecretCollection objects representing the collections in
+ * the Secret Service. This list may be %NULL if the collections have
+ * not been loaded.
+ *
+ * To load the collections, specify the %SECRET_SERVICE_LOAD_COLLECTIONS
+ * initialization flag when calling the secret_service_get() or
+ * secret_service_new() functions. Or call the secret_service_load_collections()
+ * method.
+ */
+ g_object_class_install_property (object_class, PROP_COLLECTIONS,
+ g_param_spec_boxed ("collections", "Collections", "Secret Service Collections",
+ _secret_list_get_type (), G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (klass, sizeof (SecretServicePrivate));
+
+ /* Initialize this error domain, registers dbus errors */
+ _secret_error_quark = secret_error_get_quark ();
+}
+
+typedef struct {
+ GCancellable *cancellable;
+ SecretServiceFlags flags;
+} InitClosure;
+
+static void
+init_closure_free (gpointer data)
+{
+ InitClosure *closure = data;
+ g_clear_object (&closure->cancellable);
+ g_slice_free (InitClosure, closure);
+}
+
+static gboolean
+service_ensure_for_flags_sync (SecretService *self,
+ SecretServiceFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (flags & SECRET_SERVICE_OPEN_SESSION)
+ if (!secret_service_ensure_session_sync (self, cancellable, error))
+ return FALSE;
+
+ if (flags & SECRET_SERVICE_LOAD_COLLECTIONS)
+ if (!secret_service_load_collections_sync (self, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+on_load_collections (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+ SecretService *self = SECRET_SERVICE (source);
+ GError *error = NULL;
+
+ if (!secret_service_load_collections_finish (self, result, &error))
+ g_simple_async_result_take_error (res, error);
+
+ g_simple_async_result_complete (res);
+ g_object_unref (res);
+}
+
+static void
+on_ensure_session (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+ InitClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
+ SecretService *self = SECRET_SERVICE (source);
+ GError *error = NULL;
+
+ if (!secret_service_ensure_session_finish (self, result, &error)) {
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+
+ } else if (closure->flags & SECRET_SERVICE_LOAD_COLLECTIONS) {
+ secret_service_load_collections (self, closure->cancellable,
+ on_load_collections, g_object_ref (res));
+
+ } else {
+ g_simple_async_result_complete_in_idle (res);
+ }
+
+ g_object_unref (res);
+}
+
+static void
+service_ensure_for_flags_async (SecretService *self,
+ SecretServiceFlags flags,
+ GSimpleAsyncResult *res)
+{
+ InitClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
+
+ closure->flags = flags;
+
+ if (closure->flags & SECRET_SERVICE_OPEN_SESSION)
+ secret_service_ensure_session (self, closure->cancellable,
+ on_ensure_session, g_object_ref (res));
+
+ else if (closure->flags & SECRET_SERVICE_LOAD_COLLECTIONS)
+ secret_service_load_collections (self, closure->cancellable,
+ on_load_collections, g_object_ref (res));
+
+ else
+ g_simple_async_result_complete_in_idle (res);
+}
+
+static gboolean
+secret_service_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SecretService *self;
+
+ if (!secret_service_initable_parent_iface->init (initable, cancellable, error))
+ return FALSE;
+
+ self = SECRET_SERVICE (initable);
+ return service_ensure_for_flags_sync (self, self->pv->init_flags, cancellable, error);
+}
+
+static void
+secret_service_initable_iface (GInitableIface *iface)
+{
+ secret_service_initable_parent_iface = g_type_interface_peek_parent (iface);
+
+ iface->init = secret_service_initable_init;
+}
+
+static void
+on_init_base (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+ SecretService *self = SECRET_SERVICE (source);
+ GError *error = NULL;
+
+ if (!secret_service_async_initable_parent_iface->init_finish (G_ASYNC_INITABLE (self),
+ result, &error)) {
+ g_simple_async_result_take_error (res, error);
+ g_simple_async_result_complete (res);
+ } else {
+ service_ensure_for_flags_async (self, self->pv->init_flags, res);
+ }
+
+ g_object_unref (res);
+}
+
+static void
+secret_service_async_initable_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+ InitClosure *closure;
+
+ res = g_simple_async_result_new (G_OBJECT (initable), callback, user_data,
+ secret_service_async_initable_init_async);
+ closure = g_slice_new0 (InitClosure);
+ closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ g_simple_async_result_set_op_res_gpointer (res, closure, init_closure_free);
+
+ secret_service_async_initable_parent_iface->init_async (initable, io_priority,
+ cancellable,
+ on_init_base,
+ g_object_ref (res));
+
+ g_object_unref (res);
+}
+
+static gboolean
+secret_service_async_initable_init_finish (GAsyncInitable *initable,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (initable),
+ secret_service_async_initable_init_async), FALSE);
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+secret_service_async_initable_iface (GAsyncInitableIface *iface)
+{
+ secret_service_async_initable_parent_iface = g_type_interface_peek_parent (iface);
+
+ iface->init_async = secret_service_async_initable_init_async;
+ iface->init_finish = secret_service_async_initable_init_finish;
+}
+
+void
+_secret_service_set_default_bus_name (const gchar *bus_name)
+{
+ g_return_if_fail (bus_name != NULL);
+ default_bus_name = bus_name;
+}
+
+/**
+ * secret_service_get:
+ * @flags: flags for which service functionality to ensure is initialized
+ * @cancellable: optional cancellation object
+ * @callback: called when the operation completes
+ * @user_data: data to be passed to the callback
+ *
+ * Get a #SecretService proxy for the Secret Service. If such a proxy object
+ * already exists, then the same proxy is returned.
+ *
+ * If @flags contains any flags of which parts of the secret service to
+ * ensure are initialized, then those will be initialized before completing.
+ *
+ * This method will return immediately and complete asynchronously.
+ */
+void
+secret_service_get (SecretServiceFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SecretService *service = NULL;
+ GSimpleAsyncResult *res;
+ InitClosure *closure;
+
+ service = service_get_instance ();
+
+ /* Create a whole new service */
+ if (service == NULL) {
+ g_async_initable_new_async (SECRET_TYPE_SERVICE, G_PRIORITY_DEFAULT,
+ cancellable, callback, user_data,
+ "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+ "g-interface-info", _secret_gen_service_interface_info (),
+ "g-name", default_bus_name,
+ "g-bus-type", G_BUS_TYPE_SESSION,
+ "g-object-path", SECRET_SERVICE_PATH,
+ "g-interface-name", SECRET_SERVICE_INTERFACE,
+ "flags", flags,
+ NULL);
+
+ /* Just have to ensure that the service matches flags */
+ } else {
+ res = g_simple_async_result_new (G_OBJECT (service), callback,
+ user_data, secret_service_get);
+ closure = g_slice_new0 (InitClosure);
+ closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ closure->flags = flags;
+ g_simple_async_result_set_op_res_gpointer (res, closure, init_closure_free);
+
+ service_ensure_for_flags_async (service, flags, res);
+
+ g_object_unref (service);
+ g_object_unref (res);
+ }
+}
+
+/**
+ * secret_service_get_finish:
+ * @result: the asynchronous result passed to the callback
+ * @error: location to place an error on failure
+ *
+ * Complete an asynchronous operation to get a #SecretService proxy for the
+ * Secret Service.
+ *
+ * Returns: (transfer full): a new reference to a #SecretService proxy, which
+ * should be released with g_object_unref().
+ */
+SecretService *
+secret_service_get_finish (GAsyncResult *result,
+ GError **error)
+{
+ GObject *service = NULL;
+ GObject *source_object;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ source_object = g_async_result_get_source_object (result);
+
+ /* Just ensuring that the service matches flags */
+ if (g_simple_async_result_is_valid (result, source_object, secret_service_get)) {
+ if (!g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+ service = g_object_ref (source_object);
+
+ /* Creating a whole new service */
+ } else {
+ service = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, error);
+ if (service)
+ service_cache_instance (SECRET_SERVICE (service));
+ }
+
+ if (source_object)
+ g_object_unref (source_object);
+
+ if (service == NULL)
+ return NULL;
+
+ return SECRET_SERVICE (service);
+}
+
+/**
+ * secret_service_get_sync:
+ * @flags: flags for which service functionality to ensure is initialized
+ * @cancellable: optional cancellation object
+ * @error: location to place an error on failure
+ *
+ * Get a #SecretService proxy for the Secret Service. If such a proxy object
+ * already exists, then the same proxy is returned.
+ *
+ * If @flags contains any flags of which parts of the secret service to
+ * ensure are initialized, then those will be initialized before returning.
+ *
+ * This method may block indefinitely and should not be used in user interface
+ * threads.
+ *
+ * Returns: (transfer full): a new reference to a #SecretService proxy, which
+ * should be released with g_object_unref().
+ */
+SecretService *
+secret_service_get_sync (SecretServiceFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SecretService *service = NULL;
+
+ service = service_get_instance ();
+
+ if (service == NULL) {
+ service = g_initable_new (SECRET_TYPE_SERVICE, cancellable, error,
+ "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+ "g-interface-info", _secret_gen_service_interface_info (),
+ "g-name", default_bus_name,
+ "g-bus-type", G_BUS_TYPE_SESSION,
+ "g-object-path", SECRET_SERVICE_PATH,
+ "g-interface-name", SECRET_SERVICE_INTERFACE,
+ "flags", flags,
+ NULL);
+
+ if (service != NULL)
+ service_cache_instance (service);
+
+ } else {
+ if (!service_ensure_for_flags_sync (service, flags, cancellable, error)) {
+ g_object_unref (service);
+ return NULL;
+ }
+ }
+
+ return service;
+}
+
+/**
+ * secret_service_disconnect:
+ *
+ * Disconnect the default #SecretService proxy returned by secret_service_get()
+ * and secret_service_get_sync().
+ *
+ * It is not necessary to call this function, but you may choose to do so at
+ * program exit. It is useful for testing that memory is not leaked.
+ *
+ * This function is safe to call at any time. But if other objects in this
+ * library are still referenced, then this will not result in all memory
+ * being freed.
+ */
+void
+secret_service_disconnect (void)
+{
+ service_uncache_instance (NULL);
+}
+
+/**
+ * secret_service_new:
+ * @service_gtype: the GType of the new secret service
+ * @service_bus_name: (allow-none): the D-Bus service name of the secret service
+ * @flags: flags for which service functionality to ensure is initialized
+ * @cancellable: optional cancellation object
+ * @callback: called when the operation completes
+ * @user_data: data to be passed to the callback
+ *
+ * Create a new #SecretService proxy for the Secret Service.
+ *
+ * This function is rarely used, see secret_service_get() instead.
+ *
+ * The @service_gtype argument should be set to %SECRET_TYPE_SERVICE or a the type
+ * of a derived class.
+ *
+ * If @flags contains any flags of which parts of the secret service to
+ * ensure are initialized, then those will be initialized before returning.
+ *
+ * If @service_bus_name is %NULL then the default is used.
+ *
+ * This method will return immediately and complete asynchronously.
+ */
+void
+secret_service_new (GType service_gtype,
+ const gchar *service_bus_name,
+ SecretServiceFlags flags,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (g_type_is_a (service_gtype, SECRET_TYPE_SERVICE));
+
+ if (service_bus_name == NULL)
+ service_bus_name = default_bus_name;
+
+ g_async_initable_new_async (service_gtype, G_PRIORITY_DEFAULT,
+ cancellable, callback, user_data,
+ "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+ "g-interface-info", _secret_gen_service_interface_info (),
+ "g-name", service_bus_name,
+ "g-bus-type", G_BUS_TYPE_SESSION,
+ "g-object-path", SECRET_SERVICE_PATH,
+ "g-interface-name", SECRET_SERVICE_INTERFACE,
+ "flags", flags,
+ NULL);
+}
+
+/**
+ * secret_service_new_finish:
+ * @result: the asynchronous result passed to the callback
+ * @error: location to place an error on failure
+ *
+ * Complete an asynchronous operation to create a new #SecretService proxy for
+ * the Secret Service.
+ *
+ * Returns: (transfer full): a new reference to a #SecretService proxy, which
+ * should be released with g_object_unref().
+ */
+SecretService *
+secret_service_new_finish (GAsyncResult *result,
+ GError **error)
+{
+ GObject *source_object;
+ GObject *object;
+
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ source_object = g_async_result_get_source_object (result);
+ object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+ result, error);
+ g_object_unref (source_object);
+
+ if (object == NULL)
+ return NULL;
+
+ return SECRET_SERVICE (object);
+}
+
+/**
+ * secret_service_new_sync:
+ * @service_gtype: the GType of the new secret service
+ * @service_bus_name: (allow-none): the D-Bus service name of the secret service
+ * @flags: flags for which service functionality to ensure is initialized
+ * @cancellable: optional cancellation object
+ * @error: location to place an error on failure
+ *
+ * Create a new #SecretService proxy for the Secret Service.
+ *
+ * This function is rarely used, see secret_service_get_sync() instead.
+ *
+ * The @service_gtype argument should be set to %SECRET_TYPE_SERVICE or a the
+ * type of a derived class.
+ *
+ * If @flags contains any flags of which parts of the secret service to
+ * ensure are initialized, then those will be initialized before returning.
+ *
+ * If @service_bus_name is %NULL then the default is used.
+ *
+ * This method may block indefinitely and should not be used in user interface
+ * threads.
+ *
+ * Returns: (transfer full): a new reference to a #SecretService proxy, which
+ * should be released with g_object_unref().
+ */
+SecretService *
+secret_service_new_sync (GType service_gtype,
+ const gchar *service_bus_name,
+ SecretServiceFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (g_type_is_a (service_gtype, SECRET_TYPE_SERVICE), NULL);
+
+ if (service_bus_name == NULL)
+ service_bus_name = default_bus_name;
+
+ return g_initable_new (service_gtype, cancellable, error,
+ "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+ "g-interface-info", _secret_gen_service_interface_info (),
+ "g-name", service_bus_name,
+ "g-bus-type", G_BUS_TYPE_SESSION,
+ "g-object-path", SECRET_SERVICE_PATH,
+ "g-interface-name", SECRET_SERVICE_INTERFACE,
+ "flags", flags,
+ NULL);
+}
+
+/**
+ * secret_service_get_flags:
+ * @self: the secret service proxy
+ *
+ * Get the flags representing what features of the #SecretService proxy
+ * have been initialized.
+ *
+ * Use secret_service_ensure_session() or secret_service_load_collections()
+ * to initialize further features and change the flags.
+ *
+ * Returns: the flags for features initialized
+ */
+SecretServiceFlags
+secret_service_get_flags (SecretService *self)
+{
+ SecretServiceFlags flags = 0;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), SECRET_SERVICE_NONE);
+
+ g_mutex_lock (&self->pv->mutex);
+
+ if (self->pv->session)
+ flags |= SECRET_SERVICE_OPEN_SESSION;
+ if (self->pv->collections)
+ flags |= SECRET_SERVICE_LOAD_COLLECTIONS;
+
+ g_mutex_unlock (&self->pv->mutex);
+
+ return flags;
+}
+
+/**
+ * secret_service_get_collections:
+ * @self: the secret service proxy
+ *
+ * Get a list of #SecretCollection objects representing all the collections
+ * in the secret service.
+ *
+ * If the %SECRET_SERVICE_LOAD_COLLECTIONS flag was not specified when
+ * initializing #SecretService proxy object, then this method will return
+ * %NULL. Use secret_service_load_collections() to load the collections.
+ *
+ * Returns: (transfer full) (element-type Secret.Collection) (allow-none): a
+ * list of the collections in the secret service
+ */
+GList *
+secret_service_get_collections (SecretService *self)
+{
+ GList *l, *collections;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), NULL);
+
+ g_mutex_lock (&self->pv->mutex);
+
+ if (self->pv->collections == NULL) {
+ collections = NULL;
+
+ } else {
+ collections = g_hash_table_get_values (self->pv->collections);
+ for (l = collections; l != NULL; l = g_list_next (l))
+ g_object_ref (l->data);
+ }
+
+ g_mutex_unlock (&self->pv->mutex);
+
+ return collections;
+}
+
+SecretItem *
+_secret_service_find_item_instance (SecretService *self,
+ const gchar *item_path)
+{
+ SecretCollection *collection = NULL;
+ gchar *collection_path;
+ SecretItem *item;
+
+ collection_path = _secret_util_parent_path (item_path);
+
+ collection = _secret_service_find_collection_instance (self, collection_path);
+
+ g_free (collection_path);
+
+ if (collection == NULL)
+ return NULL;
+
+ item = _secret_collection_find_item_instance (collection, item_path);
+ g_object_unref (collection);
+
+ return item;
+}
+
+SecretCollection *
+_secret_service_find_collection_instance (SecretService *self,
+ const gchar *collection_path)
+{
+ SecretCollection *collection = NULL;
+
+ g_mutex_lock (&self->pv->mutex);
+ if (self->pv->collections) {
+ collection = g_hash_table_lookup (self->pv->collections, collection_path);
+ if (collection != NULL)
+ g_object_ref (collection);
+ }
+ g_mutex_unlock (&self->pv->mutex);
+
+ return collection;
+}
+
+SecretSession *
+_secret_service_get_session (SecretService *self)
+{
+ SecretSession *session;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), NULL);
+
+ g_mutex_lock (&self->pv->mutex);
+ session = self->pv->session;
+ g_mutex_unlock (&self->pv->mutex);
+
+ return session;
+}
+
+void
+_secret_service_take_session (SecretService *self,
+ SecretSession *session)
+{
+ g_return_if_fail (SECRET_IS_SERVICE (self));
+ g_return_if_fail (session != NULL);
+
+ g_mutex_lock (&self->pv->mutex);
+ if (self->pv->session == NULL)
+ self->pv->session = session;
+ else
+ _secret_session_free (session);
+ g_mutex_unlock (&self->pv->mutex);
+}
+
+/**
+ * secret_service_get_session_algorithms:
+ * @self: the secret service proxy
+ *
+ * Get the set of algorithms being used to transfer secrets between this
+ * secret service proxy and the Secret Service itself.
+ *
+ * This will be %NULL if no session has been established. Use
+ * secret_service_ensure_session() to establish a session.
+ *
+ * Returns: (allow-none): a string representing the algorithms for transferring
+ * secrets
+ */
+const gchar *
+secret_service_get_session_algorithms (SecretService *self)
+{
+ SecretSession *session;
+ const gchar *algorithms;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), NULL);
+
+ g_mutex_lock (&self->pv->mutex);
+ session = self->pv->session;
+ algorithms = session ? _secret_session_get_algorithms (session) : NULL;
+ g_mutex_unlock (&self->pv->mutex);
+
+ /* Session never changes once established, so can return const */
+ return algorithms;
+}
+
+/**
+ * secret_service_get_session_dbus_path:
+ * @self: the secret service proxy
+ *
+ * Get the D-Bus object path of the session object being used to transfer
+ * secrets between this secret service proxy and the Secret Service itself.
+ *
+ * This will be %NULL if no session has been established. Use
+ * secret_service_ensure_session() to establish a session.
+ *
+ * Returns: (allow-none): a string representing the D-Bus object path of the
+ * session
+ */
+const gchar *
+secret_service_get_session_dbus_path (SecretService *self)
+{
+ SecretSession *session;
+ const gchar *path;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), NULL);
+
+ g_mutex_lock (&self->pv->mutex);
+ session = self->pv->session;
+ path = session ? _secret_session_get_path (session) : NULL;
+ g_mutex_unlock (&self->pv->mutex);
+
+ /* Session never changes once established, so can return const */
+ return path;
+}
+
+/**
+ * secret_service_ensure_session:
+ * @self: the secret service
+ * @cancellable: optional cancellation object
+ * @callback: called when the operation completes
+ * @user_data: data to be passed to the callback
+ *
+ * Ensure that the #SecretService proxy has established a session with the
+ * Secret Service. This session is used to transfer secrets.
+ *
+ * It is not normally necessary to call this method, as the session is
+ * established as necessary. You can also pass the %SECRET_SERVICE_OPEN_SESSION
+ * to secret_service_get() in order to ensure that a session has been established
+ * by the time you get the #SecretService proxy.
+ *
+ * This method will return immediately and complete asynchronously.
+ */
+void
+secret_service_ensure_session (SecretService *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res;
+ SecretSession *session;
+
+ g_return_if_fail (SECRET_IS_SERVICE (self));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ g_mutex_lock (&self->pv->mutex);
+ session = self->pv->session;
+ g_mutex_unlock (&self->pv->mutex);
+
+ if (session == NULL) {
+ _secret_session_open (self, cancellable, callback, user_data);
+
+ } else {
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ secret_service_ensure_session);
+ g_simple_async_result_complete_in_idle (res);
+ g_object_unref (res);
+ }
+}
+
+/**
+ * secret_service_ensure_session_finish:
+ * @self: the secret service
+ * @result: the asynchronous result passed to the callback
+ * @error: location to place an error on failure
+ *
+ * Finish an asynchronous operation to ensure that the #SecretService proxy
+ * has established a session with the Secret Service.
+ *
+ * Returns: whether a session is established or not
+ */
+gboolean
+secret_service_ensure_session_finish (SecretService *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (!g_simple_async_result_is_valid (result, G_OBJECT (self),
+ secret_service_ensure_session)) {
+ if (!_secret_session_open_finish (result, error))
+ return FALSE;
+ }
+
+ g_return_val_if_fail (self->pv->session != NULL, FALSE);
+ return TRUE;
+}
+
+/**
+ * secret_service_ensure_session_sync:
+ * @self: the secret service
+ * @cancellable: optional cancellation object
+ * @error: location to place an error on failure
+ *
+ * Ensure that the #SecretService proxy has established a session with the
+ * Secret Service. This session is used to transfer secrets.
+ *
+ * It is not normally necessary to call this method, as the session is
+ * established as necessary. You can also pass the %SECRET_SERVICE_OPEN_SESSION
+ * to secret_service_get_sync() in order to ensure that a session has been
+ * established by the time you get the #SecretService proxy.
+ *
+ * This method may block indefinitely and should not be used in user interface
+ * threads.
+ *
+ * Returns: whether a session is established or not
+ */
+gboolean
+secret_service_ensure_session_sync (SecretService *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SecretSync *sync;
+ gboolean ret;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ sync = _secret_sync_new ();
+ g_main_context_push_thread_default (sync->context);
+
+ secret_service_ensure_session (self, cancellable,
+ _secret_sync_on_result, sync);
+
+ g_main_loop_run (sync->loop);
+
+ ret = secret_service_ensure_session_finish (self, sync->result, error);
+
+ g_main_context_pop_thread_default (sync->context);
+ _secret_sync_free (sync);
+
+ return ret;
+}
+
+static SecretCollection *
+service_lookup_collection (SecretService *self,
+ const gchar *path)
+{
+ SecretCollection *collection = NULL;
+
+ g_mutex_lock (&self->pv->mutex);
+
+ if (self->pv->collections) {
+ collection = g_hash_table_lookup (self->pv->collections, path);
+ if (collection != NULL)
+ g_object_ref (collection);
+ }
+
+ g_mutex_unlock (&self->pv->mutex);
+
+ return collection;
+}
+
+static void
+service_update_collections (SecretService *self,
+ GHashTable *collections)
+{
+ GHashTable *previous;
+
+ g_hash_table_ref (collections);
+
+ g_mutex_lock (&self->pv->mutex);
+
+ previous = self->pv->collections;
+ self->pv->collections = collections;
+
+ g_mutex_unlock (&self->pv->mutex);
+
+ if (previous != NULL)
+ g_hash_table_unref (previous);
+
+ g_object_notify (G_OBJECT (self), "collections");
+}
+
+typedef struct {
+ GCancellable *cancellable;
+ GHashTable *collections;
+ gint collections_loading;
+} EnsureClosure;
+
+static GHashTable *
+collections_table_new (void)
+{
+ return g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+}
+
+static void
+ensure_closure_free (gpointer data)
+{
+ EnsureClosure *closure = data;
+ g_clear_object (&closure->cancellable);
+ g_hash_table_unref (closure->collections);
+ g_slice_free (EnsureClosure, closure);
+}
+
+static void
+on_ensure_collection (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+ SecretService *self = SECRET_SERVICE (g_async_result_get_source_object (user_data));
+ EnsureClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
+ SecretCollection *collection;
+ const gchar *path;
+ GError *error = NULL;
+
+ closure->collections_loading--;
+
+ collection = secret_collection_new_for_dbus_path_finish (result, &error);
+
+ if (error != NULL)
+ g_simple_async_result_take_error (res, error);
+
+ if (collection != NULL) {
+ path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (collection));
+ g_hash_table_insert (closure->collections, g_strdup (path), collection);
+ }
+
+ if (closure->collections_loading == 0) {
+ service_update_collections (self, closure->collections);
+ g_simple_async_result_complete (res);
+ }
+
+ g_object_unref (self);
+ g_object_unref (res);
+}
+
+/**
+ * secret_service_load_collections:
+ * @self: the secret service
+ * @cancellable: optional cancellation object
+ * @callback: called when the operation completes
+ * @user_data: data to be passed to the callback
+ *
+ * Ensure that the #SecretService proxy has loaded all the collections present
+ * in the Secret Service. This affects the result of
+ * secret_service_get_collections().
+ *
+ * You can also pass the %SECRET_SERVICE_LOAD_COLLECTIONS to
+ * secret_service_get_sync() in order to ensure that the collections have been
+ * loaded by the time you get the #SecretService proxy.
+ *
+ * This method will return immediately and complete asynchronously.
+ */
+void
+secret_service_load_collections (SecretService *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ EnsureClosure *closure;
+ SecretCollection *collection;
+ GSimpleAsyncResult *res;
+ const gchar *path;
+ GVariant *paths;
+ GVariantIter iter;
+
+ g_return_if_fail (SECRET_IS_SERVICE (self));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ paths = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (self), "Collections");
+ g_return_if_fail (paths != NULL);
+
+ res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ secret_service_load_collections);
+ closure = g_slice_new0 (EnsureClosure);
+ closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ closure->collections = collections_table_new ();
+ g_simple_async_result_set_op_res_gpointer (res, closure, ensure_closure_free);
+
+ g_variant_iter_init (&iter, paths);
+ while (g_variant_iter_loop (&iter, "&o", &path)) {
+ collection = service_lookup_collection (self, path);
+
+ /* No such collection yet create a new one */
+ if (collection == NULL) {
+ secret_collection_new_for_dbus_path (self, path, SECRET_COLLECTION_LOAD_ITEMS,
+ cancellable, on_ensure_collection, g_object_ref (res));
+ closure->collections_loading++;
+ } else {
+ g_hash_table_insert (closure->collections, g_strdup (path), collection);
+ }
+ }
+
+ if (closure->collections_loading == 0) {
+ service_update_collections (self, closure->collections);
+ g_simple_async_result_complete_in_idle (res);
+ }
+
+ g_variant_unref (paths);
+ g_object_unref (res);
+}
+
+/**
+ * secret_service_load_collections_finish:
+ * @self: the secret service
+ * @result: the asynchronous result passed to the callback
+ * @error: location to place an error on failure
+ *
+ * Complete an asynchronous operation to ensure that the #SecretService proxy
+ * has loaded all the collections present in the Secret Service.
+ *
+ * Returns: whether the load was successful or not
+ */
+gboolean
+secret_service_load_collections_finish (SecretService *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+ secret_service_load_collections), FALSE);
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * secret_service_load_collections_sync:
+ * @self: the secret service
+ * @cancellable: optional cancellation object
+ * @error: location to place an error on failure
+ *
+ * Ensure that the #SecretService proxy has loaded all the collections present
+ * in the Secret Service. This affects the result of
+ * secret_service_get_collections().
+ *
+ * You can also pass the %SECRET_SERVICE_LOAD_COLLECTIONS to
+ * secret_service_get_sync() in order to ensure that the collections have been
+ * loaded by the time you get the #SecretService proxy.
+ *
+ * This method may block indefinitely and should not be used in user interface
+ * threads.
+ *
+ * Returns: whether the load was successful or not
+ */
+gboolean
+secret_service_load_collections_sync (SecretService *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SecretCollection *collection;
+ GHashTable *collections;
+ GVariant *paths;
+ GVariantIter iter;
+ const gchar *path;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ paths = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (self), "Collections");
+ g_return_val_if_fail (paths != NULL, FALSE);
+
+ collections = collections_table_new ();
+
+ g_variant_iter_init (&iter, paths);
+ while (g_variant_iter_next (&iter, "&o", &path)) {
+ collection = service_lookup_collection (self, path);
+
+ /* No such collection yet create a new one */
+ if (collection == NULL) {
+ collection = secret_collection_new_for_dbus_path_sync (self, path,
+ SECRET_COLLECTION_LOAD_ITEMS,
+ cancellable, error);
+ if (collection == NULL) {
+ ret = FALSE;
+ break;
+ }
+ }
+
+ g_hash_table_insert (collections, g_strdup (path), collection);
+ }
+
+ if (ret)
+ service_update_collections (self, collections);
+
+ g_hash_table_unref (collections);
+ g_variant_unref (paths);
+ return ret;
+}
+
+/**
+ * secret_service_prompt_sync:
+ * @self: the secret service
+ * @prompt: the prompt
+ * @cancellable: optional cancellation object
+ * @return_type: the variant type of the prompt result
+ * @error: location to place an error on failure
+ *
+ * Perform prompting for a #SecretPrompt.
+ *
+ * Runs a prompt and performs the prompting. Returns a variant result if the
+ * prompt was completed and not dismissed. The type of result depends on the
+ * action the prompt is completing, and is defined in the Secret Service DBus
+ * API specification.
+ *
+ * This function is called by other parts of this library to handle prompts
+ * for the various actions that can require prompting.
+ *
+ * Override the #SecretServiceClass <literal>prompt_sync</literal> virtual method
+ * to change the behavior of the propmting. The default behavior is to simply
+ * run secret_prompt_perform_sync() on the prompt.
+ *
+ * Returns: (transfer full): %NULL if the prompt was dismissed or an error occurred,
+ * a variant result if the prompt was successful
+ */
+GVariant *
+secret_service_prompt_sync (SecretService *self,
+ SecretPrompt *prompt,
+ GCancellable *cancellable,
+ const GVariantType *return_type,
+ GError **error)
+{
+ SecretServiceClass *klass;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), NULL);
+ g_return_val_if_fail (SECRET_IS_PROMPT (prompt), NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ klass = SECRET_SERVICE_GET_CLASS (self);
+ g_return_val_if_fail (klass->prompt_sync != NULL, NULL);
+
+ return (klass->prompt_sync) (self, prompt, cancellable, return_type, error);
+}
+
+/**
+ * secret_service_prompt:
+ * @self: the secret service
+ * @prompt: the prompt
+ * @cancellable: optional cancellation object
+ * @callback: called when the operation completes
+ * @user_data: data to be passed to the callback
+ *
+ * Perform prompting for a #SecretPrompt.
+ *
+ * This function is called by other parts of this library to handle prompts
+ * for the various actions that can require prompting.
+ *
+ * Override the #SecretServiceClass <literal>prompt_async</literal> virtual method
+ * to change the behavior of the propmting. The default behavior is to simply
+ * run secret_prompt_perform() on the prompt.
+ */
+void
+secret_service_prompt (SecretService *self,
+ SecretPrompt *prompt,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SecretServiceClass *klass;
+
+ g_return_if_fail (SECRET_IS_SERVICE (self));
+ g_return_if_fail (SECRET_IS_PROMPT (prompt));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ klass = SECRET_SERVICE_GET_CLASS (self);
+ g_return_if_fail (klass->prompt_async != NULL);
+
+ (klass->prompt_async) (self, prompt, cancellable, callback, user_data);
+}
+
+/**
+ * secret_service_prompt_finish:
+ * @self: the secret service
+ * @result: the asynchronous result passed to the callback
+ * @return_type: the variant type of the prompt result
+ * @error: location to place an error on failure
+ *
+ * Complete asynchronous operation to perform prompting for a #SecretPrompt.
+ *
+ * Returns a variant result if the prompt was completed and not dismissed. The
+ * type of result depends on the action the prompt is completing, and is defined
+ * in the Secret Service DBus API specification.
+ *
+ * Returns: (transfer full): %NULL if the prompt was dismissed or an error occurred,
+ * a variant result if the prompt was successful
+ */
+GVariant *
+secret_service_prompt_finish (SecretService *self,
+ GAsyncResult *result,
+ const GVariantType *return_type,
+ GError **error)
+{
+ SecretServiceClass *klass;
+
+ g_return_val_if_fail (SECRET_IS_SERVICE (self), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ klass = SECRET_SERVICE_GET_CLASS (self);
+ g_return_val_if_fail (klass->prompt_finish != NULL, NULL);
+
+ return (klass->prompt_finish) (self, result, return_type, error);
+}