<commit_before>/*  XMMS2 - X Music Multiplexer System
 *  Copyright (C) 2003-2006 XMMS2 Team
 *
 *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
 *
 *  This library 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 License, or (at your option) any later version.
 *
 *  This 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
 *  Lesser General Public License for more details.
 */


/** @file
 *  Manages collections
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <math.h>

#include "xmmspriv/xmms_collection.h"
#include "xmmspriv/xmms_playlist.h"
#include "xmmspriv/xmms_medialib.h"
#include "xmms/xmms_ipc.h"
#include "xmms/xmms_config.h"
#include "xmms/xmms_log.h"


/* Internal helper structures */

typedef void (*FuncApplyToColl)(xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);

typedef struct {
	guint limit_start;
	guint limit_len;
	GList *order;
	GList *group;
	GList *fetch;
} coll_query_params_t;

typedef struct {
	guint alias_count;
	GString *conditions;
	coll_query_params_t *params;
} coll_query_t;

typedef struct {
	gchar* name;
	gchar* namespace;
	xmmsc_coll_t *oldtarget;
	xmmsc_coll_t *newtarget;
} coll_rebind_infos_t;

typedef struct {
	xmms_coll_dag_t *dag;
	FuncApplyToColl func;
	void *udata;
} coll_call_infos_t;

typedef struct {
	gchar *target_name;
	gchar *target_namespace;
	gboolean found;
} coll_refcheck_t;


/* Functions */

static void xmms_collection_destroy (xmms_object_t *object);

static gboolean xmms_collection_validate (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, gchar *save_name, gchar *save_namespace);


static xmms_collection_namespace_id_t xmms_collection_get_namespace_id (gchar *namespace);
static gchar* xmms_collection_get_namespace_string (xmms_collection_namespace_id_t nsid);
static xmmsc_coll_t * xmms_collection_get_pointer (xmms_coll_dag_t *dag, gchar *collname, guint namespace);
static gboolean xmms_collection_has_reference_to (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, gchar *tg_name, gchar *tg_ns);

static void xmms_collection_foreach_in_namespace (xmms_coll_dag_t *dag, guint nsid, GHFunc f, void *udata);
static void xmms_collection_apply_to_all_collections (xmms_coll_dag_t *dag, FuncApplyToColl f, void *udata);
static void xmms_collection_apply_to_collection (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, FuncApplyToColl f, void *udata);
static void xmms_collection_apply_to_collection_recurs (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, FuncApplyToColl f, void *udata);

static void call_apply_to_coll (gpointer name, gpointer coll, gpointer udata);
static void prepend_key_string (gpointer key, gpointer value, gpointer udata);

static void bind_all_references (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);
static void rebind_references (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);
static void strip_references (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);
static void check_for_reference (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);

static void coll_unref (void *coll);


static void query_append_uint (coll_query_t *query, guint i);
static void query_append_string (coll_query_t *query, gchar *s);
static void query_append_protect_string (coll_query_t *query, gchar *s);
static void query_append_currfield (coll_query_t *query);
static void query_append_currvalue (coll_query_t *query);
static void query_append_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsc_coll_t *coll);
static void xmms_collection_append_to_query (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, coll_query_t *query);
static coll_query_t* init_query (coll_query_params_t *params);
static GString* xmms_collection_gen_query (coll_query_t *query);
static GString* xmms_collection_get_query (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, coll_query_params_t *params);


XMMS_CMD_DEFINE (collection_get, xmms_collection_get, xmms_coll_dag_t *, COLL, STRING, STRING);
XMMS_CMD_DEFINE (collection_list, xmms_collection_list, xmms_coll_dag_t *, LIST, STRING, NONE);
XMMS_CMD_DEFINE3(collection_save, xmms_collection_save, xmms_coll_dag_t *, NONE, STRING, STRING, COLL);
XMMS_CMD_DEFINE (collection_remove, xmms_collection_remove, xmms_coll_dag_t *, NONE, STRING, STRING);
XMMS_CMD_DEFINE (collection_find, xmms_collection_find, xmms_coll_dag_t *, LIST, UINT32, STRING);

XMMS_CMD_DEFINE4(query_ids, xmms_collection_query_ids, xmms_coll_dag_t *, LIST, COLL, UINT32, UINT32, STRINGLIST);
/* FIXME: Arrays, num args, etc?
XMMS_CMD_DEFINE6(query_infos, xmms_collection_query_infos, xmms_coll_dag_t *, LIST, COLL, UINT32, UINT32, LIST, LIST, LIST);
*/



/** @defgroup Collection Collection
  * @ingroup XMMSServer
  * @brief This is the collection manager.
  *
  * The set of collections is stored as a DAG of collection operators.
  * Each collection namespace contains a list of saved collections,
  * with a pointer to the node in the graph.
  * @{
  */

/** Collection DAG structure */

struct xmms_coll_dag_St {
	xmms_object_t object;

	GHashTable *collrefs[XMMS_COLLECTION_NUM_NAMESPACES];

	GMutex *mutex;

};


/** Initializes a new xmms_coll_dag_t.
 *
 * @returns  The newly allocated collection DAG.
 */
xmms_coll_dag_t *
xmms_collection_init (void)
{
	gint i;
	xmms_coll_dag_t *ret;
	
	ret = xmms_object_new (xmms_coll_dag_t, xmms_collection_destroy);
	ret->mutex = g_mutex_new ();

	for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
		ret->collrefs[i] = g_hash_table_new_full (g_str_hash, g_str_equal,
		                                          g_free, coll_unref);
	}

	xmms_ipc_object_register (XMMS_IPC_OBJECT_COLLECTION, XMMS_OBJECT (ret));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_GET, 
			     XMMS_CMD_FUNC (collection_get));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_LIST, 
			     XMMS_CMD_FUNC (collection_list));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_SAVE, 
			     XMMS_CMD_FUNC (collection_save));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_REMOVE, 
			     XMMS_CMD_FUNC (collection_remove));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_FIND, 
			     XMMS_CMD_FUNC (collection_find));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_QUERY_IDS, 
			     XMMS_CMD_FUNC (query_ids));

/*
	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_QUERY_INFOS, 
			     XMMS_CMD_FUNC (query_infos));
*/
	return ret;
}

/** Remove the given collection from the DAG.
 *
 * If to be removed from ALL namespaces, then all matching collections are removed.
 *
 * @param dag  The collection DAG.
 * @param name  The name of the collection to remove.
 * @param namespace  The namespace where the collection to remove is (can be ALL).
 * @param err  If an error occurs, a message is stored in it.
 * @returns  True on success, false otherwise.
 */
gboolean
xmms_collection_remove (xmms_coll_dag_t *dag, gchar *name, gchar *namespace, xmms_error_t *err)
{
	xmmsc_coll_t *existing = NULL;
	guint32 nsid;
	gboolean retval;
	gint i;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_remove");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return FALSE;
	}

	g_mutex_lock (dag->mutex);

	/* FIXME: reduce copy-paste */
	if (nsid == XMMS_COLLECTION_NSID_ALL) {
		for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
			existing = g_hash_table_lookup (dag->collrefs[i], name);
			if (existing != NULL) {
				char *nsname = xmms_collection_get_namespace_string (i);

				/* Strip all references to the deleted coll, bind operator directly */
				coll_rebind_infos_t infos = { name, nsname, existing, NULL };
				xmms_collection_apply_to_all_collections (dag, strip_references, &infos);

				g_hash_table_remove (dag->collrefs[i], name);
				retval = TRUE;
			}
		}
	}
	else {
		existing = g_hash_table_lookup (dag->collrefs[nsid], name);
		if (existing != NULL) {
			/* Strip all references to the deleted coll, bind operator directly */
			coll_rebind_infos_t infos = { name, namespace, existing, NULL };
			xmms_collection_apply_to_all_collections (dag, strip_references, &infos);

			g_hash_table_remove (dag->collrefs[nsid], name);
			retval = TRUE;
		}
	}

	g_mutex_unlock (dag->mutex);

	if (retval == FALSE) {
		xmms_error_set (err, XMMS_ERROR_NOENT, "No such collection!");
	}

	return retval;
}

/** Save the given collection in the DAG under the given name in the given namespace.
 *
 * @param dag  The collection DAG in which to save the collection.
 * @param name  The name under which to save the collection.
 * @param namespace  The namespace in which to save th collection.
 * @param coll  The collection structure to save.
 * @param err  If an error occurs, a message is stored in it.
 * @returns  True on success, false otherwise.
 */
gboolean
xmms_collection_save (xmms_coll_dag_t *dag, gchar *name, gchar *namespace,
                      xmmsc_coll_t *coll, xmms_error_t *err)
{
	xmmsc_coll_t *existing;
	guint32 nsid;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_save");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return FALSE;
	}
	else if (nsid == XMMS_COLLECTION_NSID_ALL) {
		xmms_error_set (err, XMMS_ERROR_GENERIC, "cannot save collection in all namespaces");
		return FALSE;
	}

	/* Validate collection structure */
	if (!xmms_collection_validate (dag, coll, name, namespace)) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection structure");
		return FALSE;
	}

	g_mutex_lock (dag->mutex);

	/* Unreference previously saved collection */
	existing = xmms_collection_get_pointer (dag, name, nsid);
	if (existing != NULL) {
		/* Rebind reference pointers to the new collection */
		coll_rebind_infos_t infos = { name, namespace, existing, coll };
		xmms_collection_apply_to_all_collections (dag, rebind_references, &infos);
	}

	/* Link references in saved collection to actual operators */
	xmms_collection_apply_to_collection (dag, coll, bind_all_references, NULL);

	/* Save new collection in the table */
	g_hash_table_replace (dag->collrefs[nsid], g_strdup (name), coll);
	xmmsc_coll_ref (coll);

	g_mutex_unlock (dag->mutex);

	XMMS_DBG("COLLECTIONS: xmms_collection_save, end");

	return TRUE;
}

/** Retrieve the structure of a given collection.
 *
 * If looking in ALL namespaces, only the collection first found is returned!
 *
 * @param dag  The collection DAG.
 * @param name  The name of the collection to retrieve.
 * @param namespace  The namespace in which to look for the collection.
 * @param err  If an error occurs, a message is stored in it.
 * @returns  The collection structure if found, NULL otherwise.
 */
xmmsc_coll_t *
xmms_collection_get (xmms_coll_dag_t *dag, gchar *name, gchar *namespace, xmms_error_t *err)
{
	xmmsc_coll_t *coll = NULL;
	guint32 nsid;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_get");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return NULL;
	}

	g_mutex_lock (dag->mutex);

	coll = xmms_collection_get_pointer (dag, name, nsid);

	/* Not found! */
	if(coll == NULL) {
		xmms_error_set (err, XMMS_ERROR_NOENT, "no such collection");
	}
	/* New reference, will be freed after being put in the return message */
	else {
		xmmsc_coll_ref (coll);
	}
	
	g_mutex_unlock (dag->mutex);

	XMMS_DBG("COLLECTIONS: xmms_collection_get, end");

	return coll;
}


/** Lists the collections in the given namespace.
 *
 * @param dag  The collection DAG.
 * @param namespace  The namespace to list collections from (can be ALL).
 * @returns A newly allocated GList with the list of collection names.
 * Remeber that it is only the LIST that is copied. Not the entries.
 * The entries are however referenced, and must be unreffed!
 */
GList *
xmms_collection_list (xmms_coll_dag_t *dag, gchar *namespace, xmms_error_t *err)
{
	GList *r = NULL;
	guint32 nsid;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_list");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return NULL;
	}

	g_mutex_lock (dag->mutex);

	/* Get the list of collections in the given namespace */
	xmms_collection_foreach_in_namespace (dag, nsid, prepend_key_string, &r);

	g_mutex_unlock (dag->mutex);

	XMMS_DBG("COLLECTIONS: xmms_collection_list, end");

	return r;
}   


GList *
xmms_collection_find (xmms_coll_dag_t *dag, guint mid, gchar *namespace, xmms_error_t *err)
{
	/* FIXME: Code that later */
	return NULL;
}


GList *
xmms_collection_query_ids (xmms_coll_dag_t *dag, xmmsc_coll_t *coll,
                           guint lim_start, guint lim_len, GList *order,
                           xmms_error_t *err)
{
	GList *res = NULL;
	GList *ids = NULL;
	GList *n = NULL;
	GString *query;
	coll_query_params_t params = { lim_start, lim_len, order };

	/* validate the collection to query */
	if (!xmms_collection_validate (dag, coll, NULL, NULL)) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection structure");
		return NULL;
	}

	g_mutex_lock (dag->mutex);

	query = xmms_collection_get_query (dag, coll, &params);
	
	g_mutex_unlock (dag->mutex);

	XMMS_DBG ("COLLECTIONS: query_ids with %s", query->str);

	/* Run the query */
	xmms_medialib_session_t *session = xmms_medialib_begin ();
	res = xmms_medialib_select (session, query->str, err);
	xmms_medialib_end (session);

	g_string_free (query, TRUE);

	/* FIXME: get an int list directly !   or Int vs UInt? */
	for (n = res; n; n = n->next) {
		xmms_object_cmd_value_t *cmdval = (xmms_object_cmd_value_t*)n->data;
		gint *v = (gint*)g_hash_table_lookup (cmdval->value.dict, "id");
		ids = g_list_prepend (ids, xmms_object_cmd_value_int_new (*v));
		g_free (n->data);
	}

	g_list_free (res);

	XMMS_DBG ("COLLECTIONS: done");

	return g_list_reverse (ids);
}


/** @} */



/** Free the playlist and other memory in the xmms_playlist_t
 *
 *  This will free all entries in the list!
 */
static void
xmms_collection_destroy (xmms_object_t *object)
{
	gint i;
	xmms_coll_dag_t *dag = (xmms_coll_dag_t *)object;

	g_return_if_fail (dag);

	g_mutex_free (dag->mutex);

	for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
		g_hash_table_destroy (dag->collrefs[i]);  /* dag is freed here */
	}

	xmms_ipc_object_unregister (XMMS_IPC_OBJECT_COLLECTION);
}

/** Validate the given collection against a DAG.
 *
 * @param dag  The collection DAG.
 * @param coll  The collection to validate.
 * @param name  The name under which the collection will be saved (NULL
 *              if none).
 * @param namespace  The namespace in which the collection will be
 *                   saved (NULL if none).
 * @returns  True if the collection is valid, false otherwise.
 */
static gboolean
xmms_collection_validate (xmms_coll_dag_t *dag, xmmsc_coll_t *coll,
                          gchar *save_name, gchar *save_namespace)
{
	guint num_operands = 0;
	xmmsc_coll_t *op;
	gchar *attr, *attr2;
	gboolean valid = TRUE;
	xmms_collection_namespace_id_t nsid;

	/* count operands */
	xmmsc_coll_operand_list_save (coll);

	xmmsc_coll_operand_list_first (coll);
	while (xmmsc_coll_operand_list_entry (coll, &op)) {
		num_operands++;
		xmmsc_coll_operand_list_next (coll);
	}

	xmmsc_coll_operand_list_restore (coll);


	/* analyse by type */
	switch (xmmsc_coll_get_type (coll)) {
	case XMMS_COLLECTION_TYPE_REFERENCE:
		/* no operand */
		if (num_operands > 0) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (ref)");
			return FALSE;
		}

		/* check if referenced collection exists */
		xmmsc_coll_attribute_get (coll, "reference", &attr);
		xmmsc_coll_attribute_get (coll, "namespace", &attr2);
		if (strcmp (attr, "All Media") != 0) {
			if (attr == NULL || attr2 == NULL) {
				XMMS_DBG("COLLECTIONS: validation, ref no attr");
				return FALSE;
			}

			nsid = xmms_collection_get_namespace_id (attr2);
			if (nsid == XMMS_COLLECTION_NSID_INVALID) {
				XMMS_DBG("COLLECTIONS: validation, ref invalid ns");
				return FALSE;
			}

			g_mutex_lock (dag->mutex);
			op = xmms_collection_get_pointer (dag, attr, nsid);
			if (op == NULL) {
				XMMS_DBG("COLLECTIONS: validation, ref invalid coll");
				g_mutex_unlock (dag->mutex);
				return FALSE;
			}

			/* check if the referenced coll references this one (loop!) */
			if (save_name && save_namespace &&
			    xmms_collection_has_reference_to (dag, op, save_name, save_namespace)) {
				XMMS_DBG("COLLECTIONS: validation, ref loop");
				g_mutex_unlock (dag->mutex);
				return FALSE;
			}
			g_mutex_unlock (dag->mutex);
		}
		break;

	case XMMS_COLLECTION_TYPE_UNION:
	case XMMS_COLLECTION_TYPE_INTERSECTION:
		/* need operand(s) */
		if (num_operands == 0) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (set)");
			return FALSE;
		}
		break;

	case XMMS_COLLECTION_TYPE_COMPLEMENT:
		/* one operand */
		if (num_operands != 1) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (complement)");
			return FALSE;
		}
		break;

	case XMMS_COLLECTION_TYPE_MATCH:
	case XMMS_COLLECTION_TYPE_CONTAINS:
	case XMMS_COLLECTION_TYPE_SMALLER:
	case XMMS_COLLECTION_TYPE_GREATER:
		/* one operand */
		if (num_operands != 1) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (filter)");
			return FALSE;
		}

		/* "field"/"value" attributes */
		/* with valid values */
		if (!xmmsc_coll_attribute_get (coll, "field", &attr)) {
			XMMS_DBG("COLLECTIONS: validation, field");
			return FALSE;
		}
		/* FIXME: valid fields?
		else if (...) {
			return FALSE;
		}
		*/

		if (!xmmsc_coll_attribute_get (coll, "value", &attr)) {
			XMMS_DBG("COLLECTIONS: validation, value");
			return FALSE;
		}
		break;
	
	case XMMS_COLLECTION_TYPE_IDLIST:
		/* no operand */
		if (num_operands > 0) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (idlist)");
			return FALSE;
		}
		break;

	/* invalid type */
	default:
		XMMS_DBG("COLLECTIONS: validation, invalid type");
		return FALSE;
		break;
	}


	/* recurse in operands */
	xmmsc_coll_operand_list_save (coll);

	xmmsc_coll_operand_list_first (coll);
	while (xmmsc_coll_operand_list_entry (coll, &op) && valid) {
		if (!xmms_collection_validate (dag, op, save_name, save_namespace)) {
			valid = FALSE;
		}
		xmmsc_coll_operand_list_next (coll);
	}

	xmmsc_coll_operand_list_restore (coll);

	return valid;
}<commit_msg>Cleanup and factorize collection code.<commit_after>/*  XMMS2 - X Music Multiplexer System
 *  Copyright (C) 2003-2006 XMMS2 Team
 *
 *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
 *
 *  This library 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 License, or (at your option) any later version.
 *
 *  This 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
 *  Lesser General Public License for more details.
 */


/** @file
 *  Manages collections
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <math.h>

#include "xmmspriv/xmms_collection.h"
#include "xmmspriv/xmms_playlist.h"
#include "xmmspriv/xmms_medialib.h"
#include "xmms/xmms_ipc.h"
#include "xmms/xmms_config.h"
#include "xmms/xmms_log.h"


/* Internal helper structures */

typedef void (*FuncApplyToColl)(xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);

typedef struct {
	guint limit_start;
	guint limit_len;
	GList *order;
	GList *group;
	GList *fetch;
} coll_query_params_t;

typedef struct {
	guint alias_count;
	GString *conditions;
	coll_query_params_t *params;
} coll_query_t;

typedef struct {
	gchar* name;
	gchar* namespace;
	xmmsc_coll_t *oldtarget;
	xmmsc_coll_t *newtarget;
} coll_rebind_infos_t;

typedef struct {
	xmms_coll_dag_t *dag;
	FuncApplyToColl func;
	void *udata;
} coll_call_infos_t;

typedef struct {
	gchar *target_name;
	gchar *target_namespace;
	gboolean found;
} coll_refcheck_t;


/* Functions */

static void xmms_collection_destroy (xmms_object_t *object);

static gboolean xmms_collection_validate (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, gchar *save_name, gchar *save_namespace);
static gboolean xmms_collection_unreference (xmms_coll_dag_t *dag, gchar *name, guint nsid);


static xmms_collection_namespace_id_t xmms_collection_get_namespace_id (gchar *namespace);
static gchar* xmms_collection_get_namespace_string (xmms_collection_namespace_id_t nsid);
static xmmsc_coll_t * xmms_collection_get_pointer (xmms_coll_dag_t *dag, gchar *collname, guint namespace);
static gboolean xmms_collection_has_reference_to (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, gchar *tg_name, gchar *tg_ns);

static void xmms_collection_foreach_in_namespace (xmms_coll_dag_t *dag, guint nsid, GHFunc f, void *udata);
static void xmms_collection_apply_to_all_collections (xmms_coll_dag_t *dag, FuncApplyToColl f, void *udata);
static void xmms_collection_apply_to_collection (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, FuncApplyToColl f, void *udata);
static void xmms_collection_apply_to_collection_recurs (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, FuncApplyToColl f, void *udata);

static void call_apply_to_coll (gpointer name, gpointer coll, gpointer udata);
static void prepend_key_string (gpointer key, gpointer value, gpointer udata);

static void bind_all_references (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);
static void rebind_references (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);
static void strip_references (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);
static void check_for_reference (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, xmmsc_coll_t *parent, void *udata);

static void coll_unref (void *coll);


static void query_append_uint (coll_query_t *query, guint i);
static void query_append_string (coll_query_t *query, gchar *s);
static void query_append_protect_string (coll_query_t *query, gchar *s);
static void query_append_currfield (coll_query_t *query);
static void query_append_currvalue (coll_query_t *query);
static void query_append_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsc_coll_t *coll);
static void xmms_collection_append_to_query (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, coll_query_t *query);
static coll_query_t* init_query (coll_query_params_t *params);
static GString* xmms_collection_gen_query (coll_query_t *query);
static GString* xmms_collection_get_query (xmms_coll_dag_t *dag, xmmsc_coll_t *coll, coll_query_params_t *params);


XMMS_CMD_DEFINE (collection_get, xmms_collection_get, xmms_coll_dag_t *, COLL, STRING, STRING);
XMMS_CMD_DEFINE (collection_list, xmms_collection_list, xmms_coll_dag_t *, LIST, STRING, NONE);
XMMS_CMD_DEFINE3(collection_save, xmms_collection_save, xmms_coll_dag_t *, NONE, STRING, STRING, COLL);
XMMS_CMD_DEFINE (collection_remove, xmms_collection_remove, xmms_coll_dag_t *, NONE, STRING, STRING);
XMMS_CMD_DEFINE (collection_find, xmms_collection_find, xmms_coll_dag_t *, LIST, UINT32, STRING);

XMMS_CMD_DEFINE4(query_ids, xmms_collection_query_ids, xmms_coll_dag_t *, LIST, COLL, UINT32, UINT32, STRINGLIST);
/* FIXME: Arrays, num args, etc?
XMMS_CMD_DEFINE6(query_infos, xmms_collection_query_infos, xmms_coll_dag_t *, LIST, COLL, UINT32, UINT32, LIST, LIST, LIST);
*/



/** @defgroup Collection Collection
  * @ingroup XMMSServer
  * @brief This is the collection manager.
  *
  * The set of collections is stored as a DAG of collection operators.
  * Each collection namespace contains a list of saved collections,
  * with a pointer to the node in the graph.
  * @{
  */

/** Collection DAG structure */

struct xmms_coll_dag_St {
	xmms_object_t object;

	GHashTable *collrefs[XMMS_COLLECTION_NUM_NAMESPACES];

	GMutex *mutex;

};


/** Initializes a new xmms_coll_dag_t.
 *
 * @returns  The newly allocated collection DAG.
 */
xmms_coll_dag_t *
xmms_collection_init (void)
{
	gint i;
	xmms_coll_dag_t *ret;
	
	ret = xmms_object_new (xmms_coll_dag_t, xmms_collection_destroy);
	ret->mutex = g_mutex_new ();

	for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
		ret->collrefs[i] = g_hash_table_new_full (g_str_hash, g_str_equal,
		                                          g_free, coll_unref);
	}

	xmms_ipc_object_register (XMMS_IPC_OBJECT_COLLECTION, XMMS_OBJECT (ret));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_GET, 
			     XMMS_CMD_FUNC (collection_get));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_LIST, 
			     XMMS_CMD_FUNC (collection_list));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_SAVE, 
			     XMMS_CMD_FUNC (collection_save));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_REMOVE, 
			     XMMS_CMD_FUNC (collection_remove));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_COLLECTION_FIND, 
			     XMMS_CMD_FUNC (collection_find));

	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_QUERY_IDS, 
			     XMMS_CMD_FUNC (query_ids));

/*
	xmms_object_cmd_add (XMMS_OBJECT (ret), 
			     XMMS_IPC_CMD_QUERY_INFOS, 
			     XMMS_CMD_FUNC (query_infos));
*/
	return ret;
}

/** Remove the given collection from the DAG.
 *
 * If to be removed from ALL namespaces, then all matching collections are removed.
 *
 * @param dag  The collection DAG.
 * @param name  The name of the collection to remove.
 * @param namespace  The namespace where the collection to remove is (can be ALL).
 * @param err  If an error occurs, a message is stored in it.
 * @returns  True on success, false otherwise.
 */
gboolean
xmms_collection_remove (xmms_coll_dag_t *dag, gchar *name, gchar *namespace, xmms_error_t *err)
{
	guint nsid;
	gboolean retval;
	guint i;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_remove");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return FALSE;
	}

	g_mutex_lock (dag->mutex);

	/* Unreference the matching collection(s) */
	if (nsid == XMMS_COLLECTION_NSID_ALL) {
		for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
			retval = xmms_collection_unreference (dag, name, i) || retval;
		}
	}
	else {
		retval = xmms_collection_unreference (dag, name, nsid);
	}

	g_mutex_unlock (dag->mutex);

	if (retval == FALSE) {
		xmms_error_set (err, XMMS_ERROR_NOENT, "No such collection!");
	}

	return retval;
}

/** Save the given collection in the DAG under the given name in the given namespace.
 *
 * @param dag  The collection DAG in which to save the collection.
 * @param name  The name under which to save the collection.
 * @param namespace  The namespace in which to save th collection.
 * @param coll  The collection structure to save.
 * @param err  If an error occurs, a message is stored in it.
 * @returns  True on success, false otherwise.
 */
gboolean
xmms_collection_save (xmms_coll_dag_t *dag, gchar *name, gchar *namespace,
                      xmmsc_coll_t *coll, xmms_error_t *err)
{
	xmmsc_coll_t *existing;
	guint nsid;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_save");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return FALSE;
	}
	else if (nsid == XMMS_COLLECTION_NSID_ALL) {
		xmms_error_set (err, XMMS_ERROR_GENERIC, "cannot save collection in all namespaces");
		return FALSE;
	}

	/* Validate collection structure */
	if (!xmms_collection_validate (dag, coll, name, namespace)) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection structure");
		return FALSE;
	}

	g_mutex_lock (dag->mutex);

	/* Unreference previously saved collection */
	existing = xmms_collection_get_pointer (dag, name, nsid);
	if (existing != NULL) {
		/* Rebind reference pointers to the new collection */
		coll_rebind_infos_t infos = { name, namespace, existing, coll };
		xmms_collection_apply_to_all_collections (dag, rebind_references, &infos);
	}

	/* Link references in saved collection to actual operators */
	xmms_collection_apply_to_collection (dag, coll, bind_all_references, NULL);

	/* Save new collection in the table */
	g_hash_table_replace (dag->collrefs[nsid], g_strdup (name), coll);
	xmmsc_coll_ref (coll);

	g_mutex_unlock (dag->mutex);

	XMMS_DBG("COLLECTIONS: xmms_collection_save, end");

	return TRUE;
}

/** Retrieve the structure of a given collection.
 *
 * If looking in ALL namespaces, only the collection first found is returned!
 *
 * @param dag  The collection DAG.
 * @param name  The name of the collection to retrieve.
 * @param namespace  The namespace in which to look for the collection.
 * @param err  If an error occurs, a message is stored in it.
 * @returns  The collection structure if found, NULL otherwise.
 */
xmmsc_coll_t *
xmms_collection_get (xmms_coll_dag_t *dag, gchar *name, gchar *namespace, xmms_error_t *err)
{
	xmmsc_coll_t *coll = NULL;
	guint nsid;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_get");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return NULL;
	}

	g_mutex_lock (dag->mutex);

	coll = xmms_collection_get_pointer (dag, name, nsid);

	/* Not found! */
	if(coll == NULL) {
		xmms_error_set (err, XMMS_ERROR_NOENT, "no such collection");
	}
	/* New reference, will be freed after being put in the return message */
	else {
		xmmsc_coll_ref (coll);
	}
	
	g_mutex_unlock (dag->mutex);

	XMMS_DBG("COLLECTIONS: xmms_collection_get, end");

	return coll;
}


/** Lists the collections in the given namespace.
 *
 * @param dag  The collection DAG.
 * @param namespace  The namespace to list collections from (can be ALL).
 * @returns A newly allocated GList with the list of collection names.
 * Remeber that it is only the LIST that is copied. Not the entries.
 * The entries are however referenced, and must be unreffed!
 */
GList *
xmms_collection_list (xmms_coll_dag_t *dag, gchar *namespace, xmms_error_t *err)
{
	GList *r = NULL;
	guint nsid;

	XMMS_DBG("COLLECTIONS: Entering xmms_collection_list");

	nsid = xmms_collection_get_namespace_id (namespace);
	if (nsid == XMMS_COLLECTION_NSID_INVALID) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
		return NULL;
	}

	g_mutex_lock (dag->mutex);

	/* Get the list of collections in the given namespace */
	xmms_collection_foreach_in_namespace (dag, nsid, prepend_key_string, &r);

	g_mutex_unlock (dag->mutex);

	XMMS_DBG("COLLECTIONS: xmms_collection_list, end");

	return r;
}   


GList *
xmms_collection_find (xmms_coll_dag_t *dag, guint mid, gchar *namespace, xmms_error_t *err)
{
	/* FIXME: Code that later */
	return NULL;
}


GList *
xmms_collection_query_ids (xmms_coll_dag_t *dag, xmmsc_coll_t *coll,
                           guint lim_start, guint lim_len, GList *order,
                           xmms_error_t *err)
{
	GList *res = NULL;
	GList *ids = NULL;
	GList *n = NULL;
	GString *query;
	coll_query_params_t params = { lim_start, lim_len, order };

	/* validate the collection to query */
	if (!xmms_collection_validate (dag, coll, NULL, NULL)) {
		xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection structure");
		return NULL;
	}

	g_mutex_lock (dag->mutex);

	query = xmms_collection_get_query (dag, coll, &params);
	
	g_mutex_unlock (dag->mutex);

	XMMS_DBG ("COLLECTIONS: query_ids with %s", query->str);

	/* Run the query */
	xmms_medialib_session_t *session = xmms_medialib_begin ();
	res = xmms_medialib_select (session, query->str, err);
	xmms_medialib_end (session);

	g_string_free (query, TRUE);

	/* FIXME: get an int list directly !   or Int vs UInt? */
	for (n = res; n; n = n->next) {
		xmms_object_cmd_value_t *cmdval = (xmms_object_cmd_value_t*)n->data;
		gint *v = (gint*)g_hash_table_lookup (cmdval->value.dict, "id");
		ids = g_list_prepend (ids, xmms_object_cmd_value_int_new (*v));
		g_free (n->data);
	}

	g_list_free (res);

	XMMS_DBG ("COLLECTIONS: done");

	return g_list_reverse (ids);
}


/** @} */



/** Free the playlist and other memory in the xmms_playlist_t
 *
 *  This will free all entries in the list!
 */
static void
xmms_collection_destroy (xmms_object_t *object)
{
	gint i;
	xmms_coll_dag_t *dag = (xmms_coll_dag_t *)object;

	g_return_if_fail (dag);

	g_mutex_free (dag->mutex);

	for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
		g_hash_table_destroy (dag->collrefs[i]);  /* dag is freed here */
	}

	xmms_ipc_object_unregister (XMMS_IPC_OBJECT_COLLECTION);
}

/** Validate the given collection against a DAG.
 *
 * @param dag  The collection DAG.
 * @param coll  The collection to validate.
 * @param name  The name under which the collection will be saved (NULL
 *              if none).
 * @param namespace  The namespace in which the collection will be
 *                   saved (NULL if none).
 * @returns  True if the collection is valid, false otherwise.
 */
static gboolean
xmms_collection_validate (xmms_coll_dag_t *dag, xmmsc_coll_t *coll,
                          gchar *save_name, gchar *save_namespace)
{
	guint num_operands = 0;
	xmmsc_coll_t *op;
	gchar *attr, *attr2;
	gboolean valid = TRUE;
	xmms_collection_namespace_id_t nsid;

	/* count operands */
	xmmsc_coll_operand_list_save (coll);

	xmmsc_coll_operand_list_first (coll);
	while (xmmsc_coll_operand_list_entry (coll, &op)) {
		num_operands++;
		xmmsc_coll_operand_list_next (coll);
	}

	xmmsc_coll_operand_list_restore (coll);


	/* analyse by type */
	switch (xmmsc_coll_get_type (coll)) {
	case XMMS_COLLECTION_TYPE_REFERENCE:
		/* no operand */
		if (num_operands > 0) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (ref)");
			return FALSE;
		}

		/* check if referenced collection exists */
		xmmsc_coll_attribute_get (coll, "reference", &attr);
		xmmsc_coll_attribute_get (coll, "namespace", &attr2);
		if (strcmp (attr, "All Media") != 0) {
			if (attr == NULL || attr2 == NULL) {
				XMMS_DBG("COLLECTIONS: validation, ref no attr");
				return FALSE;
			}

			nsid = xmms_collection_get_namespace_id (attr2);
			if (nsid == XMMS_COLLECTION_NSID_INVALID) {
				XMMS_DBG("COLLECTIONS: validation, ref invalid ns");
				return FALSE;
			}

			g_mutex_lock (dag->mutex);
			op = xmms_collection_get_pointer (dag, attr, nsid);
			if (op == NULL) {
				XMMS_DBG("COLLECTIONS: validation, ref invalid coll");
				g_mutex_unlock (dag->mutex);
				return FALSE;
			}

			/* check if the referenced coll references this one (loop!) */
			if (save_name && save_namespace &&
			    xmms_collection_has_reference_to (dag, op, save_name, save_namespace)) {
				XMMS_DBG("COLLECTIONS: validation, ref loop");
				g_mutex_unlock (dag->mutex);
				return FALSE;
			}
			g_mutex_unlock (dag->mutex);
		}
		break;

	case XMMS_COLLECTION_TYPE_UNION:
	case XMMS_COLLECTION_TYPE_INTERSECTION:
		/* need operand(s) */
		if (num_operands == 0) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (set)");
			return FALSE;
		}
		break;

	case XMMS_COLLECTION_TYPE_COMPLEMENT:
		/* one operand */
		if (num_operands != 1) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (complement)");
			return FALSE;
		}
		break;

	case XMMS_COLLECTION_TYPE_MATCH:
	case XMMS_COLLECTION_TYPE_CONTAINS:
	case XMMS_COLLECTION_TYPE_SMALLER:
	case XMMS_COLLECTION_TYPE_GREATER:
		/* one operand */
		if (num_operands != 1) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (filter)");
			return FALSE;
		}

		/* "field"/"value" attributes */
		/* with valid values */
		if (!xmmsc_coll_attribute_get (coll, "field", &attr)) {
			XMMS_DBG("COLLECTIONS: validation, field");
			return FALSE;
		}
		/* FIXME: valid fields?
		else if (...) {
			return FALSE;
		}
		*/

		if (!xmmsc_coll_attribute_get (coll, "value", &attr)) {
			XMMS_DBG("COLLECTIONS: validation, value");
			return FALSE;
		}
		break;
	
	case XMMS_COLLECTION_TYPE_IDLIST:
		/* no operand */
		if (num_operands > 0) {
			XMMS_DBG("COLLECTIONS: validation, num_operands (idlist)");
			return FALSE;
		}
		break;

	/* invalid type */
	default:
		XMMS_DBG("COLLECTIONS: validation, invalid type");
		return FALSE;
		break;
	}


	/* recurse in operands */
	xmmsc_coll_operand_list_save (coll);

	xmmsc_coll_operand_list_first (coll);
	while (xmmsc_coll_operand_list_entry (coll, &op) && valid) {
		if (!xmms_collection_validate (dag, op, save_name, save_namespace)) {
			valid = FALSE;
		}
		xmmsc_coll_operand_list_next (coll);
	}

	xmmsc_coll_operand_list_restore (coll);

	return valid;
}

/** Try to unreference a collection from a given namespace.
 *
 * @param dag  The collection DAG.
 * @param name  The name of the collection to remove.
 * @param nsid  The namespace in which to look for the collection.
 * @returns  TRUE if a collection was removed, FALSE otherwise.
 */
static gboolean
xmms_collection_unreference (xmms_coll_dag_t *dag, gchar *name, guint nsid)
{
	xmmsc_coll_t *existing;
	gboolean retval = FALSE;

	existing = g_hash_table_lookup (dag->collrefs[nsid], name);
	if (existing != NULL) {
		char *nsname = xmms_collection_get_namespace_string (nsid);

		/* Strip all references to the deleted coll, bind operator directly */
		coll_rebind_infos_t infos = { name, nsname, existing, NULL };
		xmms_collection_apply_to_all_collections (dag, strip_references, &infos);

		g_hash_table_remove (dag->collrefs[nsid], name);
		retval = TRUE;
	}

	return retval;
}
{'fraction_non_alphanumeric': 0.09921555341273358, 'fraction_numerical': 0.0022412759636183575, 'mean_word_length': 4.6570838861860535, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5793193', 'n_tokens_mistral': 14983, 'n_tokens_neox': 14193, 'n_words': 3587}