Codebase list realmd / upstream/0.6 service / realm-all-provider.c
upstream/0.6

Tree @upstream/0.6 (Download .tar.gz)

realm-all-provider.c @upstream/0.6raw · history · blame

/* realmd -- Realm configuration service
 *
 * 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 of the licence or (at
 * your option) all later version.
 *
 * See the included COPYING file for more information.
 *
 * Author: Stef Walter <stefw@gnome.org>
 */

#include "config.h"

#include "realm-all-provider.h"
#include "realm-daemon.h"
#define DEBUG_FLAG REALM_DEBUG_PROVIDER
#include "realm-debug.h"
#include "realm-diagnostics.h"
#include "realm-errors.h"
#include "realm-dbus-constants.h"
#include "realm-dbus-generated.h"
#include "realm-provider.h"

#include <glib/gstdio.h>

#include <errno.h>
#include <string.h>

struct _RealmAllProvider {
	RealmProvider parent;
	GList *providers;
};

typedef struct {
	RealmProviderClass parent_class;
} RealmAllProviderClass;

G_DEFINE_TYPE (RealmAllProvider, realm_all_provider, REALM_TYPE_PROVIDER);

static void
realm_all_provider_init (RealmAllProvider *self)
{
	/* The dbus Name property of the provider */
	g_object_set (self, "name", "All", NULL);
}

static GVariant *
reduce_array (GQueue *input,
              const gchar *array_sig)
{
	GVariantBuilder builder;
	GVariant *element;
	GVariant *array;
	GVariantIter iter;

	g_variant_builder_init (&builder, G_VARIANT_TYPE (array_sig));

	for (;;) {
		array = g_queue_pop_head (input);
		if (!array)
			break;
		g_variant_iter_init (&iter, array);
		for (;;) {
			element = g_variant_iter_next_value (&iter);
			if (!element)
				break;
			g_variant_builder_add_value (&builder, element);
			g_variant_unref (element);
		}
		g_variant_unref (array);
	}

	return g_variant_builder_end (&builder);
}

static void
update_realms_property (RealmAllProvider *self)
{
	GQueue realms = G_QUEUE_INIT;
	GVariant *variant;
	GList *l;

	for (l = self->providers; l != NULL; l = g_list_next (l)) {
		variant = realm_dbus_provider_get_realms (l->data);
		if (variant)
			g_queue_push_tail (&realms, g_variant_ref (variant));
	}

	variant = g_variant_ref_sink (reduce_array (&realms, "a(os)"));
	g_object_set (self, "realms", variant, NULL);
	g_variant_unref (variant);
}

static void
update_all_properties (RealmAllProvider *self)
{
	update_realms_property (self);
}

static void
on_provider_notify (GObject *obj,
                    GParamSpec *spec,
                    gpointer user_data)
{
	RealmAllProvider *self = REALM_ALL_PROVIDER (user_data);
	update_all_properties (self);
}

typedef struct {
	GDBusMethodInvocation *invocation;
	gchar *operation_id;
	guint timeout_id;
	gboolean completed;
	gint outstanding;
	GQueue failures;
	GQueue results;
	gint relevance;
	GVariant *realms;
} DiscoverClosure;

static void
discover_closure_free (gpointer data)
{
	DiscoverClosure *discover = data;
	g_free (discover->operation_id);
	g_object_unref (discover->invocation);
	while (!g_queue_is_empty (&discover->results))
		g_variant_unref (g_queue_pop_head (&discover->results));
	while (!g_queue_is_empty (&discover->failures))
		g_error_free (g_queue_pop_head (&discover->failures));
	if (discover->realms)
		g_variant_unref (discover->realms);
	if (discover->timeout_id)
		g_source_remove (discover->timeout_id);
	g_slice_free (DiscoverClosure, discover);
}

static gint
compare_relevance (gconstpointer a,
                   gconstpointer b,
                   gpointer user_data)
{
	gint relevance_a = 0;
	gint relevance_b = 0;
	GVariant *realms;

	g_variant_get ((GVariant *)a, "(i@a(os))", &relevance_a, &realms);
	g_variant_unref (realms);

	g_variant_get ((GVariant *)b, "(i@a(os))", &relevance_b, &realms);
	g_variant_unref (realms);

	return relevance_b - relevance_a;
}

static void
discover_process_results (GSimpleAsyncResult *res,
                          DiscoverClosure *discover)
{
	gint relevance = 0;
	GError *error;
	GVariant *result;
	GVariant *realms;
	gboolean any = FALSE;
	GPtrArray *results;
	GVariantIter iter;
	GVariant *realm;

	g_queue_sort (&discover->results, compare_relevance, NULL);
	results = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);

	for (;;) {
		result = g_queue_pop_head (&discover->results);
		if (result == NULL)
			break;
		g_variant_get (result, "(i@a(os))", &relevance, &realms);
		g_variant_iter_init (&iter, realms);
		while ((realm = g_variant_iter_next_value (&iter)) != NULL) {
			const gchar *iface, *path;
			g_variant_get (realm, "(&o&s)", &iface, &path);
			g_ptr_array_add (results, realm);
		}
		if (relevance > discover->relevance)
			discover->relevance = relevance;
		g_variant_unref (realms);
		g_variant_unref (result);
		any = TRUE;
	}

	discover->realms = g_variant_new_array (G_VARIANT_TYPE ("(os)"),
	                                        (GVariant *const *)results->pdata,
	                                        results->len);
	g_variant_ref_sink (discover->realms);
	g_ptr_array_free (results, TRUE);

	if (!any) {
		/* If there was a failure, return one of them */
		error = g_queue_pop_head (&discover->failures);
		if (error != NULL)
			g_simple_async_result_take_error (res, error);
	}
}

static void
on_provider_discover (GObject *source,
                      GAsyncResult *result,
                      gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	DiscoverClosure *discover = g_simple_async_result_get_op_res_gpointer (res);
	RealmAllProvider *self = REALM_ALL_PROVIDER (g_async_result_get_source_object (user_data));
	GError *error = NULL;
	GVariant *retval;
	GVariant *realms;
	gint relevance;

	relevance = realm_provider_discover_finish (REALM_PROVIDER (source), result, &realms, &error);
	if (error == NULL) {
		retval = g_variant_new ("(i@a(os))", relevance, realms);
		g_queue_push_tail (&discover->results, g_variant_ref_sink (retval));
	} else {
		g_queue_push_tail (&discover->failures, error);
	}

	if (realms)
		g_variant_unref (realms);

	g_assert (discover->outstanding > 0);
	discover->outstanding--;

	/* All done at this point? */
	if (!discover->completed && discover->outstanding == 0) {
		discover_process_results (res, discover);
		discover->completed = TRUE;
		g_simple_async_result_complete (res);
	}

	g_object_unref (res);
	g_object_unref (self);
}

static gboolean
on_discover_timeout (gpointer user_data)
{
	GSimpleAsyncResult *async = G_SIMPLE_ASYNC_RESULT (user_data);
	DiscoverClosure *discover = g_simple_async_result_get_op_res_gpointer (async);

	if (discover->completed)
		return TRUE;

	/*
	 * So at this point if we have results, then consider the rest of
	 * the providers as taking too long, and ignore their results.
	 */

	if (!g_queue_is_empty (&discover->results)) {
		discover_process_results (async, discover);
		discover->completed = TRUE;
		g_simple_async_result_complete (async);
	}

	return TRUE;
}

static void
realm_all_provider_discover_async (RealmProvider *provider,
                                   const gchar *string,
                                   GDBusMethodInvocation *invocation,
                                   GAsyncReadyCallback callback,
                                   gpointer user_data)
{
	RealmAllProvider *self = REALM_ALL_PROVIDER (provider);
	GSimpleAsyncResult *res;
	DiscoverClosure *discover;
	GList *l;

	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
	                                 realm_all_provider_discover_async);
	discover = g_slice_new0 (DiscoverClosure);
	g_queue_init (&discover->results);
	discover->invocation = g_object_ref (invocation);
	discover->timeout_id = g_timeout_add_seconds (3, on_discover_timeout, res);
	g_simple_async_result_set_op_res_gpointer (res, discover, discover_closure_free);

	for (l = self->providers; l != NULL; l = g_list_next (l)) {
		realm_provider_discover (l->data, string, invocation, on_provider_discover,
		                         g_object_ref (res));
		discover->outstanding++;
	}

	/* If no discovery going on then just complete */
	if (discover->outstanding == 0) {
		discover_process_results (res, discover);
		discover->completed = TRUE;
		g_simple_async_result_complete_in_idle (res);
	}

	g_object_unref (res);
}

static gint
realm_all_provider_discover_finish (RealmProvider *provider,
                                    GAsyncResult *result,
                                    GVariant **realms,
                                    GError **error)
{
	GSimpleAsyncResult *res;
	DiscoverClosure *discover;

	res = G_SIMPLE_ASYNC_RESULT (result);

	if (g_simple_async_result_propagate_error (res, error))
		return -1;

	discover = g_simple_async_result_get_op_res_gpointer (res);
	*realms = discover->realms;
	discover->realms = NULL;
	return discover->relevance;
}

static void
realm_all_provider_finalize (GObject *obj)
{
	RealmAllProvider *self = REALM_ALL_PROVIDER (obj);
	GList *l;

	for (l = self->providers; l != NULL; l = g_list_next (l))
		g_signal_handlers_disconnect_by_func (l->data, on_provider_notify, self);
	g_list_free_full (self->providers, g_object_unref);

	G_OBJECT_CLASS (realm_all_provider_parent_class)->finalize (obj);
}

void
realm_all_provider_class_init (RealmAllProviderClass *klass)
{
	RealmProviderClass *provider_class = REALM_PROVIDER_CLASS (klass);
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->finalize = realm_all_provider_finalize;

	provider_class->dbus_path = REALM_DBUS_SERVICE_PATH;

	provider_class->discover_async = realm_all_provider_discover_async;
	provider_class->discover_finish = realm_all_provider_discover_finish;
}

void
realm_all_provider_register (RealmProvider *all_provider,
                             RealmProvider *provider)
{
	RealmAllProvider *self;

	g_return_if_fail (REALM_IS_ALL_PROVIDER (all_provider));
	g_return_if_fail (REALM_IS_PROVIDER (provider));

	self = REALM_ALL_PROVIDER (all_provider);
	self->providers = g_list_prepend (self->providers, g_object_ref (provider));

	update_all_properties (self);
	g_signal_connect (provider, "notify", G_CALLBACK (on_provider_notify), self);
}