/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gck-object-cache.c - the GObject PKCS#11 wrapper library

   Copyright (C) 2011 Collabora Ltd.

   The Gnome Keyring Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Keyring 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   see <http://www.gnu.org/licenses/>.

   Author: Stef Walter <stefw@collabora.co.uk>
*/

#include "config.h"

#include "gck.h"
#include "gck-private.h"

#include <string.h>

/**
 * SECTION:gck-object-cache
 * @title: GckObjectCache
 * @short_description: An interface which holds attributes for a PKCS\#11 object
 *
 * #GckObjectCache is an interface implemented by derived classes of
 * #GckObject to indicate which attributes they'd like an enumerator to retrieve.
 * These attributes are then cached on the object and can be retrieved through
 * the #GckObjectCache:attributes property.
 */

/**
 * GckObjectCache:
 *
 * An interface implemented on an #GckObject which contains a cache of attributes.
 */

/**
 * GckObjectCacheIface:
 * @interface: parent interface
 * @default_types: (array length=n_default_types): attribute types that an
 *                   enumerator should retrieve
 * @n_default_types: number of attribute types to be retrieved
 * @fill: virtual method to add attributes to the cache
 *
 * Interface for #GckObjectCache. If the @default_types field is set by
 * a implementing class, then the a #GckEnumerator which has been setup using
 * gck_enumerator_set_object_type()
 *
 * The implementation for @populate should add the attributes to the
 * cache. It must be thread safe.
 */

typedef GckObjectCacheIface GckObjectCacheInterface;
G_DEFINE_INTERFACE (GckObjectCache, gck_object_cache, GCK_TYPE_OBJECT);

static void
gck_object_cache_default_init (GckObjectCacheIface *iface)
{
	static volatile gsize initialized = 0;
	if (g_once_init_enter (&initialized)) {

		/**
		 * GckObjectCache:attributes:
		 *
		 * The attributes cached on this object.
		 */
		g_object_interface_install_property (iface,
		         g_param_spec_boxed ("attributes", "Attributes", "PKCS#11 Attributes",
		                             GCK_TYPE_ATTRIBUTES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

		g_once_init_leave (&initialized, 1);
	}
}

/**
 * gck_object_cache_get_attributes: (skip):
 * @object: an object with an attribute cache
 *
 * Gets the attributes cached on this object.
 *
 * Returns: (transfer full) (allow-none): the attributes
 */
GckAttributes *
gck_object_cache_get_attributes (GckObjectCache *object)
{
	GckAttributes *attributes = NULL;
	g_return_val_if_fail (GCK_IS_OBJECT_CACHE (object), NULL);
	g_object_get (object, "attributes", &attributes, NULL);
	return attributes;
}

/**
 * gck_object_cache_set_attributes:
 * @object: an object with an attribute cache
 * @attrs: (allow-none): the attributes to set
 *
 * Sets the attributes cached on this object.
 *
 * If the @attrs #GckAttributes is floating, it is consumed.
 *
 */
void
gck_object_cache_set_attributes (GckObjectCache *object,
                                 GckAttributes *attrs)
{
	g_return_if_fail (GCK_IS_OBJECT_CACHE (object));

	gck_attributes_ref_sink (attrs);
	g_object_set (object, "attributes", attrs, NULL);
	gck_attributes_unref (attrs);
}

/**
 * gck_object_cache_fill:
 * @object: an object with the cache
 * @attrs: the attributes to cache
 *
 * Adds the attributes to the set cached on this object. If an attribute is
 * already present in the cache it will be overridden by this value.
 *
 * This will be done in a thread-safe manner.
 *
 * If the @attrs #GckAttributes is floating, it is consumed.
 */
void
gck_object_cache_fill (GckObjectCache *object,
                       GckAttributes *attrs)
{
	GckObjectCacheIface *iface;

	g_return_if_fail (GCK_IS_OBJECT_CACHE (object));
	g_return_if_fail (attrs != NULL);

	iface = GCK_OBJECT_CACHE_GET_INTERFACE (object);
	g_return_if_fail (iface->fill != NULL);

	gck_attributes_ref_sink (attrs);
	(iface->fill) (object, attrs);
	gck_attributes_unref (attrs);
}

/**
 * gck_object_cache_update:
 * @object: the object with the cache
 * @attr_types: (array length=n_attr_types): the types of attributes to update
 * @n_attr_types: the number of attribute types
 * @cancellable: optional cancellation object
 * @error: location to place an error
 *
 * Update the object cache with given attributes. If an attribute already
 * exists in the cache, it will be updated, and if it doesn't it will be added.
 *
 * This may block, use the asynchronous version when this is not desirable
 *
 * Returns: whether the cache update was successful
 */
gboolean
gck_object_cache_update (GckObjectCache *object,
                         const gulong *attr_types,
                         gint n_attr_types,
                         GCancellable *cancellable,
                         GError **error)
{
	GckObjectCacheIface *iface;
	GckAttributes *attrs;

	g_return_val_if_fail (GCK_IS_OBJECT_CACHE (object), FALSE);
	g_return_val_if_fail (attr_types != NULL || n_attr_types == 0, FALSE);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	iface = GCK_OBJECT_CACHE_GET_INTERFACE (object);

	if (attr_types == NULL) {
		attr_types = iface->default_types;
		n_attr_types = iface->n_default_types;

		if (attr_types == NULL || n_attr_types == 0) {
			g_warning ("no attribute types passed to gck_object_cache_update() "
			           "and no default types on object.");
			return FALSE;
		}
	}

	attrs = gck_object_get_full (GCK_OBJECT (object),
	                             attr_types, n_attr_types,
	                             cancellable, error);

	if (attrs != NULL) {
		gck_object_cache_fill (object, attrs);
		gck_attributes_unref (attrs);
	}

	return attrs != NULL;
}

static void
on_cache_object_get (GObject *source,
                     GAsyncResult *result,
                     gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	GckAttributes *attrs;
	GError *error = NULL;

	attrs = gck_object_get_finish (GCK_OBJECT (source), result, &error);
	if (error == NULL) {
		gck_object_cache_fill (GCK_OBJECT_CACHE (source), attrs);
		gck_attributes_unref (attrs);
	} else {
		g_simple_async_result_take_error (res, error);
	}

	g_simple_async_result_complete (res);
	g_object_unref (res);
}

/**
 * gck_object_cache_update_async:
 * @object: the object with the cache
 * @attr_types: (array length=n_attr_types): the types of attributes to update
 * @n_attr_types: the number of attribute types
 * @cancellable: optional cancellation object
 * @callback: called when the operation completes
 * @user_data: data to be passed to the callback
 *
 * Update the object cache with given attributes. If an attribute already
 * exists in the cache, it will be updated, and if it doesn't it will be added.
 *
 * This call will return immediately and complete asynchronously.
 */
void
gck_object_cache_update_async (GckObjectCache *object,
                               const gulong *attr_types,
                               gint n_attr_types,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	GckObjectCacheIface *iface;
	GSimpleAsyncResult *res;

	g_return_if_fail (GCK_IS_OBJECT_CACHE (object));
	g_return_if_fail (attr_types != NULL || n_attr_types == 0);
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	iface = GCK_OBJECT_CACHE_GET_INTERFACE (object);

	if (attr_types == NULL) {
		attr_types = iface->default_types;
		n_attr_types = iface->n_default_types;

		if (attr_types == NULL || n_attr_types == 0) {
			g_warning ("no attribute types passed to gck_object_cache_update_async() "
			           "and no default types on object.");
			return;
		}
	}

	res = g_simple_async_result_new (G_OBJECT (object), callback, user_data,
	                                 gck_object_cache_update_async);

	gck_object_get_async (GCK_OBJECT (object), attr_types, n_attr_types,
	                      cancellable, on_cache_object_get, g_object_ref (res));

	g_object_unref (res);
}

/**
 * gck_object_cache_update_finish:
 * @object: the object with the cache
 * @result: the asynchronous result passed to the callback
 * @error: location to place an error
 *
 * Complete an asynchronous operation to update the object cache with given
 * attributes.
 *
 * Returns: whether the cache update was successful
 */
gboolean
gck_object_cache_update_finish (GckObjectCache *object,
                                GAsyncResult *result,
                                GError **error)
{
	g_return_val_if_fail (GCK_IS_OBJECT_CACHE (object), FALSE);
	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (object),
	                      gck_object_cache_update_async), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
		return FALSE;

	return TRUE;
}

static gboolean
check_have_attributes (GckAttributes *attrs,
                       const gulong *attr_types,
                       gint n_attr_types)
{
	gint i;

	if (attrs == NULL)
		return FALSE;

	for (i = 0; i < n_attr_types; i++) {
		if (!gck_attributes_find (attrs, attr_types[i]))
			return FALSE;
	}

	return TRUE;
}

/**
 * gck_object_cache_lookup:
 * @object: the object
 * @attr_types: (array length=n_attr_types): the types of attributes to update
 * @n_attr_types: the number of attribute types
 * @cancellable: optional cancellation object
 * @error: location to place an error
 *
 * Lookup attributes in the cache, or retrieve them from the object if necessary.
 *
 * If @object is a #GckObjectCache then this will lookup the attributes there
 * first if available, otherwise will read them from the object and update
 * the cache.
 *
 * If @object is not a #GckObjectCache, then the attributes will simply be
 * read from the object.
 *
 * This may block, use the asynchronous version when this is not desirable
 *
 * Returns: (transfer full): the attributes retrieved or %NULL on failure
 */
GckAttributes *
gck_object_cache_lookup (GckObject *object,
                         const gulong *attr_types,
                         gint n_attr_types,
                         GCancellable *cancellable,
                         GError **error)
{
	GckAttributes *attrs;
	GckObjectCache *cache;

	g_return_val_if_fail (GCK_IS_OBJECT (object), NULL);
	g_return_val_if_fail (attr_types != NULL || n_attr_types == 0, NULL);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	if (GCK_IS_OBJECT_CACHE (object)) {
		cache = GCK_OBJECT_CACHE (object);
		attrs = gck_object_cache_get_attributes (cache);
		if (check_have_attributes (attrs, attr_types, n_attr_types))
			return attrs;
		gck_attributes_unref (attrs);

		if (!gck_object_cache_update (cache, attr_types, n_attr_types,
		                              cancellable, error))
			return NULL;

		return gck_object_cache_get_attributes (cache);

	} else {
		return gck_object_get_full (object, attr_types, n_attr_types,
		                            cancellable, error);
	}
}

/**
 * gck_object_cache_lookup_async:
 * @object: the object
 * @attr_types: (array length=n_attr_types): the types of attributes to update
 * @n_attr_types: the number of attribute types
 * @cancellable: optional cancellation object
 * @callback: called when the operation completes
 * @user_data: data to pass to the callback
 *
 * Lookup attributes in the cache, or retrieve them from the object if necessary.
 *
 * If @object is a #GckObjectCache then this will lookup the attributes there
 * first if available, otherwise will read them from the object and update
 * the cache.
 *
 * If @object is not a #GckObjectCache, then the attributes will simply be
 * read from the object.
 *
 * This will return immediately and complete asynchronously
 */
void
gck_object_cache_lookup_async (GckObject *object,
                               const gulong *attr_types,
                               gint n_attr_types,
                               GCancellable *cancellable,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
	GSimpleAsyncResult *res;
	GckAttributes *attrs;
	GckObjectCache *cache;
	gboolean have;

	g_return_if_fail (GCK_IS_OBJECT (object));
	g_return_if_fail (attr_types != NULL || n_attr_types == 0);
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	if (GCK_IS_OBJECT_CACHE (object)) {
		cache = GCK_OBJECT_CACHE (object);
		attrs = gck_object_cache_get_attributes (cache);
		have = check_have_attributes (attrs, attr_types, n_attr_types);
		gck_attributes_unref (attrs);

		if (have) {
			res = g_simple_async_result_new (G_OBJECT (cache), callback, user_data,
			                                 gck_object_cache_lookup_async);
			g_simple_async_result_complete_in_idle (res);
			g_object_unref (res);
		} else {
			gck_object_cache_update_async (cache, attr_types, n_attr_types,
			                               cancellable, callback, user_data);
		}
	} else {
		gck_object_get_async (object, attr_types, n_attr_types, cancellable,
		                      callback, user_data);
	}
}

/**
 * gck_object_cache_lookup_finish:
 * @object: the object
 * @result: the asynchrounous result passed to the callback
 * @error: location to place an error
 *
 * Complete an operation to lookup attributes in the cache or retrieve them
 * from the object if necessary.
 *
 * Returns: (transfer full): the attributes retrieved or %NULL on failure
 */
GckAttributes *
gck_object_cache_lookup_finish (GckObject *object,
                                GAsyncResult *result,
                                GError **error)
{
	GckObjectCache *cache;

	g_return_val_if_fail (GCK_IS_OBJECT (object), NULL);
	g_return_val_if_fail (error == NULL || *error == NULL, NULL);

	if (GCK_IS_OBJECT_CACHE (object)) {
		cache = GCK_OBJECT_CACHE (object);
		if (!g_simple_async_result_is_valid (result, G_OBJECT (object),
		                                     gck_object_cache_lookup_async))
			if (!gck_object_cache_update_finish (cache, result, error))
				return NULL;
		return gck_object_cache_get_attributes (cache);
	} else {

		return gck_object_get_finish (object, result, error);
	}
}
