/*
 * SPDX-License-Identifier: LGPL-2.0-or-later
 */

/**
 * SECTION: e-source-registry-server
 * @include: libebackend/libebackend.h
 * @short_description: Server-side repository for data sources
 *
 * The #ESourceRegistryServer is the heart of the registry D-Bus service.
 * Acting as a global singleton store for all #EServerSideSource instances,
 * its responsibilities include loading data source content from key files,
 * exporting data sources to clients over D-Bus, handling content change
 * requests from clients, and saving content changes back to key files.
 *
 * It also hosts any number of built-in or 3rd party data source collection
 * backends, which coordinate with #ESourceRegistryServer to automatically
 * advertise available data sources on a remote server.
 **/

#include "evolution-data-server-config.h"

#include <string.h>
#include <glib/gi18n-lib.h>

/* Private D-Bus classes. */
#include "e-dbus-source.h"
#include "e-dbus-source-manager.h"

#include "e-server-side-source.h"
#include "e-server-side-source-credentials-provider.h"

#include "e-source-registry-server.h"

/* Collection backends get tacked on to
 * sources with a [Collection] extension. */
#define BACKEND_DATA_KEY "__e_collection_backend__"

struct _ESourceRegistryServerPrivate {
	GMainContext *main_context;

	GDBusObjectManagerServer *object_manager;
	EDBusSourceManager *source_manager;

	GHashTable *sources;  /* sources added to hierarchy */
	GHashTable *orphans;  /* sources waiting for parent */
	GHashTable *monitors;

	GMutex sources_lock;
	GMutex orphans_lock;

	ESourceCredentialsProvider *credentials_provider;

	GMutex file_monitor_lock;
	GHashTable *file_monitor_events; /* gchar *uid ~> FileEventData * */
	GSource *file_monitor_source;

	EOAuth2Services *oauth2_services;
};

enum {
	LOAD_ERROR,
	FILES_LOADED,
	SOURCE_ADDED,
	SOURCE_REMOVED,
	TWEAK_KEY_FILE,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

static void e_source_registry_server_oauth2_support_init (EOAuth2SupportInterface *iface);

G_DEFINE_TYPE_WITH_CODE (ESourceRegistryServer, e_source_registry_server, E_TYPE_DATA_FACTORY,
	G_ADD_PRIVATE (ESourceRegistryServer)
	G_IMPLEMENT_INTERFACE (E_TYPE_OAUTH2_SUPPORT, e_source_registry_server_oauth2_support_init))

/* GDestroyNotify callback for 'sources' values */
static void
unref_data_source (ESource *source)
{
	/* The breaks the reference cycle with ECollectionBackend. */
	g_object_set_data (G_OBJECT (source), BACKEND_DATA_KEY, NULL);
	g_object_unref (source);
}

static void
source_registry_server_sources_insert (ESourceRegistryServer *server,
                                       ESource *source)
{
	const gchar *uid;

	uid = e_source_get_uid (source);
	g_return_if_fail (uid != NULL);

	g_mutex_lock (&server->priv->sources_lock);

	g_hash_table_insert (
		server->priv->sources,
		g_strdup (uid), g_object_ref (source));

	g_mutex_unlock (&server->priv->sources_lock);
}

static gboolean
source_registry_server_sources_remove (ESourceRegistryServer *server,
                                       ESource *source)
{
	const gchar *uid;
	gboolean removed;

	uid = e_source_get_uid (source);
	g_return_val_if_fail (uid != NULL, FALSE);

	g_mutex_lock (&server->priv->sources_lock);

	removed = g_hash_table_remove (server->priv->sources, uid);

	g_mutex_unlock (&server->priv->sources_lock);

	return removed;
}

static ESource *
source_registry_server_sources_lookup (ESourceRegistryServer *server,
                                       const gchar *uid)
{
	ESource *source;

	g_return_val_if_fail (uid != NULL, NULL);

	g_mutex_lock (&server->priv->sources_lock);

	source = g_hash_table_lookup (server->priv->sources, uid);

	if (source != NULL)
		g_object_ref (source);

	g_mutex_unlock (&server->priv->sources_lock);

	return source;
}

static GList *
source_registry_server_sources_get_values (ESourceRegistryServer *server)
{
	GList *values;

	g_mutex_lock (&server->priv->sources_lock);

	values = g_hash_table_get_values (server->priv->sources);

	g_list_foreach (values, (GFunc) g_object_ref, NULL);

	g_mutex_unlock (&server->priv->sources_lock);

	return values;
}

static void
source_registry_server_orphans_insert (ESourceRegistryServer *server,
                                       ESource *orphan_source)
{
	GHashTable *orphans;
	GPtrArray *array;
	gchar *parent_uid;

	g_mutex_lock (&server->priv->orphans_lock);

	orphans = server->priv->orphans;

	parent_uid = e_source_dup_parent (orphan_source);

	/* A top-level object has no parent UID, so we
	 * use a special "empty" key in the hash table. */
	if (parent_uid == NULL)
		parent_uid = g_strdup ("");

	array = g_hash_table_lookup (orphans, parent_uid);

	if (array == NULL) {
		array = g_ptr_array_new_with_free_func (g_object_unref);

		/* Takes ownership of the 'parent_uid' string. */
		g_hash_table_insert (orphans, parent_uid, array);
		parent_uid = NULL;
	}

	g_ptr_array_add (array, g_object_ref (orphan_source));

	g_free (parent_uid);

	g_mutex_unlock (&server->priv->orphans_lock);
}

static gboolean
source_registry_server_orphans_remove (ESourceRegistryServer *server,
                                       ESource *orphan_source)
{
	GHashTable *orphans;
	GPtrArray *array;
	gchar *parent_uid;
	gboolean removed = FALSE;

	g_mutex_lock (&server->priv->orphans_lock);

	orphans = server->priv->orphans;

	parent_uid = e_source_dup_parent (orphan_source);

	/* A top-level object has no parent UID, so we
	 * use a special "empty" key in the hash table. */
	if (parent_uid == NULL)
		parent_uid = g_strdup ("");

	array = g_hash_table_lookup (orphans, parent_uid);

	if (array != NULL) {
		/* Array is not ordered, so use "remove_fast". */
		removed = g_ptr_array_remove_fast (array, orphan_source);
	}

	g_free (parent_uid);

	g_mutex_unlock (&server->priv->orphans_lock);

	return removed;
}

static GPtrArray *
source_registry_server_orphans_steal (ESourceRegistryServer *server,
                                      ESource *parent_source)
{
	GHashTable *orphans;
	GPtrArray *array;
	const gchar *parent_uid;

	parent_uid = e_source_get_uid (parent_source);
	g_return_val_if_fail (parent_uid != NULL, NULL);

	g_mutex_lock (&server->priv->orphans_lock);

	orphans = server->priv->orphans;

	array = g_hash_table_lookup (orphans, parent_uid);

	/* g_hash_table_remove() will unreference the array,
	 * so we need to reference it first to keep it alive. */
	if (array != NULL) {
		g_ptr_array_ref (array);
		g_hash_table_remove (orphans, parent_uid);
	}

	g_mutex_unlock (&server->priv->orphans_lock);

	return array;
}

static gboolean
source_registry_server_create_source (ESourceRegistryServer *server,
                                      const gchar *uid,
                                      const gchar *data,
                                      GError **error)
{
	ESource *source = NULL;
	GFile *file;
	GFile *parent;
	GKeyFile *key_file;
	gboolean success;
	gsize length;
	GError *local_error = NULL;

	g_return_val_if_fail (uid != NULL, FALSE);
	g_return_val_if_fail (data != NULL, FALSE);

	length = strlen (data);

	/* Make sure the data is syntactically valid. */
	key_file = g_key_file_new ();
	success = g_key_file_load_from_data (
		key_file, data, length, G_KEY_FILE_NONE, error);
	g_key_file_free (key_file);

	if (!success)
		return FALSE;

	/* Check that the given unique identifier really is unique.
	 *
	 * XXX There's a valid case to be made that the server should be
	 *     assigning unique identifiers to new sources to avoid this
	 *     error.  That's fine for standalone sources but makes life
	 *     more difficult for clients creating a set or hierarchy of
	 *     sources that cross reference one another, such for a mail
	 *     account.  Having CLIENTS generate new UIDs means they can
	 *     prepare any cross references in advance, then submit each
	 *     source as is without having to make further modifications
	 *     as would be necessary if using server-assigned UIDs.
	 *
	 *     Anyway, if used properly the odds of a UID collision here
	 *     are slim enough that I think it's a reasonable trade-off.
	 */
	source = e_source_registry_server_ref_source (server, uid);
	if (source != NULL) {
		g_set_error (
			error, G_IO_ERROR, G_IO_ERROR_EXISTS,
			_("UID “%s” is already in use"), uid);
		g_object_unref (source);
		return FALSE;
	}

	file = e_server_side_source_new_user_file (uid);

	/* Create the directory where we'll be writing. */

	parent = g_file_get_parent (file);
	g_file_make_directory_with_parents (parent, NULL, &local_error);
	g_object_unref (parent);

	if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
		g_clear_error (&local_error);

	if (local_error != NULL) {
		g_propagate_error (error, local_error);
		success = FALSE;
	}

	/* Write the data to disk.  The file monitor should eventually
	 * notice the new file and call e_source_registry_server_load_file()
	 * per design, but we're going to beat it to the punch since we
	 * need to return the new D-Bus object path back to the caller.
	 * By the time the file monitor gets around to loading the file,
	 * it will simply get back the EDBusSourceObject we've already
	 * created and exported. */

	if (success)
		success = g_file_replace_contents (
			file, data, length, NULL, FALSE,
			G_FILE_CREATE_PRIVATE, NULL, NULL, error);

	if (success) {
		ESourcePermissionFlags flags;

		/* New sources are always writable + removable. */
		flags = E_SOURCE_PERMISSION_WRITABLE |
			E_SOURCE_PERMISSION_REMOVABLE;

		source = e_source_registry_server_load_file (
			server, file, flags, error);

		/* We don't need the returned reference. */
		if (source != NULL)
			g_object_unref (source);
		else
			success = FALSE;
	}

	g_object_unref (file);

	return success;
}

static gboolean
source_registry_server_create_sources_cb (EDBusSourceManager *dbus_interface,
                                          GDBusMethodInvocation *invocation,
                                          GVariant *array,
                                          ESourceRegistryServer *server)
{
	GVariantIter iter;
	gchar *uid, *data;
	GError *error = NULL;

	g_variant_iter_init (&iter, array);

	while (g_variant_iter_next (&iter, "{ss}", &uid, &data)) {
		source_registry_server_create_source (
			server, uid, data, &error);

		g_free (uid);
		g_free (data);

		if (error != NULL)
			break;
	}

	if (error != NULL)
		g_dbus_method_invocation_take_error (invocation, error);
	else
		e_dbus_source_manager_complete_create_sources (
			dbus_interface, invocation);

	return TRUE;
}

static gboolean
source_registry_server_reload_cb (EDBusSourceManager *dbus_interface,
                                  GDBusMethodInvocation *invocation,
                                  ESourceRegistryServer *server)
{
	e_dbus_server_quit (
		E_DBUS_SERVER (server),
		E_DBUS_SERVER_EXIT_RELOAD);

	e_dbus_source_manager_complete_reload (dbus_interface, invocation);

	return TRUE;
}

static gboolean
source_registry_server_refresh_backend_cb (EDBusSourceManager *dbus_interface,
					   GDBusMethodInvocation *invocation,
					   const gchar *source_uid,
					   ESourceRegistryServer *server)
{
	ESource *source;
	GError *error = NULL;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), FALSE);
	g_return_val_if_fail (source_uid != NULL, FALSE);

	source = e_source_registry_server_ref_source (server, source_uid);
	if (source) {
		if (e_source_has_extension (source, E_SOURCE_EXTENSION_COLLECTION)) {
			ECollectionBackend *backend;

			backend = e_source_registry_server_ref_backend (server, source);
			if (backend) {
				e_collection_backend_schedule_populate (backend);
				g_object_unref (backend);
			} else {
				error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
					_("Cannot find corresponding collection backend for source “%s”"), source_uid);
			}
		} else {
			error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
				_("Source “%s” is not a collection source"), source_uid);
		}

		g_object_unref (source);
	} else {
		error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
			_("Cannot find source “%s”"), source_uid);
	}

	if (error)
		g_dbus_method_invocation_take_error (invocation, error);
	else
		e_dbus_source_manager_complete_refresh_backend (dbus_interface, invocation);

	return TRUE;
}

typedef struct _FileEventData {
	GFile *file;
	GFileMonitorEvent event_type;
} FileEventData;

static FileEventData *
file_event_data_new (GFile *file,
		     GFileMonitorEvent event_type)
{
	FileEventData *fed;

	fed = g_slice_new0 (FileEventData);
	fed->file = g_object_ref (file);
	fed->event_type = event_type;

	return fed;
}

static void
file_event_data_free (gpointer ptr)
{
	FileEventData *fed = ptr;

	if (fed) {
		g_clear_object (&fed->file);
		g_slice_free (FileEventData, fed);
	}
}

static void
source_registry_server_process_file_monitor_event (gpointer key,
						   gpointer value,
						   gpointer user_data)
{
	const gchar *uid = key;
	const FileEventData *fed = value;
	ESourceRegistryServer *server = user_data;
	GFileMonitorEvent event_type;

	g_return_if_fail (uid != NULL);
	g_return_if_fail (fed != NULL);

	event_type = fed->event_type;

	if (e_source_registry_debug_enabled ()) {
		e_source_registry_debug_print ("Processing file monitor event %s (%u) for UID: %s\n",
			event_type == G_FILE_MONITOR_EVENT_CHANGED ? "CHANGED" :
			event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT ? "CHANGES_DONE_HINT" :
			event_type == G_FILE_MONITOR_EVENT_DELETED ? "DELETED" :
			event_type == G_FILE_MONITOR_EVENT_CREATED ? "CREATED" :
			event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED ? "ATTRIBUTE_CHANGED" :
			event_type == G_FILE_MONITOR_EVENT_PRE_UNMOUNT ? "PRE_UNMOUNT" :
			event_type == G_FILE_MONITOR_EVENT_UNMOUNTED ? "UNMOUNTED" :
			event_type == G_FILE_MONITOR_EVENT_MOVED ? "MOVED" : "???",
			event_type,
			uid);
	}

	if (event_type == G_FILE_MONITOR_EVENT_CHANGED ||
	    event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
		ESource *source;
		GError *error = NULL;

		source = e_source_registry_server_ref_source (server, uid);

		/* If the source does not exist, create it; parsing may have
		 * failed when the file was originally created. This can happen
		 * if the file is created (empty), then e-source-registry-server
		 * detects it, then it’s populated and made valid.
		 *
		 * Otherwise, reload the file since it has changed. */
		if (source == NULL) {
			event_type = G_FILE_MONITOR_EVENT_CREATED;
		} else if (!e_server_side_source_load (E_SERVER_SIDE_SOURCE (source), NULL, &error)) {
			g_warning ("Error reloading source ‘%s’: %s", uid, error->message);

			g_error_free (error);
			g_object_unref (source);

			return;
		}

		g_clear_object (&source);
	}

	if (event_type == G_FILE_MONITOR_EVENT_CREATED) {
		ESource *source;
		GError *error = NULL;

		source = e_source_registry_server_ref_source (server, uid);

		if (!source) {
			/* it can return NULL source for hidden files */
			source = e_server_side_source_new (server, fed->file, &error);
		}

		if (!error && source) {
			/* File monitors are only placed on directories
			 * where data sources are writable and removable,
			 * so it should be safe to assume these flags. */
			e_server_side_source_set_writable (E_SERVER_SIDE_SOURCE (source), TRUE);
			e_server_side_source_set_removable (E_SERVER_SIDE_SOURCE (source), TRUE);

			e_source_registry_server_add_source (server, source);
		} else if (error) {
			e_source_registry_server_load_error (server, fed->file, error);
			g_error_free (error);
		}

		g_clear_object (&source);
	}

	if (event_type == G_FILE_MONITOR_EVENT_DELETED) {
		ESource *source;

		source = e_source_registry_server_ref_source (server, uid);

		if (source == NULL)
			return;

		/* If the key file for a non-removable source was
		 * somehow deleted, disregard the event and leave
		 * the source object in memory. */
		if (e_source_get_removable (source))
			e_source_registry_server_remove_source (server, source);

		g_object_unref (source);
	}
}

static gboolean
source_registry_server_process_file_monitor_events_cb (gpointer user_data)
{
	ESourceRegistryServer *server = user_data;
	GHashTable *events;

	if (g_source_is_destroyed (g_main_current_source ()))
		return FALSE;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), FALSE);

	g_mutex_lock (&server->priv->file_monitor_lock);
	events = server->priv->file_monitor_events;
	server->priv->file_monitor_events = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, file_event_data_free);
	g_mutex_unlock (&server->priv->file_monitor_lock);

	g_hash_table_foreach (events, source_registry_server_process_file_monitor_event, server);
	g_hash_table_destroy (events);

	return FALSE;
}

static void
source_registry_server_monitor_changed_cb (GFileMonitor *monitor,
                                           GFile *file,
                                           GFile *other_file,
                                           GFileMonitorEvent event_type,
                                           ESourceRegistryServer *server)
{
	if (e_source_registry_debug_enabled ()) {
		gchar *uri;

		uri = g_file_get_uri (file);
		e_source_registry_debug_print ("Handling file monitor event %s (%u) for URI: %s\n",
			event_type == G_FILE_MONITOR_EVENT_CHANGED ? "CHANGED" :
			event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT ? "CHANGES_DONE_HINT" :
			event_type == G_FILE_MONITOR_EVENT_DELETED ? "DELETED" :
			event_type == G_FILE_MONITOR_EVENT_CREATED ? "CREATED" :
			event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED ? "ATTRIBUTE_CHANGED" :
			event_type == G_FILE_MONITOR_EVENT_PRE_UNMOUNT ? "PRE_UNMOUNT" :
			event_type == G_FILE_MONITOR_EVENT_UNMOUNTED ? "UNMOUNTED" :
			event_type == G_FILE_MONITOR_EVENT_MOVED ? "MOVED" : "???",
			event_type,
			uri);
		g_free (uri);
	}

	if (event_type == G_FILE_MONITOR_EVENT_CHANGED ||
	    event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT ||
	    event_type == G_FILE_MONITOR_EVENT_CREATED ||
	    event_type == G_FILE_MONITOR_EVENT_DELETED) {
		gchar *uid;

		uid = e_server_side_source_uid_from_file (file, NULL);

		if (uid == NULL)
			return;

		g_mutex_lock (&server->priv->file_monitor_lock);
		/* This overwrites any previous events, aka the last wins
		   (overwrite can be DELETE + CREATE, which handles it correctly). */
		g_hash_table_insert (server->priv->file_monitor_events, uid, file_event_data_new (file, event_type));

		if (server->priv->file_monitor_source) {
			g_source_destroy (server->priv->file_monitor_source);
			g_source_unref (server->priv->file_monitor_source);
		}

		server->priv->file_monitor_source = g_timeout_source_new_seconds (3);
		g_source_set_callback (
			server->priv->file_monitor_source,
			source_registry_server_process_file_monitor_events_cb,
			server, NULL);
		g_source_attach (
			server->priv->file_monitor_source,
			server->priv->main_context);

		g_mutex_unlock (&server->priv->file_monitor_lock);
	}
}

static gboolean
source_registry_server_traverse_cb (GNode *node,
                                    GQueue *queue)
{
	g_queue_push_tail (queue, g_object_ref (node->data));

	return FALSE;
}

static void
source_registry_server_queue_subtree (ESource *source,
                                      GQueue *queue)
{
	GNode *node;

	node = e_server_side_source_get_node (E_SERVER_SIDE_SOURCE (source));

	g_node_traverse (
		node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
		(GNodeTraverseFunc) source_registry_server_traverse_cb, queue);
}

static gboolean
source_registry_server_find_parent (ESourceRegistryServer *server,
                                    ESource *source)
{
	ESource *parent;
	const gchar *parent_uid;

	/* If the given source references a parent source and the
	 * parent source is not present in the hierarchy, the given
	 * source is added to an orphan table until the referenced
	 * parent is added to the hierarchy. */

	parent_uid = e_source_get_parent (source);

	if (parent_uid == NULL || *parent_uid == '\0')
		return TRUE;

	parent = g_hash_table_lookup (server->priv->sources, parent_uid);

	if (parent != NULL) {
		GNode *parent_node;
		GNode *object_node;

		parent_node = e_server_side_source_get_node (
			E_SERVER_SIDE_SOURCE (parent));
		object_node = e_server_side_source_get_node (
			E_SERVER_SIDE_SOURCE (source));
		g_node_append (parent_node, object_node);

		return TRUE;
	}

	source_registry_server_orphans_insert (server, source);

	return FALSE;
}

static void
source_registry_server_adopt_orphans (ESourceRegistryServer *server,
                                      ESource *source)
{
	GPtrArray *array;

	/* Check if a newly-added source has any orphan sources
	 * that are waiting for it.  The orphans can now be added
	 * to the hierarchy as children of the newly-added source. */

	array = source_registry_server_orphans_steal (server, source);

	if (array != NULL) {
		guint ii;

		for (ii = 0; ii < array->len; ii++) {
			ESource *orphan = array->pdata[ii];
			e_source_registry_server_add_source (server, orphan);
		}

		g_ptr_array_unref (array);
	}
}

static GObject *server_singleton = NULL;
G_LOCK_DEFINE_STATIC (server_singleton);

static void
server_singleton_weak_ref_cb (gpointer user_data,
			      GObject *object)
{
	G_LOCK (server_singleton);

	g_warn_if_fail (object == server_singleton);
	server_singleton = NULL;

	G_UNLOCK (server_singleton);
}

static GObject *
source_registry_server_constructor (GType type,
				    guint n_construct_params,
				    GObjectConstructParam *construct_params)
{
	GObject *object;

	G_LOCK (server_singleton);

	if (server_singleton) {
		object = g_object_ref (server_singleton);
	} else {
		object = G_OBJECT_CLASS (e_source_registry_server_parent_class)->constructor (type, n_construct_params, construct_params);

		if (object)
			g_object_weak_ref (object, server_singleton_weak_ref_cb, NULL);

		server_singleton = object;
	}

	G_UNLOCK (server_singleton);

	return object;
}

static void
source_registry_server_constructed (GObject *object)
{
	ESourceRegistryServer *server;

	server = E_SOURCE_REGISTRY_SERVER (object);

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (e_source_registry_server_parent_class)->constructed (object);

	server->priv->credentials_provider = e_server_side_source_credentials_provider_new (server);
	server->priv->oauth2_services = e_oauth2_services_new ();
}

static void
source_registry_server_dispose (GObject *object)
{
	ESourceRegistryServerPrivate *priv;

	priv = E_SOURCE_REGISTRY_SERVER (object)->priv;

	g_mutex_lock (&priv->file_monitor_lock);
	if (priv->file_monitor_source) {
		g_source_destroy (priv->file_monitor_source);
		g_source_unref (priv->file_monitor_source);
		priv->file_monitor_source = NULL;
	}
	g_mutex_unlock (&priv->file_monitor_lock);

	g_clear_pointer (&priv->main_context, g_main_context_unref);

	g_clear_object (&priv->object_manager);
	g_clear_object (&priv->source_manager);
	g_clear_object (&priv->credentials_provider);

	g_hash_table_remove_all (priv->sources);
	g_hash_table_remove_all (priv->orphans);
	g_hash_table_remove_all (priv->monitors);
	g_hash_table_remove_all (priv->file_monitor_events);

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (e_source_registry_server_parent_class)->
		dispose (object);
}

static void
source_registry_server_finalize (GObject *object)
{
	ESourceRegistryServerPrivate *priv;

	priv = E_SOURCE_REGISTRY_SERVER (object)->priv;

	g_hash_table_destroy (priv->sources);
	g_hash_table_destroy (priv->orphans);
	g_hash_table_destroy (priv->monitors);
	g_hash_table_destroy (priv->file_monitor_events);

	g_mutex_clear (&priv->sources_lock);
	g_mutex_clear (&priv->orphans_lock);
	g_mutex_clear (&priv->file_monitor_lock);

	g_clear_object (&priv->oauth2_services);

	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (e_source_registry_server_parent_class)->
		finalize (object);
}

static void
source_registry_server_bus_acquired (EDBusServer *server,
                                     GDBusConnection *connection)
{
	ESourceRegistryServerPrivate *priv;

	priv = E_SOURCE_REGISTRY_SERVER (server)->priv;

	g_dbus_object_manager_server_set_connection (
		priv->object_manager, connection);

	/* Chain up to parent's bus_acquired() method. */
	E_DBUS_SERVER_CLASS (e_source_registry_server_parent_class)->
		bus_acquired (server, connection);
}

static void
source_registry_server_quit_server (EDBusServer *server,
                                    EDBusServerExitCode code)
{
	ESourceRegistryServerPrivate *priv;

	priv = E_SOURCE_REGISTRY_SERVER (server)->priv;

	/* This makes the object manager unexport all objects. */
	g_dbus_object_manager_server_set_connection (
		priv->object_manager, NULL);

	/* Chain up to parent's quit_server() method. */
	E_DBUS_SERVER_CLASS (e_source_registry_server_parent_class)->
		quit_server (server, code);
}

static void
source_registry_server_source_added (ESourceRegistryServer *server,
                                     ESource *source)
{
	GDBusObject *dbus_object;
	GDBusObject *g_dbus_object;
	const gchar *uid;
	const gchar *object_name;
	const gchar *object_path;
	const gchar *extension_name;

	/* Instantiate an ECollectionBackend if appropriate.
	 *
	 * Do this BEFORE exporting so backends have a chance
	 * to make any last-minute tweaks to the data source. */

	extension_name = E_SOURCE_EXTENSION_COLLECTION;
	if (e_source_has_extension (source, extension_name)) {
		EBackend *backend = NULL;
		ECollectionBackendFactory *backend_factory;
		ESourceBackend *extension;
		const gchar *backend_name;
		GError *error = NULL;

		extension = e_source_get_extension (source, extension_name);
		backend_name = e_source_backend_get_backend_name (extension);

		/* For convenience, we attach the EBackend to the ESource
		 * itself, which creates a reference cycle.  The cycle is
		 * explicitly broken when the ESource is removed from the
		 * 'sources' hash table (see unref_data_source() above). */
		backend_factory = e_source_registry_server_ref_backend_factory (server, source);
		backend = e_backend_factory_new_backend (E_BACKEND_FACTORY (backend_factory), source);

		if (G_IS_INITABLE (backend)) {
			GInitable *initable = G_INITABLE (backend);

			if (!g_initable_init (initable, NULL, &error))
				g_clear_object (&backend);
		}

		g_object_unref (backend_factory);

		if (backend != NULL) {
			g_object_set_data_full (
				G_OBJECT (source),
				BACKEND_DATA_KEY, backend,
				(GDestroyNotify) g_object_unref);
		} else {
			g_warning (
				"No collection backend '%s' for %s: %s",
				backend_name, e_source_get_uid (source),
				error ? error->message : "Unknown error");

			g_clear_error (&error);
		}
	}

	/* Export the data source to clients over D-Bus. */

	dbus_object = e_source_ref_dbus_object (source);

	g_dbus_object_manager_server_export_uniquely (
		server->priv->object_manager,
		G_DBUS_OBJECT_SKELETON (dbus_object));

	g_object_notify (G_OBJECT (source), "exported");

	uid = e_source_get_uid (source);

	g_dbus_object = G_DBUS_OBJECT (dbus_object);
	object_path = g_dbus_object_get_object_path (g_dbus_object);
	object_name = strrchr (object_path, '/') + 1;

	g_debug ("Adding %s ('%s')", uid, object_name);

	g_object_unref (dbus_object);
}

static void
source_registry_server_source_removed (ESourceRegistryServer *server,
                                       ESource *source)
{
	GDBusObject *dbus_object;
	const gchar *uid;
	const gchar *object_name;
	const gchar *object_path;

	uid = e_source_get_uid (source);

	dbus_object = e_source_ref_dbus_object (source);

	object_path = g_dbus_object_get_object_path (dbus_object);
	object_name = strrchr (object_path, '/') + 1;

	e_source_registry_debug_print ("Removing %s ('%s')\n", uid, object_name);

	g_dbus_object_manager_server_unexport (
		server->priv->object_manager, object_path);

	g_object_notify (G_OBJECT (source), "exported");

	g_object_unref (dbus_object);
}

static gboolean
source_registry_server_any_true (GSignalInvocationHint *ihint,
                                 GValue *return_accu,
                                 const GValue *handler_return,
                                 gpointer unused)
{
	if (g_value_get_boolean (handler_return))
		g_value_set_boolean (return_accu, TRUE);

	return TRUE;
}

static gboolean
e_source_registry_server_get_access_token_sync (EOAuth2Support *support,
						ESource *source,
						GCancellable *cancellable,
						gchar **out_access_token,
						gint *out_expires_in,
						GError **error)
{
	EOAuth2ServiceRefSourceFunc ref_source;
	ESourceRegistryServer *server;
	EOAuth2Service *service;
	gboolean success;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (support), FALSE);
	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);

	server = E_SOURCE_REGISTRY_SERVER (support);
	service = e_oauth2_services_find (server->priv->oauth2_services, source);

	if (!service) {
		g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
			_("Data source “%s” does not support OAuth 2.0 authentication"),
			e_source_get_display_name (source));
		return FALSE;
	}

	ref_source = (EOAuth2ServiceRefSourceFunc) e_source_registry_server_ref_source;

	success = e_oauth2_service_get_access_token_sync (service, source, ref_source, server,
		out_access_token, out_expires_in, cancellable, error);

	g_clear_object (&service);

	return success;
}

static GDBusInterfaceSkeleton *
source_registry_server_get_dbus_interface_skeleton (EDBusServer *server)
{
	ESourceRegistryServerPrivate *priv;

	priv = E_SOURCE_REGISTRY_SERVER (server)->priv;

	return G_DBUS_INTERFACE_SKELETON (priv->source_manager);
}

static void
e_source_registry_server_class_init (ESourceRegistryServerClass *class)
{
	GObjectClass *object_class;
	EDBusServerClass *dbus_server_class;
	EDataFactoryClass *data_factory_class;
	GType backend_factory_type;
	const gchar *modules_directory = MODULE_DIRECTORY;
	const gchar *modules_directory_env;

	modules_directory_env = g_getenv (EDS_REGISTRY_MODULES);
	if (modules_directory_env &&
	    g_file_test (modules_directory_env, G_FILE_TEST_IS_DIR))
		modules_directory = g_strdup (modules_directory_env);

	object_class = G_OBJECT_CLASS (class);
	object_class->constructor = source_registry_server_constructor;
	object_class->constructed = source_registry_server_constructed;
	object_class->dispose = source_registry_server_dispose;
	object_class->finalize = source_registry_server_finalize;

	dbus_server_class = E_DBUS_SERVER_CLASS (class);
	dbus_server_class->bus_name = SOURCES_DBUS_SERVICE_NAME;
	dbus_server_class->module_directory = modules_directory;
	dbus_server_class->bus_acquired = source_registry_server_bus_acquired;
	dbus_server_class->quit_server = source_registry_server_quit_server;
	data_factory_class = E_DATA_FACTORY_CLASS (class);
	backend_factory_type = E_TYPE_COLLECTION_BACKEND_FACTORY;
	data_factory_class->backend_factory_type = backend_factory_type;
	data_factory_class->factory_object_path = E_SOURCE_REGISTRY_SERVER_OBJECT_PATH;
	data_factory_class->data_object_path_prefix = E_SOURCE_REGISTRY_SERVER_OBJECT_PATH;
	data_factory_class->get_dbus_interface_skeleton = source_registry_server_get_dbus_interface_skeleton;

	class->source_added = source_registry_server_source_added;
	class->source_removed = source_registry_server_source_removed;

	/**
	 * ESourceRegistryServer::load-error:
	 * @server: the #ESourceRegistryServer which emitted the signal
	 * @file: the #GFile being loaded
	 * @error: a #GError describing the error
	 *
	 * Emitted when an error occurs while loading or parsing a
	 * data source key file.
	 **/
	signals[LOAD_ERROR] = g_signal_new (
		"load-error",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (ESourceRegistryServerClass, load_error),
		NULL, NULL, NULL,
		G_TYPE_NONE, 2,
		G_TYPE_FILE,
		G_TYPE_ERROR | G_SIGNAL_TYPE_STATIC_SCOPE);

	/**
	 * ESourceRegistryServer::files-loaded:
	 * @server: the #ESourceRegistryServer which emitted the signal
	 *
	 * Emitted after all data source key files are loaded on startup.
	 * Extensions can connect to this signal to perform any additional
	 * work prior to running the main loop.
	 **/
	signals[FILES_LOADED] = g_signal_new (
		"files-loaded",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (ESourceRegistryServerClass, files_loaded),
		NULL, NULL, NULL,
		G_TYPE_NONE, 0);

	/**
	 * ESourceRegistryServer::source-added:
	 * @server: the #ESourceRegistryServer which emitted the signal
	 * @source: the newly-added #EServerSideSource
	 *
	 * Emitted when an #EServerSideSource is added to @server.
	 **/
	signals[SOURCE_ADDED] = g_signal_new (
		"source-added",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (ESourceRegistryServerClass, source_added),
		NULL, NULL, NULL,
		G_TYPE_NONE, 1,
		E_TYPE_SERVER_SIDE_SOURCE);

	/**
	 * ESourceRegistryServer::source-removed:
	 * @server: the #ESourceRegistryServer which emitted the signal
	 * @source: the #EServerSideSource that got removed
	 *
	 * Emitted when an #EServerSideSource is removed from @server.
	 **/
	signals[SOURCE_REMOVED] = g_signal_new (
		"source-removed",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (ESourceRegistryServerClass, source_removed),
		NULL, NULL, NULL,
		G_TYPE_NONE, 1,
		E_TYPE_SERVER_SIDE_SOURCE);

	/**
	 * ESourceRegistryServer::tweak-key-file:
	 * @server: the #ESourceRegistryServer which emitted the signal
	 * @key_file: a #GKeyFile
	 * @uid: a unique identifier string for @key_file
	 *
	 * Emitted from e_source_registry_server_load_file() just prior
	 * to instantiating an #EServerSideSource.  Signal handlers can
	 * tweak the @key_file content as necessary and return %TRUE to
	 * write the modified content back to disk.
	 *
	 * For the purposes of tweaking, it's easier to deal with a plain
	 * #GKeyFile than an #ESource instance.  An #ESource, for example,
	 * does not allow key file groups to be removed.
	 *
	 * The return value is cumulative.  If any signal handler returns
	 * %TRUE, the @key_file content is written back to disk.
	 *
	 * Returns: %TRUE if @key_file was modified, %FALSE otherwise
	 *
	 * Since: 3.8
	 **/
	signals[TWEAK_KEY_FILE] = g_signal_new (
		"tweak-key-file",
		G_OBJECT_CLASS_TYPE (object_class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET (ESourceRegistryServerClass, tweak_key_file),
		source_registry_server_any_true, NULL,
		NULL,
		G_TYPE_BOOLEAN, 2,
		G_TYPE_KEY_FILE,
		G_TYPE_STRING);
}

static void
e_source_registry_server_oauth2_support_init (EOAuth2SupportInterface *iface)
{
	iface->get_access_token_sync = e_source_registry_server_get_access_token_sync;
}

static void
e_source_registry_server_init (ESourceRegistryServer *server)
{
	GDBusObjectManagerServer *object_manager;
	EDBusSourceManager *source_manager;
	GHashTable *sources;
	GHashTable *orphans;
	GHashTable *monitors;
	const gchar *object_path;

	object_path = E_SOURCE_REGISTRY_SERVER_OBJECT_PATH;
	object_manager = g_dbus_object_manager_server_new (object_path);
	source_manager = e_dbus_source_manager_skeleton_new ();

	/* UID string -> ESource */
	sources = g_hash_table_new_full (
		(GHashFunc) g_str_hash,
		(GEqualFunc) g_str_equal,
		(GDestroyNotify) g_free,
		(GDestroyNotify) unref_data_source);

	/* Parent UID string -> GPtrArray of ESources */
	orphans = g_hash_table_new_full (
		(GHashFunc) g_str_hash,
		(GEqualFunc) g_str_equal,
		(GDestroyNotify) g_free,
		(GDestroyNotify) g_ptr_array_unref);

	/* GFile -> GFileMonitor */
	monitors = g_hash_table_new_full (
		(GHashFunc) g_file_hash,
		(GEqualFunc) g_file_equal,
		(GDestroyNotify) g_object_unref,
		(GDestroyNotify) g_object_unref);

	server->priv = e_source_registry_server_get_instance_private (server);
	server->priv->main_context = g_main_context_ref_thread_default ();
	server->priv->object_manager = object_manager;
	server->priv->source_manager = source_manager;
	server->priv->sources = sources;
	server->priv->orphans = orphans;
	server->priv->monitors = monitors;
	g_mutex_init (&server->priv->sources_lock);
	g_mutex_init (&server->priv->orphans_lock);
	g_mutex_init (&server->priv->file_monitor_lock);

	server->priv->file_monitor_source = NULL;
	server->priv->file_monitor_events = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, file_event_data_free);

	g_signal_connect (
		source_manager, "handle-create-sources",
		G_CALLBACK (source_registry_server_create_sources_cb),
		server);

	g_signal_connect (
		source_manager, "handle-reload",
		G_CALLBACK (source_registry_server_reload_cb),
		server);

	g_signal_connect (
		source_manager, "handle-refresh-backend",
		G_CALLBACK (source_registry_server_refresh_backend_cb),
		server);
}

/**
 * e_source_registry_server_new:
 *
 * Creates a new instance of #ESourceRegistryServer.
 *
 * Returns: a new instance of #ESourceRegistryServer
 *
 * Since: 3.6
 **/
EDBusServer *
e_source_registry_server_new (void)
{
	return g_object_new (E_TYPE_SOURCE_REGISTRY_SERVER, "reload-supported", TRUE, NULL);
}

/**
 * e_source_registry_server_ref_credentials_provider:
 * @server: an #ESourceRegistryServer
 *
 * Returns a referenced #ESourceCredentialsProvider. Unref it with
 * g_object_unref(), when no longer needed.
 *
 * Returns: (transfer full): A referenced #ESourceCredentialsProvider.
 *
 * Since: 3.16
 **/
ESourceCredentialsProvider *
e_source_registry_server_ref_credentials_provider (ESourceRegistryServer *server)
{
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);

	return g_object_ref (server->priv->credentials_provider);
}

/**
 * e_source_registry_server_get_oauth2_services:
 * @server: an #ESourceRegistryServer
 *
 * Returns: (transfer none): an #EOAuth2Services instance owned by @server
 *
 * Since: 3.28
 **/
EOAuth2Services *
e_source_registry_server_get_oauth2_services (ESourceRegistryServer *server)
{
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);

	return server->priv->oauth2_services;
}

/**
 * e_source_registry_server_add_source:
 * @server: an #ESourceRegistryServer
 * @source: an #ESource
 *
 * Adds @source to @server.
 *
 * Since: 3.6
 **/
void
e_source_registry_server_add_source (ESourceRegistryServer *server,
                                     ESource *source)
{
	GDBusObject *dbus_object;
	EDBusSource *dbus_source;
	const gchar *extension_name;
	const gchar *uid;
	gchar *data;

	g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
	g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));

	uid = e_source_get_uid (source);
	g_return_if_fail (uid != NULL);

	g_mutex_lock (&server->priv->sources_lock);

	/* Check if we already have this object in the hierarchy. */
	if (g_hash_table_lookup (server->priv->sources, uid) != NULL) {
		g_mutex_unlock (&server->priv->sources_lock);
		return;
	}

	/* Make sure the parent object (if any) is in the hierarchy. */
	if (!source_registry_server_find_parent (server, source)) {
		g_mutex_unlock (&server->priv->sources_lock);
		return;
	}

	g_mutex_unlock (&server->priv->sources_lock);

	/* Before we emit, make sure the EDBusSource's "data" property
	 * is up-to-date.  ESource changes get propagated to the "data"
	 * property from an idle callback, which may still be pending. */

	dbus_object = e_source_ref_dbus_object (source);
	dbus_source = e_dbus_object_get_source (E_DBUS_OBJECT (dbus_object));

	data = e_source_to_string (source, NULL);
	e_dbus_source_set_data (dbus_source, data);
	g_free (data);

	g_object_unref (dbus_source);
	g_object_unref (dbus_object);

	/* If the added source has a [Collection] extension but the
	 * corresponding ECollectionBackendFactory is not available,
	 * the source gets permanently inserted in the orphans table
	 * to prevent it from being exported to client applications. */

	extension_name = E_SOURCE_EXTENSION_COLLECTION;
	if (e_source_has_extension (source, extension_name)) {
		ECollectionBackendFactory *backend_factory;

		backend_factory =
			e_source_registry_server_ref_backend_factory (
				server, source);
		if (backend_factory == NULL) {
			source_registry_server_orphans_insert (server, source);
			return;
		}
		g_object_unref (backend_factory);
	}

	source_registry_server_sources_insert (server, source);

	g_signal_emit (server, signals[SOURCE_ADDED], 0, source);

	/* This is to ensure the source data gets written to disk, since
	 * the ESource is exported now.  Could be racy otherwise if this
	 * function is called from a worker thread. */
	e_source_changed (source);

	/* Adopt any orphans that have been waiting for this object. */
	source_registry_server_adopt_orphans (server, source);
}

/* Helper for e_source_registry_server_remove_object() */
static void
source_registry_server_remove_object (ESourceRegistryServer *server,
                                      ESource *source)
{
	g_object_ref (source);

	if (source_registry_server_sources_remove (server, source)) {
		EServerSideSource *ss_source;

		ss_source = E_SERVER_SIDE_SOURCE (source);
		source_registry_server_orphans_insert (server, source);
		g_node_unlink (e_server_side_source_get_node (ss_source));
		g_signal_emit (server, signals[SOURCE_REMOVED], 0, source);
	}

	g_object_unref (source);
}

/**
 * e_source_registry_server_remove_source:
 * @server: an #ESourceRegistryServer
 * @source: an #ESource
 *
 * Removes @source and all of its descendants from @server.
 *
 * Since: 3.6
 **/
void
e_source_registry_server_remove_source (ESourceRegistryServer *server,
                                        ESource *source)
{
	ESource *child;
	ESource *exported;
	GQueue queue = G_QUEUE_INIT;
	const gchar *uid;

	g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
	g_return_if_fail (E_IS_SERVER_SIDE_SOURCE (source));

	uid = e_source_get_uid (source);

	/* If the removed source is in the server hierarchy, gather
	 * it and all of its descendants into a queue in "post-order"
	 * so we're always processing leaf nodes as we pop sources off
	 * the head of the queue. */
	exported = e_source_registry_server_ref_source (server, uid);
	if (exported != NULL) {
		source_registry_server_queue_subtree (source, &queue);
		g_object_unref (exported);
	}

	/* Move the queued descendants to the orphan table, and emit a
	 * "source-removed" signal for each source.  This will include
	 * the removed source unless the source was already an orphan,
	 * in which case the queue will be empty. */
	while ((child = g_queue_pop_head (&queue)) != NULL) {
		source_registry_server_remove_object (server, child);
		g_object_unref (child);
	}

	/* The removed source should be in the orphan table now. */
	source_registry_server_orphans_remove (server, source);
}

/**
 * e_source_registry_server_load_all:
 * @server: an #ESourceRegistryServer
 * @error: return location for a #GError, or %NULL
 *
 * Loads data source key files from standard system-wide and user-specific
 * locations.  Because multiple errors can occur when loading multiple files,
 * @error is only set if a directory can not be opened.  If a data source key
 * file fails to load, the error is broadcast through the
 * #ESourceRegistryServer::load-error signal.
 *
 * Returns: %TRUE if the standard directories were successfully opened,
 *          but this does not imply the key files were successfully loaded
 *
 * Since: 3.6
 *
 * Deprecated: 3.8: Instead, implement an equivalent function yourself.
 *                  It was a mistake to encode this much file location
 *                  policy directly into the library API.
 **/
gboolean
e_source_registry_server_load_all (ESourceRegistryServer *server,
                                   GError **error)
{
	ESourcePermissionFlags flags;
	const gchar *directory;
	gboolean success;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), FALSE);

	/* Load the user's sources directory first so that user-specific
	 * data sources overshadow predefined data sources with identical
	 * UIDs.  The 'local' data source is one such example. */

	directory = e_server_side_source_get_user_dir ();
	flags = E_SOURCE_PERMISSION_REMOVABLE |
		E_SOURCE_PERMISSION_WRITABLE;
	success = e_source_registry_server_load_directory (
		server, directory, flags, error);
	g_prefix_error (error, "%s: ", directory);

	if (!success)
		return FALSE;

	directory = SYSTEM_WIDE_RO_SOURCES_DIRECTORY;
	flags = E_SOURCE_PERMISSION_NONE;
	success = e_source_registry_server_load_directory (
		server, directory, flags, error);
	g_prefix_error (error, "%s: ", directory);

	if (!success)
		return FALSE;

	directory = SYSTEM_WIDE_RW_SOURCES_DIRECTORY;
	flags = E_SOURCE_PERMISSION_WRITABLE;
	success = e_source_registry_server_load_directory (
		server, directory, flags, error);
	g_prefix_error (error, "%s: ", directory);

	if (!success)
		return FALSE;

	/* Signal that all files are now loaded. */
	g_signal_emit (server, signals[FILES_LOADED], 0);

	return TRUE;
}

/**
 * e_source_registry_server_load_directory:
 * @server: an #ESourceRegistryServer
 * @path: the path to the directory to load
 * @flags: permission flags for files loaded from @path
 * @error: return location for a #GError, or %NULL
 *
 * Loads data source key files in @path.  Because multiple errors can
 * occur when loading multiple files, @error is only set if @path can
 * not be opened.  If a key file fails to load, the error is broadcast
 * through the #ESourceRegistryServer::load-error signal.
 *
 * If the #E_SOURCE_PERMISSION_REMOVABLE flag is given, then the @server
 * will emit signals on the D-Bus interface when key files are created or
 * deleted in @path.
 *
 * Returns: %TRUE if @path was successfully opened, but this
 *          does not imply the key files were successfully loaded
 *
 * Since: 3.6
 **/
gboolean
e_source_registry_server_load_directory (ESourceRegistryServer *server,
                                         const gchar *path,
                                         ESourcePermissionFlags flags,
                                         GError **error)
{
	GDir *dir;
	GFile *file;
	const gchar *name;
	gboolean removable;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), FALSE);
	g_return_val_if_fail (path != NULL, FALSE);

	removable = ((flags & E_SOURCE_PERMISSION_REMOVABLE) != 0);

	/* If the directory doesn't exist then there's nothing to load.
	 * Note we do not use G_FILE_TEST_DIR here.  If the given path
	 * exists but is not a directory then we let g_dir_open() fail. */
	if (!g_file_test (path, G_FILE_TEST_EXISTS))
		return TRUE;

	dir = g_dir_open (path, 0, error);
	if (dir == NULL)
		return FALSE;

	file = g_file_new_for_path (path);

	while ((name = g_dir_read_name (dir)) != NULL) {
		ESource *source;
		GFile *child;
		GError *local_error = NULL;

		/* Ignore files with no ".source" suffix. */
		if (!g_str_has_suffix (name, ".source"))
			continue;

		child = g_file_get_child (file, name);

		source = e_source_registry_server_load_file (
			server, child, flags, &local_error);

		/* We don't need the returned reference. */
		if (source != NULL)
			g_object_unref (source);

		if (local_error != NULL) {
			e_source_registry_server_load_error (
				server, child, local_error);
			g_error_free (local_error);
		}

		g_object_unref (child);
	}

	g_dir_close (dir);

	/* Only data source files in the user's
	 * sources directory should be removable. */
	if (removable) {
		GFileMonitor *monitor;
		GError *local_error = NULL;

		/* Directory monitoring is a nice-to-have feature.
		 * If this fails, leave a breadcrumb on the console
		 * to indicate something went wrong, but don't return
		 * an error status. */
		monitor = g_file_monitor_directory (
			file, G_FILE_MONITOR_NONE, NULL, &local_error);

		/* Sanity check. */
		g_warn_if_fail (
			((monitor != NULL) && (local_error == NULL)) ||
			((monitor == NULL) && (local_error != NULL)));

		if (monitor != NULL) {
			g_signal_connect (
				monitor, "changed", G_CALLBACK (
				source_registry_server_monitor_changed_cb),
				server);

			g_hash_table_insert (
				server->priv->monitors,
				g_object_ref (file),
				g_object_ref (monitor));

			g_object_unref (monitor);
		}

		if (local_error != NULL) {
			g_warning ("%s: %s", G_STRFUNC, local_error->message);
			g_error_free (local_error);
		}
	}

	g_object_unref (file);

	return TRUE;
}

/**
 * e_source_registry_server_load_resource:
 * @server: an #ESourceRegistryServer
 * @resource: a #GResource containing data source key files
 * @path: the path to the data source key files inside @resource
 * @flags: permission flags for files loaded from @path
 * @error: return location for a #GError, or %NULL
 *
 * Loads data source key files from @resource by enumerating the children
 * at @path and calling e_source_registry_server_load_file() on each child.
 * Because multiple errors can occur when loading multiple files, @error is
 * only set if @path is invalid.  If a key file fails to load, the error is
 * broadcast through the #ESourceRegistryServer::load-error signal.
 *
 * Returns: %TRUE if @path was successfully located, but this does not
 *          imply the key files were successfully loaded
 *
 * Since: 3.8
 **/
gboolean
e_source_registry_server_load_resource (ESourceRegistryServer *server,
                                        GResource *resource,
                                        const gchar *path,
                                        ESourcePermissionFlags flags,
                                        GError **error)
{
	gchar **children;
	gint ii;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), FALSE);
	g_return_val_if_fail (resource != NULL, FALSE);
	g_return_val_if_fail (path != NULL, FALSE);

	children = g_resource_enumerate_children (
		resource, path, G_RESOURCE_LOOKUP_FLAGS_NONE, error);

	if (children == NULL)
		return FALSE;

	for (ii = 0; children[ii] != NULL; ii++) {
		ESource *source;
		GFile *file;
		gchar *child_path;
		gchar *resource_uri;
		GError *local_error = NULL;

		child_path = g_build_path ("/", path, children[ii], NULL);
		resource_uri = g_strconcat ("resource://", child_path, NULL);
		file = g_file_new_for_uri (resource_uri);
		g_free (resource_uri);
		g_free (child_path);

		source = e_source_registry_server_load_file (
			server, file, flags, &local_error);

		/* We don't need the returned reference. */
		if (source != NULL)
			g_object_unref (source);

		if (local_error != NULL) {
			e_source_registry_server_load_error (
				server, file, local_error);
			g_error_free (local_error);
		}

		g_object_unref (file);
	}

	g_strfreev (children);

	return TRUE;
}

/* Helper for e_source_registry_server_load_file() */
static gboolean
source_registry_server_tweak_key_file (ESourceRegistryServer *server,
                                       GFile *file,
                                       const gchar *uid,
                                       GError **error)
{
	GKeyFile *key_file;
	gchar *contents = NULL;
	gsize length;
	gboolean handler_pending;
	gboolean success = FALSE;
	gboolean tweaked = FALSE;

	/* Skip this if no one's listening. */
	handler_pending = g_signal_has_handler_pending (
		server, signals[TWEAK_KEY_FILE], 0, FALSE);
	if (!handler_pending)
		return TRUE;

	key_file = g_key_file_new ();

	if (!g_file_load_contents (file, NULL, &contents, &length, NULL, error)) {
		contents = NULL;
		length = 0;
	}

	if (contents != NULL) {
		success = g_key_file_load_from_data (
			key_file, contents, length,
			G_KEY_FILE_KEEP_COMMENTS |
			G_KEY_FILE_KEEP_TRANSLATIONS,
			error);
		g_free (contents);
	}

	if (success)
		g_signal_emit (
			server, signals[TWEAK_KEY_FILE], 0,
			key_file, uid, &tweaked);

	if (tweaked) {
		contents = g_key_file_to_data (key_file, &length, NULL);
		success = g_file_replace_contents (
			file, contents, length, NULL, FALSE,
			G_FILE_CREATE_NONE, NULL, NULL, error);
		g_free (contents);
	}

	g_key_file_free (key_file);

	return success;
}

/**
 * e_source_registry_server_load_file:
 * @server: an #ESourceRegistryServer
 * @file: the data source key file to load
 * @flags: initial permission flags for the data source
 * @error: return location for a #GError, or %NULL
 *
 * Creates an #ESource for a native key file and adds it to @server.
 * If an error occurs, the function returns %NULL and sets @error.
 *
 * The returned #ESource is referenced for thread-safety.  Unreference
 * the #ESource with g_object_unref() when finished with it.
 *
 * Returns: (transfer full) (nullable): the newly-added #ESource, or %NULL on error
 *
 * Since: 3.6
 **/
ESource *
e_source_registry_server_load_file (ESourceRegistryServer *server,
                                    GFile *file,
                                    ESourcePermissionFlags flags,
                                    GError **error)
{
	ESource *source;
	gboolean writable;
	gboolean removable;
	gboolean success = TRUE;
	gchar *uid;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
	g_return_val_if_fail (G_IS_FILE (file), NULL);

	writable = ((flags & E_SOURCE_PERMISSION_WRITABLE) != 0);
	removable = ((flags & E_SOURCE_PERMISSION_REMOVABLE) != 0);

	uid = e_server_side_source_uid_from_file (file, error);

	if (uid == NULL)
		return NULL;

	/* Check if we already have this file loaded. */
	source = e_source_registry_server_ref_source (server, uid);

	/* If the source is to be removable then the key file can
	 * be written back to disk, and can therefore be tweaked. */
	if (source == NULL && removable)
		success = source_registry_server_tweak_key_file (
			server, file, uid, error);

	if (source == NULL && success)
		source = e_server_side_source_new (server, file, error);

	g_free (uid);

	if (source == NULL)
		return NULL;

	/* Set the data source's initial permissions, which
	 * determines which D-Bus methods it exports: write()
	 * if writable, remove() if removable.  We apply these
	 * before adding the source to the server because some
	 * "source-added" signal handlers may wish to override
	 * the initial permissions.
	 *
	 * Note that we apply the initial permission flags even
	 * if the data source has already been loaded.  That is
	 * intentional.  That is why the load_all() function loads
	 * the user directory before loading system-wide directories.
	 * If there's a UID collision between a data source in the
	 * user's directory and a data source in a system-wide
	 * directory, the permission flags for the system-wide
	 * directory should win.
	 *
	 * Consider an example:
	 *
	 * The built-in 'local' data source should always be
	 * writable but not removable.
	 *
	 * Suppose the user temporarily disables the 'local'
	 * data source.  The altered 'local' data source file
	 * (with Enabled=false) is saved in the user's sources
	 * directory.
	 *
	 * On the next startup, the altered 'local' file is
	 * first loaded from the user's source directory and
	 * given removable + writable permissions.
	 *
	 * We then load data sources from the 'rw-sources'
	 * system directory containing the unaltered 'local'
	 * file (with Enabled=true), which is not removable.
	 *
	 * We keep the contents of the altered 'local' file
	 * (Enabled=false), but override its permissions to
	 * just be writable, not removable.
	 */
	e_server_side_source_set_writable (
		E_SERVER_SIDE_SOURCE (source), writable);
	e_server_side_source_set_removable (
		E_SERVER_SIDE_SOURCE (source), removable);

	/* This does nothing if the source is already added. */
	e_source_registry_server_add_source (server, source);

	return source;
}

/**
 * e_source_registry_server_load_error:
 * @server: an #ESourceRegistryServer
 * @file: the #GFile that failed to load
 * @error: a #GError describing the load error
 *
 * Emits the #ESourceRegistryServer::load-error signal.
 *
 * Since: 3.6
 **/
void
e_source_registry_server_load_error (ESourceRegistryServer *server,
                                     GFile *file,
                                     const GError *error)
{
	g_return_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server));
	g_return_if_fail (G_IS_FILE (file));
	g_return_if_fail (error != NULL);

	g_signal_emit (server, signals[LOAD_ERROR], 0, file, error);
}

/**
 * e_source_registry_server_ref_source:
 * @server: an #ESourceRegistryServer
 * @uid: a unique identifier string
 *
 * Looks up an #ESource in @server by its unique identifier string.
 *
 * The returned #ESource is referenced for thread-safety and must be
 * unreferenced with g_object_unref() when finished with it.
 *
 * Returns: (transfer full) (nullable): an #ESource, or %NULL if no match was found
 *
 * Since: 3.6
 **/
ESource *
e_source_registry_server_ref_source (ESourceRegistryServer *server,
                                     const gchar *uid)
{
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
	g_return_val_if_fail (uid != NULL, NULL);

	return source_registry_server_sources_lookup (server, uid);
}

/**
 * e_source_registry_server_list_sources:
 * @server: an #ESourceRegistryServer
 * @extension_name: (nullable): an extension name, or %NULL
 *
 * Returns a list of registered sources, sorted by display name.  If
 * @extension_name is given, restrict the list to sources having that
 * extension name.
 *
 * The sources returned in the list are referenced for thread-safety.
 * They must each be unreferenced with g_object_unref() when finished
 * with them.  Free the returned #GList itself with g_list_free().
 *
 * An easy way to free the list properly in one step is as follows:
 *
 * |[
 *   g_list_free_full (list, g_object_unref);
 * ]|
 *
 * Returns: (element-type ESource) (transfer full): a sorted list of sources
 *
 * Since: 3.6
 **/
GList *
e_source_registry_server_list_sources (ESourceRegistryServer *server,
                                       const gchar *extension_name)
{
	GList *list, *link;
	GQueue trash = G_QUEUE_INIT;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);

	list = g_list_sort (
		source_registry_server_sources_get_values (server),
		(GCompareFunc) e_source_compare_by_display_name);

	if (extension_name == NULL)
		return list;

	for (link = list; link != NULL; link = g_list_next (link)) {
		ESource *source = E_SOURCE (link->data);

		if (!e_source_has_extension (source, extension_name)) {
			g_queue_push_tail (&trash, link);
			g_object_unref (source);
		}
	}

	/* We do want pop_head() here, not pop_head_link(). */
	while ((link = g_queue_pop_head (&trash)) != NULL)
		list = g_list_delete_link (list, link);

	return list;
}

/**
 * e_source_registry_server_find_extension:
 * @server: an #ESourceRegistryServer
 * @source: an #ESource
 * @extension_name: the extension name to find
 *
 * Examines @source and its ancestors and returns the "deepest" #ESource
 * having an #ESourceExtension with the given @extension_name.  If neither
 * @source nor any of its ancestors have such an extension, the function
 * returns %NULL.
 *
 * This function is useful in cases when an #ESourceExtension is meant to
 * apply to both the #ESource it belongs to and the #ESource's descendants.
 *
 * A common example is the #ESourceCollection extension, where descendants
 * of an #ESource having an #ESourceCollection extension are implied to be
 * members of that collection.  In that example, this function can be used
 * to test whether @source is a member of a collection.
 *
 * The returned #ESource is referenced for thread-safety and must be
 * unreferenced with g_object_unref() when finished with it.
 *
 * Note the function returns the #ESource containing the #ESourceExtension
 * instead of the #ESourceExtension itself because extension instances are
 * not to be referenced directly (see e_source_get_extension()).
 *
 * Returns: (transfer full) (nullable): an #ESource, or %NULL if no match was found
 *
 * Since: 3.8
 **/
ESource *
e_source_registry_server_find_extension (ESourceRegistryServer *server,
                                         ESource *source,
                                         const gchar *extension_name)
{
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
	g_return_val_if_fail (extension_name != NULL, NULL);

	g_object_ref (source);

	while (!e_source_has_extension (source, extension_name)) {
		gchar *uid;

		uid = e_source_dup_parent (source);

		g_object_unref (source);
		source = NULL;

		if (uid != NULL) {
			source = e_source_registry_server_ref_source (
				server, uid);
			g_free (uid);
		}

		if (source == NULL)
			break;
	}

	return source;
}

/**
 * e_source_registry_server_ref_backend:
 * @server: an #ESourceRegistryServer
 * @source: an #ESource
 *
 * Returns the #ECollectionBackend associated with @source, or %NULL if
 * there is no #ECollectionBackend associated with @source.
 *
 * An #ESource is associated with an #ECollectionBackend if the #ESource has
 * an #ESourceCollection extension, or if it is a hierarchical descendant of
 * another #ESource which has an #ESourceCollection extension.
 *
 * The returned #ECollectionBackend is referenced for thread-safety.
 * Unreference the #ECollectionBackend with g_object_unref() when finished
 * with it.
 *
 * Returns: (transfer full) (nullable): the #ECollectionBackend for @source, or %NULL
 *
 * Since: 3.6
 **/
ECollectionBackend *
e_source_registry_server_ref_backend (ESourceRegistryServer *server,
                                      ESource *source)
{
	ECollectionBackend *backend = NULL;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
	g_return_val_if_fail (E_IS_SOURCE (source), NULL);

	source = e_source_registry_server_find_extension (
		server, source, E_SOURCE_EXTENSION_COLLECTION);

	if (source != NULL) {
		backend = g_object_get_data (
			G_OBJECT (source), BACKEND_DATA_KEY);
		if (backend != NULL)
			g_object_ref (backend);
		g_object_unref (source);
	}

	return backend;
}

/**
 * e_source_registry_server_ref_backend_factory:
 * @server: an #ESourceRegistryServer
 * @source: an #ESource
 *
 * Returns the #ECollectionBackendFactory for @source, if available.
 * If @source does not have an #ESourceCollection extension, or if the
 * #ESourceCollection extension names an #ESourceBackend:backend-name for
 * which there is no corresponding #ECollectionBackendFactory, the function
 * returns %NULL.
 *
 * The returned #ECollectionBackendFactory is referenced for thread-safety.
 * Unreference the #ECollectionBackendFactory with g_object_unref() when
 * finished with it.
 *
 * Returns: (transfer full) (nullable): the #ECollectionBackendFactory for @source,
 *    or %NULL
 *
 * Since: 3.6
 **/
ECollectionBackendFactory *
e_source_registry_server_ref_backend_factory (ESourceRegistryServer *server,
                                              ESource *source)
{
	EBackendFactory *factory;
	ESourceBackend *extension;
	const gchar *backend_name;
	const gchar *extension_name;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);
	g_return_val_if_fail (E_IS_SOURCE (source), NULL);

	/* XXX Should we also check ancestor sources for a collection
	 *     extension so this function works for ANY source in the
	 *     collection?  Gonna refrain til a real use case emerges
	 *     but it's something to keep in mind. */

	extension_name = E_SOURCE_EXTENSION_COLLECTION;
	if (!e_source_has_extension (source, extension_name))
		return NULL;

	extension = e_source_get_extension (source, extension_name);
	backend_name = e_source_backend_get_backend_name (extension);

	factory = e_data_factory_ref_backend_factory (
		E_DATA_FACTORY (server), backend_name, extension_name);

	if (factory == NULL)
		return NULL;

	/* The factory *should* be an ECollectionBackendFactory.
	 * We specify this in source_registry_server_class_init(). */
	return E_COLLECTION_BACKEND_FACTORY (factory);
}

/**
 * e_source_registry_server_ref_oauth2_support:
 * @server: an #ESourceRegistryServer
 *
 * Returns the default #EOAuth2Support implementation, which can be used when
 * the source doesn't have it overwritten.
 *
 * Free the returned object with g_object_unref(), when no longer needed.
 *
 * Returns: (transfer full) (nullable): the default #EOAuth2Support,
 *    or %NULL, when none exists
 *
 * Since: 3.40
 **/
EOAuth2Support *
e_source_registry_server_ref_oauth2_support (ESourceRegistryServer *server)
{
	g_return_val_if_fail (E_IS_SOURCE_REGISTRY_SERVER (server), NULL);

	if (!server->priv->oauth2_services)
		return NULL;

	return g_object_ref (E_OAUTH2_SUPPORT (server));
}
