<commit_before>/*  XMMS2 - X Music Multiplexer System
 *  Copyright (C) 2003-2010 XMMS2 Team
 *
 *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This program 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
 *  General Public License for more details.
 */

#include "utils.h"
#include "status.h"
#include "compat.h"

#include "cli_infos.h"
#include "cli_cache.h"
#include "column_display.h"

static void coll_int_attribute_set (xmmsv_coll_t *coll, const char *key, gint value);
static xmmsv_coll_t *coll_make_reference (const char *name, xmmsc_coll_namespace_t ns);
static void coll_copy_attributes (const char *key, xmmsv_t *val, void *udata);
static void coll_print_attributes (const char *key, xmmsv_t *val, void *udata);
static xmmsv_coll_t *coll_copy_retype (xmmsv_coll_t *coll, xmmsv_coll_type_t type);

static void pl_print_config (xmmsv_coll_t *coll, const char *name);

static void id_print_info (xmmsc_result_t *res, guint id, gchar *source);

static gint compare_uint (gconstpointer a, gconstpointer b, gpointer userdata);

static void coll_dump (xmmsv_coll_t *coll, guint level);

typedef enum {
	IDLIST_CMD_NONE = 0,
	IDLIST_CMD_REHASH,
	IDLIST_CMD_REMOVE
} idlist_command_t;

typedef struct {
	cli_infos_t *infos;
	column_display_t *coldisp;
	const gchar *playlist;
	GArray *entries;
	gint inc;
	gint pos;
} pl_pos_udata_t;

/* Dumps a propdict on stdout */
static void
dict_dump (const gchar *source, xmmsv_t *val, void *udata)
{
	xmmsv_type_t type;

	const gchar **keyfilter = (const gchar **) udata;
	const gchar *key = (const gchar *) keyfilter[0];
	const gchar *filter = (const gchar *) keyfilter[1];

	if (filter && strcmp (filter, source) != 0) {
		return;
	}

	type = xmmsv_get_type (val);

	switch (type) {
	case XMMSV_TYPE_INT32:
	{
		gint value;
		xmmsv_get_int (val, &value);
		g_printf (_("[%s] %s = %u\n"), source, key, value);
		break;
	}
	case XMMSV_TYPE_STRING:
	{
		const gchar *value;
		xmmsv_get_string (val, &value);
		/* FIXME: special handling for url, guess charset, see common.c:print_entry */
		g_printf (_("[%s] %s = %s\n"), source, key, value);
		break;
	}
	case XMMSV_TYPE_LIST:
		g_printf (_("[%s] %s = <list>\n"), source, key);
		break;
	case XMMSV_TYPE_DICT:
		g_printf (_("[%s] %s = <dict>\n"), source, key);
		break;
	case XMMSV_TYPE_COLL:
		g_printf (_("[%s] %s = <coll>\n"), source, key);
		break;
	case XMMSV_TYPE_BIN:
		g_printf (_("[%s] %s = <bin>\n"), source, key);
		break;
	case XMMSV_TYPE_END:
		g_printf (_("[%s] %s = <end>\n"), source, key);
		break;
	case XMMSV_TYPE_NONE:
		g_printf (_("[%s] %s = <none>\n"), source, key);
		break;
	case XMMSV_TYPE_ERROR:
		g_printf (_("[%s] %s = <error>\n"), source, key);
		break;
	}
}

static void
propdict_dump (const gchar *key, xmmsv_t *src_dict, void *udata)
{
	const gchar *keyfilter[] = {key, udata};

	xmmsv_dict_foreach (src_dict, dict_dump, (void *) keyfilter);
}

/* Compare two uint like strcmp */
static gint
compare_uint (gconstpointer a, gconstpointer b, gpointer userdata)
{
	guint va = *((guint *)a);
	guint vb = *((guint *)b);

	if (va < vb) {
		return -1;
	} else if (va > vb) {
		return 1;
	} else {
		return 0;
	}
}


/* Dummy callback that resets the action status as finished. */
void
done (xmmsc_result_t *res, cli_infos_t *infos)
{
	const gchar *err;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (xmmsv_get_error (val, &err)) {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

void
tickle (xmmsc_result_t *res, cli_infos_t *infos)
{
	xmmsc_result_t *res2;

	const gchar *err;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		res2 = xmmsc_playback_tickle (infos->sync);
		xmmsc_result_wait (res2);
		done (res2, infos);
	} else {
		g_printf (_("Server error: %s\n"), err);
		cli_infos_loop_resume (infos);
	}

	xmmsc_result_unref (res);
}

void
list_plugins (cli_infos_t *infos, xmmsc_result_t *res)
{
	const gchar *name, *desc, *err;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;

		xmmsv_get_list_iter (val, &it);

		for (xmmsv_list_iter_first (it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {
			xmmsv_t *elem;

			xmmsv_list_iter_entry (it, &elem);
			xmmsv_dict_entry_get_string (elem, "shortname", &name);
			xmmsv_dict_entry_get_string (elem, "description", &desc);

			g_printf ("%s - %s\n", name, desc);
		}
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	xmmsc_result_unref (res);

	cli_infos_loop_resume (infos);
}

static void
print_server_stats (xmmsc_result_t *res)
{
	gint uptime;
	const gchar *version, *err;

	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_dict_entry_get_string (val, "version", &version);
		xmmsv_dict_entry_get_int (val, "uptime", &uptime);
		g_printf ("uptime = %d\n"
		          "version = %s\n", uptime, version);
	} else {
		g_printf ("Server error: %s\n", err);
	}
}

void
print_stats (cli_infos_t *infos, xmmsc_result_t *res)
{
	print_server_stats (res);

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

static void
print_config_entry (const gchar *confname, xmmsv_t *val, void *udata)
{
	xmmsv_type_t type;

	type = xmmsv_get_type (val);

	switch (type) {
	case XMMSV_TYPE_STRING:
	{
		const gchar *confval;
		xmmsv_get_string (val, &confval);
		g_printf ("%s = %s\n", confname, confval);
		break;
	}
	case XMMSV_TYPE_INT32:
	{
		int confval;
		xmmsv_get_int (val, &confval);
		g_printf ("%s = %d\n", confname, confval);
		break;
	}
	default:
		break;
	}
}

void
print_config (cli_infos_t *infos, gchar *confname)
{
	xmmsc_result_t *res;
	const gchar *confval;
	xmmsv_t *val;

	if (confname == NULL) {
		res = xmmsc_config_list_values (infos->sync);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		xmmsv_dict_foreach (val, print_config_entry, NULL);
	} else {
		res = xmmsc_config_get_value (infos->sync, confname);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		xmmsv_get_string (val, &confval);
		print_config_entry (confname, val, NULL);
	}

	cli_infos_loop_resume (infos);

	xmmsc_result_unref (res);
}

void
print_property (cli_infos_t *infos, xmmsc_result_t *res, guint id,
                gchar *source, gchar *property)
{
	if (property == NULL) {
		id_print_info (res, id, source);
	} else {
		/* FIXME(g): print if given an specific property */
	}

	cli_infos_loop_resume (infos);
}

/* Apply operation to an idlist */
static void
apply_ids (cli_infos_t *infos, xmmsc_result_t *res, idlist_command_t cmd)
{
	const gchar *err;
	xmmsc_result_t *cmdres;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;

		xmmsv_get_list_iter (val, &it);

		for (xmmsv_list_iter_first (it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {
			xmmsv_t *entry;
			gint32 id;

			xmmsv_list_iter_entry (it, &entry);

			if (xmmsv_get_int (entry, &id)) {
				switch (cmd) {
				case IDLIST_CMD_REHASH:
					cmdres = xmmsc_medialib_rehash (infos->sync, id);
					break;
				case IDLIST_CMD_REMOVE:
					cmdres = xmmsc_medialib_remove_entry (infos->sync, id);
					break;
				default:
					break;
				}
				xmmsc_result_wait (cmdres);
				xmmsc_result_unref (cmdres);
			}
		}
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);

	xmmsc_result_unref (res);
}

void
remove_ids (cli_infos_t *infos, xmmsc_result_t *res)
{
	apply_ids (infos, res, IDLIST_CMD_REMOVE);
}


static void
pos_remove_cb (gint pos, void *userdata)
{
	xmmsc_result_t *res;
	pl_pos_udata_t *pack = (pl_pos_udata_t *) userdata;

	res = xmmsc_playlist_remove_entry (pack->infos->sync, pack->playlist, pos);
	xmmsc_result_wait (res);
	xmmsc_result_unref (res);
}

void
positions_remove (cli_infos_t *infos, const gchar *playlist,
                  playlist_positions_t *positions)
{
	pl_pos_udata_t udata = { infos, NULL, playlist, NULL, 0, 0 };
	playlist_positions_foreach (positions, pos_remove_cb, FALSE, &udata);

	cli_infos_loop_resume (infos);
}

void
rehash_ids (cli_infos_t *infos, xmmsc_result_t *res)
{
	apply_ids (infos, res, IDLIST_CMD_REHASH);
}

static void
print_volume_entry (const gchar *key, xmmsv_t *val, void *udata)
{
	gchar *channel = udata;
	gint32 value;

	if (!udata || !strcmp (key, channel)) {
		xmmsv_get_int (val, &value);
		g_printf (_("%s = %u\n"), key, value);
	}
}

void
print_volume (xmmsc_result_t *res, cli_infos_t *infos, gchar *channel)
{
	xmmsv_t *val;
	const gchar *err;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_dict_foreach (val, print_volume_entry, channel);
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);

	xmmsc_result_unref (res);
}

static void
dict_keys (const gchar *key, xmmsv_t *val, void *udata)
{
	GList **list = udata;

	*list = g_list_prepend (*list, g_strdup (key));
}

void
set_volume (cli_infos_t *infos, gchar *channel, gint volume)
{
	xmmsc_result_t *res;
	xmmsv_t *val;
	GList *it, *channels = NULL;

	if (!channel) {
		/* get all channels */
		res = xmmsc_playback_volume_get (infos->sync);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		xmmsv_dict_foreach (val, dict_keys, &channels);
		xmmsc_result_unref (res);
	} else {
		channels = g_list_prepend (channels, g_strdup (channel));
	}

	/* set volumes for channels in list */
	for (it = g_list_first (channels); it != NULL; it = g_list_next (it)) {
		res = xmmsc_playback_volume_set (infos->sync, it->data, volume);
		xmmsc_result_wait (res);
		xmmsc_result_unref (res);

		/* free channel string */
		g_free (it->data);
	}

	g_list_free (channels);

	cli_infos_loop_resume (infos);
}

void
status_mode (cli_infos_t *infos, gchar *format, gint refresh)
{
	status_entry_t *status;

	status = status_init (format, refresh);

	if (refresh > 0) {
		g_printf (_("\n"
		            "   (n) next song\n"
		            "   (p) previous song\n"
		            "   (SPACE) toggle playback\n"
		            "   (ENTER) exit status mode\n\n"));
		cli_infos_status_mode (infos, status);
	} else {
		status_update_all (infos, status);
		status_print_entry (status);
		status_free (status);
		cli_infos_loop_resume (infos);
	}
}

static void
id_print_info (xmmsc_result_t *res, guint id, gchar *source)
{
	xmmsv_t *val;
	const gchar *err;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_dict_foreach (val, propdict_dump, source);
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	xmmsc_result_unref (res);
}

void
list_print_info (xmmsc_result_t *res, cli_infos_t *infos)
{
	xmmsc_result_t *infores = NULL;
	xmmsv_t *val;
	const gchar *err;
	gint32 id;
	gboolean first = true;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;

		xmmsv_get_list_iter (val, &it);
		while (xmmsv_list_iter_valid (it)) {
			xmmsv_t *entry;

			xmmsv_list_iter_entry (it, &entry);
			if (xmmsv_get_int (entry, &id)) {
				infores = xmmsc_medialib_get_info (infos->sync, id);
				xmmsc_result_wait (infores);

				if (!first) {
					g_printf ("\n");
				} else {
					first = false;
				}
				id_print_info (infores, id, NULL);
			}
			xmmsv_list_iter_next (it);
		}

	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

static void
pos_print_info_cb (gint pos, void *userdata)
{
	xmmsc_result_t *infores;
	pl_pos_udata_t *pack = (pl_pos_udata_t *) userdata;
	guint id;

	/* Skip if outside of playlist */
	if (pos >= pack->infos->cache->active_playlist->len) {
		return;
	}

	id = g_array_index (pack->infos->cache->active_playlist, guint, pos);

	infores = xmmsc_medialib_get_info (pack->infos->sync, id);
	xmmsc_result_wait (infores);

	/* Do not prepend newline before the first entry */
	if (pack->inc > 0) {
		g_printf ("\n");
	} else {
		pack->inc++;
	}
	id_print_info (infores, id, NULL);
}

void
positions_print_info (cli_infos_t *infos, playlist_positions_t *positions)
{
	pl_pos_udata_t udata = { infos, NULL, NULL, NULL, 0, 0 };
	playlist_positions_foreach (positions, pos_print_info_cb, TRUE, &udata);

	cli_infos_loop_resume (infos);
}

void
enrich_mediainfo (xmmsv_t *val)
{
	if (!xmmsv_dict_has_key (val, "title") && xmmsv_dict_has_key (val, "url")) {
		/* First decode the URL encoding */
		xmmsv_t *tmp, *v, *urlv;
		gchar *url = NULL;
		gchar *filename = NULL;
		const unsigned char *burl;
		unsigned int blen;

		xmmsv_dict_get (val, "url", &v);

		tmp = xmmsv_decode_url (v);
		if (tmp && xmmsv_get_bin (tmp, &burl, &blen)) {
			url = g_malloc (blen + 1);
			memcpy (url, burl, blen);
			url[blen] = 0;
			xmmsv_unref (tmp);
			filename = strrchr (url, '/');
			if (!filename || !filename[1]) {
				filename = url;
			} else {
				filename = filename + 1;
			}
		}

		/* Let's see if the result is valid utf-8. This must be done
		 * since we don't know the charset of the binary string */
		if (filename && g_utf8_validate (filename, -1, NULL)) {
			/* If it's valid utf-8 we don't have any problem just
			 * printing it to the screen
			 */
			urlv = xmmsv_new_string (filename);
		} else if (filename) {
			/* Not valid utf-8 :-( We make a valid guess here that
			 * the string when it was encoded with URL it was in the
			 * same charset as we have on the terminal now.
			 *
			 * THIS MIGHT BE WRONG since different clients can have
			 * different charsets and DIFFERENT computers most likely
			 * have it.
			 */
			gchar *tmp2 = g_locale_to_utf8 (filename, -1, NULL, NULL, NULL);
			urlv = xmmsv_new_string (tmp2);
			g_free (tmp2);
		} else {
			/* Decoding the URL failed for some reason. That's not good. */
			urlv = xmmsv_new_string (_("?"));
		}

		xmmsv_dict_set (val, "title", urlv);
		xmmsv_unref (urlv);
		g_free (url);
	}
}

static void
id_coldisp_print_info (cli_infos_t *infos, column_display_t *coldisp, guint id)
{
	xmmsc_result_t *infores;
	xmmsv_t *info;

	infores = xmmsc_medialib_get_info (infos->sync, id);
	xmmsc_result_wait (infores);
	info = xmmsv_propdict_to_dict (xmmsc_result_get_value (infores), NULL);
	enrich_mediainfo (info);
	column_display_print (coldisp, info);

	xmmsc_result_unref (infores);
	xmmsv_unref (info);
}

static void
pos_print_row_cb (gint pos, void *userdata)
{
	pl_pos_udata_t *pack = (pl_pos_udata_t *) userdata;
	guint id;

	if (pos >= pack->entries->len) {
		return;
	}

	id = g_array_index (pack->entries, guint, pos);
	column_display_set_position (pack->coldisp, pos);
	id_coldisp_print_info (pack->infos, pack->coldisp, id);
}

void
positions_print_list (xmmsc_result_t *res, playlist_positions_t *positions,
                      column_display_t *coldisp, gboolean is_search)
{
	cli_infos_t *infos = column_display_infos_get (coldisp);
	pl_pos_udata_t udata = { infos, coldisp, NULL, NULL, 0, 0};
	xmmsv_t *val;
	GArray *entries;

	gint32 id;
	const gchar *err;

	/* FIXME: separate function or merge
	   with list_print_row (lot of if(positions))? */
	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;
		column_display_prepare (coldisp);

		if (is_search) {
			column_display_print_header (coldisp);
		}

		entries = g_array_sized_new (FALSE, FALSE, sizeof (guint),
		                             xmmsv_list_get_size (val));

		for (xmmsv_get_list_iter (val, &it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {
			xmmsv_t *entry;
			xmmsv_list_iter_entry (it, &entry);
			if (xmmsv_get_int (entry, &id)) {
				g_array_append_val (entries, id);
			}
		}

		udata.entries = entries;
		playlist_positions_foreach (positions, pos_print_row_cb, TRUE, &udata);

	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	if (is_search) {
		column_display_print_footer (coldisp);
	} else {
		g_printf ("\n");
		column_display_print_footer_totaltime (coldisp);
	}

	column_display_free (coldisp);
	g_array_free (entries, TRUE);

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

/* Returned tree must be freed by the caller */
static GTree *
matching_ids_tree (xmmsc_result_t *matching)
{
	xmmsv_t *val;
	gint32 id;
	GTree *list = NULL;
	const gchar *err;

	val = xmmsc_result_get_value (matching);

	if (xmmsv_get_error (val, &err) || !xmmsv_is_type (val, XMMSV_TYPE_LIST)) {
		g_printf (_("Error retrieving the media matching the pattern!\n"));
	} else {
		xmmsv_list_iter_t *it;
		xmmsv_t *entry;

		list = g_tree_new_full (compare_uint, NULL, g_free, NULL);

		xmmsv_get_list_iter (val, &it);
		for (xmmsv_list_iter_first (it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {

			xmmsv_list_iter_entry (it, &entry);

			if (xmmsv_get_int (entry, &id)) {
				guint *tid;
				tid = g_new (guint, 1);
				*tid = id;
				g_tree_insert (list, tid, tid);
			}
		}
	}

	return list;
}

void
list_print_row (xmmsc_result_t *res, xmmsv_coll_t *filter,
                column_display_t *coldisp, gboolean is_search)
{
	/* FIXME: w00t at code copy-paste, please modularize */
	cli_infos_t *infos = column_display_infos_get (coldisp);
	xmmsv_t *val;
	GTree *list = NULL;

	const gchar *err;
	gint32 id;
	gint i = 0;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;
		column_display_prepare (coldisp);

		if (filter != NULL) {
			xmmsc_result_t *filres;
			filres = xmmsc_coll_query_ids (infos->sync, filter, NULL, 0, 0);
			xmmsc_result_wait (filres);
			if ((list = matching_ids_tree (filres)) == NULL) {
				goto finish;
			}
		}

		if (is_search) {
			column_display_print_header (coldisp);
		}

		xmmsv_get_list_iter (val, &it);
		while (xmmsv_list_iter_valid (it)) {
			xmmsv_t *entry;
			xmmsv_list_iter_entry (it, &entry);
			if (xmmsv_get_int (entry, &id) &&
			    (!list || g_tree_lookup (list, &id) != NULL)) {
				column_display_set_position (coldisp, i);
				id_coldisp_print_info (infos, coldisp, id);
			}
			xmmsv_list_iter_next (it);
			i++;
		}

	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	if (is_search) {
		column_display_print_footer (coldisp);
	} else {
		g_printf ("\n");
		column_display_print_footer_totaltime (coldisp);
	}

finish:

	if (list) {
		g_tree_destroy (list);
	}
	column_display_free (coldisp);

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

void
coll_rename (cli_infos_t *infos, gchar *oldname, gchar *newname,
             xmmsc_coll_namespace_t ns, gboolean force)
{
	xmmsc_result_t *res;

	if (force) {
		res = xmmsc_coll_remove (infos->sync, newname, ns);
		xmmsc_result_wait (res);
		/* FIXME(g): check something? */
		xmmsc_result_unref (res);
	}

	res = xmmsc_coll_rename (infos->sync, oldname, newname, ns);
	xmmsc_result_wait (res);
	done (res, infos);
}

void
coll_save (cli_infos_t *infos, xmmsv_coll_t *coll,
           xmmsc_coll_namespace_t ns, gchar *name, gboolean force)
{
	xmmsc_result_t *res;
	xmmsv_t *val;
	gboolean save = TRUE;

	if (!force) {
		xmmsv_coll_t *exists;
		res = xmmsc_coll_get (infos->sync, name, ns);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		if (xmmsv_get_coll (val, &exists)) {
			g_printf (_("Error: A collection already exists "
			            "with the target name!\n"));
			save = FALSE;
		}
		xmmsc_result_unref (res);
	}

	if (save) {
		res = xmmsc_coll_save (infos->sync, coll, name, ns);
		xmmsc_result_wait (res);
		done (res, infos);
	} else {
		cli_infos_loop_resume (infos);
	}
}

/* (from src/clients/cli/common.c) */
static void
print_info (const gchar *fmt, ...)
{
	gchar buf[8096];
	va_list ap;

	va_start (ap, fmt);
	g_vsnprintf (buf, 8096, fmt, ap);
	va_end (ap);

	g_printf ("%s\n", buf);
}

/* Produce a GString from the idlist of the collection.
   (must be freed manually!)
   (from src/clients/cli/cmd_coll.c) */
static GString *
coll_idlist_to_string (xmmsv_coll_t *coll)
{
	gint i;
	const gint32 *idlist;
	GString *s;

	s = g_string_new ("(");

	idlist = xmmsc_coll_get_idlist (coll);
	for (i = 0; idlist[i] != 0; ++i) {
		if (i > 0) {
			g_string_append (s, ", ");
		}
		g_string_append_printf (s, "%d", idlist[i]);
	}
	g_string_append_c (s, ')');

	return s;
}

/* (from src/clients/cli/cmd_coll.c) */
static void
coll_dump_list (xmmsv_t *list, unsigned int level)
{
	xmmsv_list_iter_t *it;
	xmmsv_coll_t *operand;
	xmmsv_t *v;

	xmmsv_get_list_iter (list, &it);
	while (xmmsv_list_iter_entry (it, &v)) {
		if (xmmsv_get_coll (v, &operand)) {
			coll_dump (operand, level);
		}
		xmmsv_list_iter_next (it);
	}

}<commit_msg>OTHER: NYCLI: Remove usage of deprecated get_idlist.<commit_after>/*  XMMS2 - X Music Multiplexer System
 *  Copyright (C) 2003-2010 XMMS2 Team
 *
 *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This program 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
 *  General Public License for more details.
 */

#include "utils.h"
#include "status.h"
#include "compat.h"

#include "cli_infos.h"
#include "cli_cache.h"
#include "column_display.h"

static void coll_int_attribute_set (xmmsv_coll_t *coll, const char *key, gint value);
static xmmsv_coll_t *coll_make_reference (const char *name, xmmsc_coll_namespace_t ns);
static void coll_copy_attributes (const char *key, xmmsv_t *val, void *udata);
static void coll_print_attributes (const char *key, xmmsv_t *val, void *udata);
static xmmsv_coll_t *coll_copy_retype (xmmsv_coll_t *coll, xmmsv_coll_type_t type);

static void pl_print_config (xmmsv_coll_t *coll, const char *name);

static void id_print_info (xmmsc_result_t *res, guint id, gchar *source);

static gint compare_uint (gconstpointer a, gconstpointer b, gpointer userdata);

static void coll_dump (xmmsv_coll_t *coll, guint level);

typedef enum {
	IDLIST_CMD_NONE = 0,
	IDLIST_CMD_REHASH,
	IDLIST_CMD_REMOVE
} idlist_command_t;

typedef struct {
	cli_infos_t *infos;
	column_display_t *coldisp;
	const gchar *playlist;
	GArray *entries;
	gint inc;
	gint pos;
} pl_pos_udata_t;

/* Dumps a propdict on stdout */
static void
dict_dump (const gchar *source, xmmsv_t *val, void *udata)
{
	xmmsv_type_t type;

	const gchar **keyfilter = (const gchar **) udata;
	const gchar *key = (const gchar *) keyfilter[0];
	const gchar *filter = (const gchar *) keyfilter[1];

	if (filter && strcmp (filter, source) != 0) {
		return;
	}

	type = xmmsv_get_type (val);

	switch (type) {
	case XMMSV_TYPE_INT32:
	{
		gint value;
		xmmsv_get_int (val, &value);
		g_printf (_("[%s] %s = %u\n"), source, key, value);
		break;
	}
	case XMMSV_TYPE_STRING:
	{
		const gchar *value;
		xmmsv_get_string (val, &value);
		/* FIXME: special handling for url, guess charset, see common.c:print_entry */
		g_printf (_("[%s] %s = %s\n"), source, key, value);
		break;
	}
	case XMMSV_TYPE_LIST:
		g_printf (_("[%s] %s = <list>\n"), source, key);
		break;
	case XMMSV_TYPE_DICT:
		g_printf (_("[%s] %s = <dict>\n"), source, key);
		break;
	case XMMSV_TYPE_COLL:
		g_printf (_("[%s] %s = <coll>\n"), source, key);
		break;
	case XMMSV_TYPE_BIN:
		g_printf (_("[%s] %s = <bin>\n"), source, key);
		break;
	case XMMSV_TYPE_END:
		g_printf (_("[%s] %s = <end>\n"), source, key);
		break;
	case XMMSV_TYPE_NONE:
		g_printf (_("[%s] %s = <none>\n"), source, key);
		break;
	case XMMSV_TYPE_ERROR:
		g_printf (_("[%s] %s = <error>\n"), source, key);
		break;
	}
}

static void
propdict_dump (const gchar *key, xmmsv_t *src_dict, void *udata)
{
	const gchar *keyfilter[] = {key, udata};

	xmmsv_dict_foreach (src_dict, dict_dump, (void *) keyfilter);
}

/* Compare two uint like strcmp */
static gint
compare_uint (gconstpointer a, gconstpointer b, gpointer userdata)
{
	guint va = *((guint *)a);
	guint vb = *((guint *)b);

	if (va < vb) {
		return -1;
	} else if (va > vb) {
		return 1;
	} else {
		return 0;
	}
}


/* Dummy callback that resets the action status as finished. */
void
done (xmmsc_result_t *res, cli_infos_t *infos)
{
	const gchar *err;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (xmmsv_get_error (val, &err)) {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

void
tickle (xmmsc_result_t *res, cli_infos_t *infos)
{
	xmmsc_result_t *res2;

	const gchar *err;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		res2 = xmmsc_playback_tickle (infos->sync);
		xmmsc_result_wait (res2);
		done (res2, infos);
	} else {
		g_printf (_("Server error: %s\n"), err);
		cli_infos_loop_resume (infos);
	}

	xmmsc_result_unref (res);
}

void
list_plugins (cli_infos_t *infos, xmmsc_result_t *res)
{
	const gchar *name, *desc, *err;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;

		xmmsv_get_list_iter (val, &it);

		for (xmmsv_list_iter_first (it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {
			xmmsv_t *elem;

			xmmsv_list_iter_entry (it, &elem);
			xmmsv_dict_entry_get_string (elem, "shortname", &name);
			xmmsv_dict_entry_get_string (elem, "description", &desc);

			g_printf ("%s - %s\n", name, desc);
		}
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	xmmsc_result_unref (res);

	cli_infos_loop_resume (infos);
}

static void
print_server_stats (xmmsc_result_t *res)
{
	gint uptime;
	const gchar *version, *err;

	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_dict_entry_get_string (val, "version", &version);
		xmmsv_dict_entry_get_int (val, "uptime", &uptime);
		g_printf ("uptime = %d\n"
		          "version = %s\n", uptime, version);
	} else {
		g_printf ("Server error: %s\n", err);
	}
}

void
print_stats (cli_infos_t *infos, xmmsc_result_t *res)
{
	print_server_stats (res);

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

static void
print_config_entry (const gchar *confname, xmmsv_t *val, void *udata)
{
	xmmsv_type_t type;

	type = xmmsv_get_type (val);

	switch (type) {
	case XMMSV_TYPE_STRING:
	{
		const gchar *confval;
		xmmsv_get_string (val, &confval);
		g_printf ("%s = %s\n", confname, confval);
		break;
	}
	case XMMSV_TYPE_INT32:
	{
		int confval;
		xmmsv_get_int (val, &confval);
		g_printf ("%s = %d\n", confname, confval);
		break;
	}
	default:
		break;
	}
}

void
print_config (cli_infos_t *infos, gchar *confname)
{
	xmmsc_result_t *res;
	const gchar *confval;
	xmmsv_t *val;

	if (confname == NULL) {
		res = xmmsc_config_list_values (infos->sync);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		xmmsv_dict_foreach (val, print_config_entry, NULL);
	} else {
		res = xmmsc_config_get_value (infos->sync, confname);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		xmmsv_get_string (val, &confval);
		print_config_entry (confname, val, NULL);
	}

	cli_infos_loop_resume (infos);

	xmmsc_result_unref (res);
}

void
print_property (cli_infos_t *infos, xmmsc_result_t *res, guint id,
                gchar *source, gchar *property)
{
	if (property == NULL) {
		id_print_info (res, id, source);
	} else {
		/* FIXME(g): print if given an specific property */
	}

	cli_infos_loop_resume (infos);
}

/* Apply operation to an idlist */
static void
apply_ids (cli_infos_t *infos, xmmsc_result_t *res, idlist_command_t cmd)
{
	const gchar *err;
	xmmsc_result_t *cmdres;
	xmmsv_t *val;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;

		xmmsv_get_list_iter (val, &it);

		for (xmmsv_list_iter_first (it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {
			xmmsv_t *entry;
			gint32 id;

			xmmsv_list_iter_entry (it, &entry);

			if (xmmsv_get_int (entry, &id)) {
				switch (cmd) {
				case IDLIST_CMD_REHASH:
					cmdres = xmmsc_medialib_rehash (infos->sync, id);
					break;
				case IDLIST_CMD_REMOVE:
					cmdres = xmmsc_medialib_remove_entry (infos->sync, id);
					break;
				default:
					break;
				}
				xmmsc_result_wait (cmdres);
				xmmsc_result_unref (cmdres);
			}
		}
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);

	xmmsc_result_unref (res);
}

void
remove_ids (cli_infos_t *infos, xmmsc_result_t *res)
{
	apply_ids (infos, res, IDLIST_CMD_REMOVE);
}


static void
pos_remove_cb (gint pos, void *userdata)
{
	xmmsc_result_t *res;
	pl_pos_udata_t *pack = (pl_pos_udata_t *) userdata;

	res = xmmsc_playlist_remove_entry (pack->infos->sync, pack->playlist, pos);
	xmmsc_result_wait (res);
	xmmsc_result_unref (res);
}

void
positions_remove (cli_infos_t *infos, const gchar *playlist,
                  playlist_positions_t *positions)
{
	pl_pos_udata_t udata = { infos, NULL, playlist, NULL, 0, 0 };
	playlist_positions_foreach (positions, pos_remove_cb, FALSE, &udata);

	cli_infos_loop_resume (infos);
}

void
rehash_ids (cli_infos_t *infos, xmmsc_result_t *res)
{
	apply_ids (infos, res, IDLIST_CMD_REHASH);
}

static void
print_volume_entry (const gchar *key, xmmsv_t *val, void *udata)
{
	gchar *channel = udata;
	gint32 value;

	if (!udata || !strcmp (key, channel)) {
		xmmsv_get_int (val, &value);
		g_printf (_("%s = %u\n"), key, value);
	}
}

void
print_volume (xmmsc_result_t *res, cli_infos_t *infos, gchar *channel)
{
	xmmsv_t *val;
	const gchar *err;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_dict_foreach (val, print_volume_entry, channel);
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);

	xmmsc_result_unref (res);
}

static void
dict_keys (const gchar *key, xmmsv_t *val, void *udata)
{
	GList **list = udata;

	*list = g_list_prepend (*list, g_strdup (key));
}

void
set_volume (cli_infos_t *infos, gchar *channel, gint volume)
{
	xmmsc_result_t *res;
	xmmsv_t *val;
	GList *it, *channels = NULL;

	if (!channel) {
		/* get all channels */
		res = xmmsc_playback_volume_get (infos->sync);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		xmmsv_dict_foreach (val, dict_keys, &channels);
		xmmsc_result_unref (res);
	} else {
		channels = g_list_prepend (channels, g_strdup (channel));
	}

	/* set volumes for channels in list */
	for (it = g_list_first (channels); it != NULL; it = g_list_next (it)) {
		res = xmmsc_playback_volume_set (infos->sync, it->data, volume);
		xmmsc_result_wait (res);
		xmmsc_result_unref (res);

		/* free channel string */
		g_free (it->data);
	}

	g_list_free (channels);

	cli_infos_loop_resume (infos);
}

void
status_mode (cli_infos_t *infos, gchar *format, gint refresh)
{
	status_entry_t *status;

	status = status_init (format, refresh);

	if (refresh > 0) {
		g_printf (_("\n"
		            "   (n) next song\n"
		            "   (p) previous song\n"
		            "   (SPACE) toggle playback\n"
		            "   (ENTER) exit status mode\n\n"));
		cli_infos_status_mode (infos, status);
	} else {
		status_update_all (infos, status);
		status_print_entry (status);
		status_free (status);
		cli_infos_loop_resume (infos);
	}
}

static void
id_print_info (xmmsc_result_t *res, guint id, gchar *source)
{
	xmmsv_t *val;
	const gchar *err;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_dict_foreach (val, propdict_dump, source);
	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	xmmsc_result_unref (res);
}

void
list_print_info (xmmsc_result_t *res, cli_infos_t *infos)
{
	xmmsc_result_t *infores = NULL;
	xmmsv_t *val;
	const gchar *err;
	gint32 id;
	gboolean first = true;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;

		xmmsv_get_list_iter (val, &it);
		while (xmmsv_list_iter_valid (it)) {
			xmmsv_t *entry;

			xmmsv_list_iter_entry (it, &entry);
			if (xmmsv_get_int (entry, &id)) {
				infores = xmmsc_medialib_get_info (infos->sync, id);
				xmmsc_result_wait (infores);

				if (!first) {
					g_printf ("\n");
				} else {
					first = false;
				}
				id_print_info (infores, id, NULL);
			}
			xmmsv_list_iter_next (it);
		}

	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

static void
pos_print_info_cb (gint pos, void *userdata)
{
	xmmsc_result_t *infores;
	pl_pos_udata_t *pack = (pl_pos_udata_t *) userdata;
	guint id;

	/* Skip if outside of playlist */
	if (pos >= pack->infos->cache->active_playlist->len) {
		return;
	}

	id = g_array_index (pack->infos->cache->active_playlist, guint, pos);

	infores = xmmsc_medialib_get_info (pack->infos->sync, id);
	xmmsc_result_wait (infores);

	/* Do not prepend newline before the first entry */
	if (pack->inc > 0) {
		g_printf ("\n");
	} else {
		pack->inc++;
	}
	id_print_info (infores, id, NULL);
}

void
positions_print_info (cli_infos_t *infos, playlist_positions_t *positions)
{
	pl_pos_udata_t udata = { infos, NULL, NULL, NULL, 0, 0 };
	playlist_positions_foreach (positions, pos_print_info_cb, TRUE, &udata);

	cli_infos_loop_resume (infos);
}

void
enrich_mediainfo (xmmsv_t *val)
{
	if (!xmmsv_dict_has_key (val, "title") && xmmsv_dict_has_key (val, "url")) {
		/* First decode the URL encoding */
		xmmsv_t *tmp, *v, *urlv;
		gchar *url = NULL;
		gchar *filename = NULL;
		const unsigned char *burl;
		unsigned int blen;

		xmmsv_dict_get (val, "url", &v);

		tmp = xmmsv_decode_url (v);
		if (tmp && xmmsv_get_bin (tmp, &burl, &blen)) {
			url = g_malloc (blen + 1);
			memcpy (url, burl, blen);
			url[blen] = 0;
			xmmsv_unref (tmp);
			filename = strrchr (url, '/');
			if (!filename || !filename[1]) {
				filename = url;
			} else {
				filename = filename + 1;
			}
		}

		/* Let's see if the result is valid utf-8. This must be done
		 * since we don't know the charset of the binary string */
		if (filename && g_utf8_validate (filename, -1, NULL)) {
			/* If it's valid utf-8 we don't have any problem just
			 * printing it to the screen
			 */
			urlv = xmmsv_new_string (filename);
		} else if (filename) {
			/* Not valid utf-8 :-( We make a valid guess here that
			 * the string when it was encoded with URL it was in the
			 * same charset as we have on the terminal now.
			 *
			 * THIS MIGHT BE WRONG since different clients can have
			 * different charsets and DIFFERENT computers most likely
			 * have it.
			 */
			gchar *tmp2 = g_locale_to_utf8 (filename, -1, NULL, NULL, NULL);
			urlv = xmmsv_new_string (tmp2);
			g_free (tmp2);
		} else {
			/* Decoding the URL failed for some reason. That's not good. */
			urlv = xmmsv_new_string (_("?"));
		}

		xmmsv_dict_set (val, "title", urlv);
		xmmsv_unref (urlv);
		g_free (url);
	}
}

static void
id_coldisp_print_info (cli_infos_t *infos, column_display_t *coldisp, guint id)
{
	xmmsc_result_t *infores;
	xmmsv_t *info;

	infores = xmmsc_medialib_get_info (infos->sync, id);
	xmmsc_result_wait (infores);
	info = xmmsv_propdict_to_dict (xmmsc_result_get_value (infores), NULL);
	enrich_mediainfo (info);
	column_display_print (coldisp, info);

	xmmsc_result_unref (infores);
	xmmsv_unref (info);
}

static void
pos_print_row_cb (gint pos, void *userdata)
{
	pl_pos_udata_t *pack = (pl_pos_udata_t *) userdata;
	guint id;

	if (pos >= pack->entries->len) {
		return;
	}

	id = g_array_index (pack->entries, guint, pos);
	column_display_set_position (pack->coldisp, pos);
	id_coldisp_print_info (pack->infos, pack->coldisp, id);
}

void
positions_print_list (xmmsc_result_t *res, playlist_positions_t *positions,
                      column_display_t *coldisp, gboolean is_search)
{
	cli_infos_t *infos = column_display_infos_get (coldisp);
	pl_pos_udata_t udata = { infos, coldisp, NULL, NULL, 0, 0};
	xmmsv_t *val;
	GArray *entries;

	gint32 id;
	const gchar *err;

	/* FIXME: separate function or merge
	   with list_print_row (lot of if(positions))? */
	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;
		column_display_prepare (coldisp);

		if (is_search) {
			column_display_print_header (coldisp);
		}

		entries = g_array_sized_new (FALSE, FALSE, sizeof (guint),
		                             xmmsv_list_get_size (val));

		for (xmmsv_get_list_iter (val, &it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {
			xmmsv_t *entry;
			xmmsv_list_iter_entry (it, &entry);
			if (xmmsv_get_int (entry, &id)) {
				g_array_append_val (entries, id);
			}
		}

		udata.entries = entries;
		playlist_positions_foreach (positions, pos_print_row_cb, TRUE, &udata);

	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	if (is_search) {
		column_display_print_footer (coldisp);
	} else {
		g_printf ("\n");
		column_display_print_footer_totaltime (coldisp);
	}

	column_display_free (coldisp);
	g_array_free (entries, TRUE);

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

/* Returned tree must be freed by the caller */
static GTree *
matching_ids_tree (xmmsc_result_t *matching)
{
	xmmsv_t *val;
	gint32 id;
	GTree *list = NULL;
	const gchar *err;

	val = xmmsc_result_get_value (matching);

	if (xmmsv_get_error (val, &err) || !xmmsv_is_type (val, XMMSV_TYPE_LIST)) {
		g_printf (_("Error retrieving the media matching the pattern!\n"));
	} else {
		xmmsv_list_iter_t *it;
		xmmsv_t *entry;

		list = g_tree_new_full (compare_uint, NULL, g_free, NULL);

		xmmsv_get_list_iter (val, &it);
		for (xmmsv_list_iter_first (it);
		     xmmsv_list_iter_valid (it);
		     xmmsv_list_iter_next (it)) {

			xmmsv_list_iter_entry (it, &entry);

			if (xmmsv_get_int (entry, &id)) {
				guint *tid;
				tid = g_new (guint, 1);
				*tid = id;
				g_tree_insert (list, tid, tid);
			}
		}
	}

	return list;
}

void
list_print_row (xmmsc_result_t *res, xmmsv_coll_t *filter,
                column_display_t *coldisp, gboolean is_search)
{
	/* FIXME: w00t at code copy-paste, please modularize */
	cli_infos_t *infos = column_display_infos_get (coldisp);
	xmmsv_t *val;
	GTree *list = NULL;

	const gchar *err;
	gint32 id;
	gint i = 0;

	val = xmmsc_result_get_value (res);

	if (!xmmsv_get_error (val, &err)) {
		xmmsv_list_iter_t *it;
		column_display_prepare (coldisp);

		if (filter != NULL) {
			xmmsc_result_t *filres;
			filres = xmmsc_coll_query_ids (infos->sync, filter, NULL, 0, 0);
			xmmsc_result_wait (filres);
			if ((list = matching_ids_tree (filres)) == NULL) {
				goto finish;
			}
		}

		if (is_search) {
			column_display_print_header (coldisp);
		}

		xmmsv_get_list_iter (val, &it);
		while (xmmsv_list_iter_valid (it)) {
			xmmsv_t *entry;
			xmmsv_list_iter_entry (it, &entry);
			if (xmmsv_get_int (entry, &id) &&
			    (!list || g_tree_lookup (list, &id) != NULL)) {
				column_display_set_position (coldisp, i);
				id_coldisp_print_info (infos, coldisp, id);
			}
			xmmsv_list_iter_next (it);
			i++;
		}

	} else {
		g_printf (_("Server error: %s\n"), err);
	}

	if (is_search) {
		column_display_print_footer (coldisp);
	} else {
		g_printf ("\n");
		column_display_print_footer_totaltime (coldisp);
	}

finish:

	if (list) {
		g_tree_destroy (list);
	}
	column_display_free (coldisp);

	cli_infos_loop_resume (infos);
	xmmsc_result_unref (res);
}

void
coll_rename (cli_infos_t *infos, gchar *oldname, gchar *newname,
             xmmsc_coll_namespace_t ns, gboolean force)
{
	xmmsc_result_t *res;

	if (force) {
		res = xmmsc_coll_remove (infos->sync, newname, ns);
		xmmsc_result_wait (res);
		/* FIXME(g): check something? */
		xmmsc_result_unref (res);
	}

	res = xmmsc_coll_rename (infos->sync, oldname, newname, ns);
	xmmsc_result_wait (res);
	done (res, infos);
}

void
coll_save (cli_infos_t *infos, xmmsv_coll_t *coll,
           xmmsc_coll_namespace_t ns, gchar *name, gboolean force)
{
	xmmsc_result_t *res;
	xmmsv_t *val;
	gboolean save = TRUE;

	if (!force) {
		xmmsv_coll_t *exists;
		res = xmmsc_coll_get (infos->sync, name, ns);
		xmmsc_result_wait (res);
		val = xmmsc_result_get_value (res);
		if (xmmsv_get_coll (val, &exists)) {
			g_printf (_("Error: A collection already exists "
			            "with the target name!\n"));
			save = FALSE;
		}
		xmmsc_result_unref (res);
	}

	if (save) {
		res = xmmsc_coll_save (infos->sync, coll, name, ns);
		xmmsc_result_wait (res);
		done (res, infos);
	} else {
		cli_infos_loop_resume (infos);
	}
}

/* (from src/clients/cli/common.c) */
static void
print_info (const gchar *fmt, ...)
{
	gchar buf[8096];
	va_list ap;

	va_start (ap, fmt);
	g_vsnprintf (buf, 8096, fmt, ap);
	va_end (ap);

	g_printf ("%s\n", buf);
}

/* Produce a GString from the idlist of the collection.
   (must be freed manually!)
   (from src/clients/cli/cmd_coll.c) */
static GString *
coll_idlist_to_string (xmmsv_coll_t *coll)
{
	xmmsv_list_iter_t *it;
	gint32 entry;
	gboolean first = TRUE;
	GString *s;

	s = g_string_new ("(");

	xmmsv_get_list_iter (xmmsv_coll_idlist_get (coll), &it);
	for (xmmsv_list_iter_first (it);
	     xmmsv_list_iter_valid (it);
	     xmmsv_list_iter_next (it)) {

		if (first) {
			first = FALSE;
		} else {
			g_string_append (s, ", ");
		}

		xmmsv_list_iter_entry_int (it, &entry);
		g_string_append_printf (s, "%d", entry);
	}
	xmmsv_list_iter_explicit_destroy (it);

	g_string_append_c (s, ')');

	return s;
}

/* (from src/clients/cli/cmd_coll.c) */
static void
coll_dump_list (xmmsv_t *list, unsigned int level)
{
	xmmsv_list_iter_t *it;
	xmmsv_coll_t *operand;
	xmmsv_t *v;

	xmmsv_get_list_iter (list, &it);
	while (xmmsv_list_iter_entry (it, &v)) {
		if (xmmsv_get_coll (v, &operand)) {
			coll_dump (operand, level);
		}
		xmmsv_list_iter_next (it);
	}

}
{'fraction_non_alphanumeric': 0.13056168576920338, 'fraction_numerical': 0.003678128188699841, 'mean_word_length': 4.0742925948223965, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 100, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6115789', 'n_tokens_mistral': 19036, 'n_tokens_neox': 17937, 'n_words': 4133}