XMMS2
src/xmms/collection.c
Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2011 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  */
00016 
00017 
00018 /** @file
00019  *  Manages collections
00020  */
00021 
00022 #include <stdio.h>
00023 #include <unistd.h>
00024 #include <stdlib.h>
00025 #include <string.h>
00026 #include <glib.h>
00027 #include <math.h>
00028 
00029 #include "xmmspriv/xmms_collection.h"
00030 #include "xmmspriv/xmms_playlist.h"
00031 #include "xmmspriv/xmms_collquery.h"
00032 #include "xmmspriv/xmms_collserial.h"
00033 #include "xmmspriv/xmms_collsync.h"
00034 #include "xmmspriv/xmms_xform.h"
00035 #include "xmmspriv/xmms_streamtype.h"
00036 #include "xmms/xmms_ipc.h"
00037 #include "xmms/xmms_config.h"
00038 #include "xmms/xmms_log.h"
00039 
00040 
00041 /* Internal helper structures */
00042 
00043 typedef struct {
00044     const gchar *name;
00045     const gchar *namespace;
00046     xmmsv_coll_t *oldtarget;
00047     xmmsv_coll_t *newtarget;
00048 } coll_rebind_infos_t;
00049 
00050 typedef struct {
00051     const gchar* oldname;
00052     const gchar* newname;
00053     const gchar* namespace;
00054 } coll_rename_infos_t;
00055 
00056 typedef struct {
00057     xmms_coll_dag_t *dag;
00058     FuncApplyToColl func;
00059     void *udata;
00060 } coll_call_infos_t;
00061 
00062 typedef struct {
00063     const gchar *target_name;
00064     const gchar *target_namespace;
00065     gboolean found;
00066 } coll_refcheck_t;
00067 
00068 typedef struct {
00069     const gchar *key;
00070     xmmsv_coll_t *value;
00071 } coll_table_pair_t;
00072 
00073 typedef enum {
00074     XMMS_COLLECTION_FIND_STATE_UNCHECKED,
00075     XMMS_COLLECTION_FIND_STATE_MATCH,
00076     XMMS_COLLECTION_FIND_STATE_NOMATCH,
00077 } coll_find_state_t;
00078 
00079 typedef struct add_metadata_from_tree_user_data_St {
00080     xmms_medialib_session_t *session;
00081     xmms_medialib_entry_t entry;
00082     guint src;
00083 } add_metadata_from_tree_user_data_t;
00084 
00085 static GList *global_stream_type;
00086 
00087 /* Functions */
00088 
00089 static void xmms_collection_destroy (xmms_object_t *object);
00090 
00091 static gboolean xmms_collection_validate (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, const gchar *save_name, const gchar *save_namespace);
00092 static gboolean xmms_collection_validate_recurs (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, const gchar *save_name, const gchar *save_namespace);
00093 static gboolean xmms_collection_unreference (xmms_coll_dag_t *dag, const gchar *name, guint nsid);
00094 
00095 static gboolean xmms_collection_has_reference_to (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, const gchar *tg_name, const gchar *tg_ns);
00096 
00097 static void xmms_collection_apply_to_collection_recurs (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, FuncApplyToColl f, void *udata);
00098 
00099 static void call_apply_to_coll (gpointer name, gpointer coll, gpointer udata);
00100 static void prepend_key_string (gpointer key, gpointer value, gpointer udata);
00101 static gboolean value_match_save_key (gpointer key, gpointer val, gpointer udata);
00102 
00103 static void rebind_references (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata);
00104 static void rename_references (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata);
00105 static void strip_references (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata);
00106 static void check_for_reference (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata);
00107 
00108 static void coll_unref (void *coll);
00109 
00110 static GHashTable *xmms_collection_media_info (xmms_medialib_entry_t mid, xmms_error_t *err);
00111 
00112 static gboolean filter_get_mediainfo_field_string (xmmsv_coll_t *coll, GHashTable *mediainfo, gchar **val);
00113 static gboolean filter_get_mediainfo_field_int (xmmsv_coll_t *coll, GHashTable *mediainfo, gint *val);
00114 static gboolean filter_get_operator_value_string (xmmsv_coll_t *coll, const gchar **val);
00115 static gboolean filter_get_operator_value_int (xmmsv_coll_t *coll, gint *val);
00116 static gboolean filter_get_operator_case (xmmsv_coll_t *coll, gboolean *val);
00117 
00118 static void build_match_table (gpointer key, gpointer value, gpointer udata);
00119 static gboolean find_unchecked (gpointer name, gpointer value, gpointer udata);
00120 static void build_list_matches (gpointer key, gpointer value, gpointer udata);
00121 
00122 static gboolean xmms_collection_media_match (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table);
00123 static gboolean xmms_collection_media_match_operand (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table);
00124 static gboolean xmms_collection_media_match_reference (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table, const gchar *refname, const gchar *refns);
00125 static gboolean xmms_collection_media_filter_has (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table);
00126 static gboolean xmms_collection_media_filter_equals (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table);
00127 static gboolean xmms_collection_media_filter_match (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table);
00128 static gboolean xmms_collection_media_filter_smaller (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table);
00129 static gboolean xmms_collection_media_filter_greater (xmms_coll_dag_t *dag, GHashTable *mediainfo, xmmsv_coll_t *coll, guint nsid, GHashTable *match_table);
00130 
00131 static xmmsv_coll_t * xmms_collection_client_get (xmms_coll_dag_t *dag, const gchar *collname, const gchar *namespace, xmms_error_t *error);
00132 static GList * xmms_collection_client_list (xmms_coll_dag_t *dag, const gchar *namespace, xmms_error_t *error);
00133 static void xmms_collection_client_save (xmms_coll_dag_t *dag, const gchar *name, const gchar *namespace, xmmsv_coll_t *coll, xmms_error_t *error);
00134 static void xmms_collection_client_remove (xmms_coll_dag_t *dag, const gchar *collname, const gchar *namespace, xmms_error_t *error);
00135 static GList * xmms_collection_client_find (xmms_coll_dag_t *dag, gint32 mid, const gchar *namespace, xmms_error_t *error);
00136 static void xmms_collection_client_rename (xmms_coll_dag_t *dag, const gchar *from_name, const gchar *to_name, const gchar *namespace, xmms_error_t *error);
00137 
00138 static GList * xmms_collection_client_query_infos (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, gint32 lim_start, gint32 lim_len, xmmsv_t *order, xmmsv_t *fetch, xmmsv_t *group, xmms_error_t *err);
00139 static GList * xmms_collection_client_query_ids (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, gint32 lim_start, gint32 lim_len, xmmsv_t *order, xmms_error_t *err);
00140 static xmmsv_coll_t *xmms_collection_client_idlist_from_playlist (xmms_coll_dag_t *dag, const gchar *mediainfo, xmms_error_t *err);
00141 static void xmms_collection_client_sync (xmms_coll_dag_t *dag, xmms_error_t *err);
00142 
00143 
00144 #include "collection_ipc.c"
00145 
00146 GTree *
00147 xmms_collection_changed_msg_new (xmms_collection_changed_actions_t type,
00148                                  const gchar *plname, const gchar *namespace)
00149 {
00150     GTree *dict;
00151 
00152     dict = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
00153                             NULL, (GDestroyNotify)xmmsv_unref);
00154 
00155     g_tree_insert (dict, (gpointer) "type", xmmsv_new_int (type));
00156     g_tree_insert (dict, (gpointer) "name", xmmsv_new_string (plname));
00157     g_tree_insert (dict, (gpointer) "namespace", xmmsv_new_string (namespace));
00158 
00159     return dict;
00160 }
00161 
00162 void
00163 xmms_collection_changed_msg_send (xmms_coll_dag_t *colldag, GTree *dict)
00164 {
00165     g_return_if_fail (colldag);
00166     g_return_if_fail (dict);
00167 
00168     xmms_object_emit_f (XMMS_OBJECT (colldag),
00169                         XMMS_IPC_SIGNAL_COLLECTION_CHANGED,
00170                         XMMSV_TYPE_DICT,
00171                         dict);
00172 
00173     g_tree_destroy (dict);
00174 }
00175 
00176 #define XMMS_COLLECTION_CHANGED_MSG(type, name, namespace) xmms_collection_changed_msg_send (dag, xmms_collection_changed_msg_new (type, name, namespace))
00177 
00178 
00179 /** @defgroup Collection Collection
00180   * @ingroup XMMSServer
00181   * @brief This is the collection manager.
00182   *
00183   * The set of collections is stored as a DAG of collection operators.
00184   * Each collection namespace contains a list of saved collections,
00185   * with a pointer to the node in the graph.
00186   * @{
00187   */
00188 
00189 /** Collection DAG structure */
00190 
00191 struct xmms_coll_dag_St {
00192     xmms_object_t object;
00193 
00194     /* Ref to the playlist object, needed to notify it when a playlist changes */
00195     xmms_playlist_t *playlist;
00196 
00197     GHashTable *collrefs[XMMS_COLLECTION_NUM_NAMESPACES];
00198 
00199     GMutex *mutex;
00200 
00201 };
00202 
00203 static void
00204 coll_sync_cb (xmms_object_t *object, xmmsv_t *val, gpointer udata)
00205 {
00206     xmms_coll_sync_schedule_sync ();
00207 }
00208 
00209 /** Initializes a new xmms_coll_dag_t.
00210  *
00211  * @returns  The newly allocated collection DAG.
00212  */
00213 xmms_coll_dag_t *
00214 xmms_collection_init (xmms_playlist_t *playlist)
00215 {
00216     gint i;
00217     xmms_coll_dag_t *ret;
00218     xmms_stream_type_t *f;
00219 
00220     ret = xmms_object_new (xmms_coll_dag_t, xmms_collection_destroy);
00221     ret->mutex = g_mutex_new ();
00222     ret->playlist = playlist;
00223 
00224     xmms_coll_sync_init (ret);
00225 
00226     for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
00227         ret->collrefs[i] = g_hash_table_new_full (g_str_hash, g_str_equal,
00228                                                   g_free, coll_unref);
00229     }
00230 
00231     xmms_collection_register_ipc_commands (XMMS_OBJECT (ret));
00232 
00233     /* Connection coll_sync_cb to some signals */
00234     xmms_object_connect (XMMS_OBJECT (ret),
00235                          XMMS_IPC_SIGNAL_COLLECTION_CHANGED,
00236                          coll_sync_cb, ret);
00237 
00238     /* FIXME: These signals should trigger COLLECTION_CHANGED */
00239     xmms_object_connect (XMMS_OBJECT (playlist),
00240                          XMMS_IPC_SIGNAL_PLAYLIST_CHANGED,
00241                          coll_sync_cb, ret);
00242 
00243     xmms_object_connect (XMMS_OBJECT (playlist),
00244                          XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS,
00245                          coll_sync_cb, ret);
00246 
00247     xmms_object_connect (XMMS_OBJECT (playlist),
00248                          XMMS_IPC_SIGNAL_PLAYLIST_LOADED,
00249                          coll_sync_cb, ret);
00250 
00251 
00252     xmms_collection_dag_restore (ret);
00253 
00254     f = _xmms_stream_type_new (XMMS_STREAM_TYPE_BEGIN,
00255                                XMMS_STREAM_TYPE_MIMETYPE,
00256                                "application/x-xmms2-playlist-entries",
00257                                XMMS_STREAM_TYPE_END);
00258     global_stream_type = g_list_prepend (NULL, f);
00259 
00260     return ret;
00261 }
00262 
00263 static void
00264 add_metadata_from_tree (const gchar *key, xmmsv_t *value, gpointer user_data)
00265 {
00266     add_metadata_from_tree_user_data_t *ud = user_data;
00267 
00268     if (xmmsv_get_type (value) == XMMSV_TYPE_INT32) {
00269         gint iv;
00270         xmmsv_get_int (value, &iv);
00271         xmms_medialib_entry_property_set_int_source (ud->session, ud->entry,
00272                                                      key,
00273                                                      iv,
00274                                                      ud->src);
00275     } else if (xmmsv_get_type (value) == XMMSV_TYPE_STRING) {
00276         const gchar *sv;
00277         xmmsv_get_string (value, &sv);
00278         xmms_medialib_entry_property_set_str_source (ud->session, ud->entry,
00279                                                      key,
00280                                                      sv,
00281                                                      ud->src);
00282     }
00283 }
00284 
00285 
00286 /** Create a idlist from a playlist file
00287  * @param dag  The collection DAG.
00288  * @param path  URL to the playlist file
00289  * @param err  If error occurs, a message is stored in this variable.
00290  * @returns  A idlist
00291  */
00292 static xmmsv_coll_t *
00293 xmms_collection_client_idlist_from_playlist (xmms_coll_dag_t *dag,
00294                                              const gchar *path,
00295                                              xmms_error_t *err)
00296 {
00297     xmms_xform_t *xform;
00298     GList *lst, *n;
00299     xmmsv_coll_t *coll;
00300     xmms_medialib_session_t *session;
00301     guint src;
00302     const gchar *buf;
00303 
00304     /* we don't want any effects for playlist, so just report we're rehashing */
00305     xform = xmms_xform_chain_setup_url (0, path, global_stream_type, TRUE);
00306 
00307     if (!xform) {
00308         xmms_error_set (err, XMMS_ERROR_NO_SAUSAGE, "We can't handle this type of playlist or URL");
00309         return NULL;
00310     }
00311 
00312     lst = xmms_xform_browse_method (xform, "/", err);
00313     if (xmms_error_iserror (err)) {
00314         xmms_object_unref (xform);
00315         return NULL;
00316     }
00317 
00318     coll = xmmsv_coll_new (XMMS_COLLECTION_TYPE_IDLIST);
00319     session = xmms_medialib_begin_write ();
00320     src = xmms_medialib_source_to_id (session, "plugin/playlist");
00321 
00322     n = lst;
00323     while (n) {
00324         xmms_medialib_entry_t entry;
00325 
00326         xmmsv_t *a = n->data;
00327         xmmsv_t *b;
00328 
00329         if (!xmmsv_dict_get (a, "realpath", &b)) {
00330             xmms_log_error ("Playlist plugin did not set realpath; probably a bug in plugin");
00331             xmmsv_unref (a);
00332             n = g_list_delete_link (n, n);
00333             continue;
00334         }
00335 
00336         xmmsv_get_string (b, &buf);
00337         entry = xmms_medialib_entry_new_encoded (session, buf, err);
00338         xmmsv_dict_remove (a, "realpath");
00339         xmmsv_dict_remove (a, "path");
00340 
00341         if (entry) {
00342             add_metadata_from_tree_user_data_t udata;
00343             udata.session = session;
00344             udata.entry = entry;
00345             udata.src = src;
00346 
00347             xmmsv_dict_foreach(a, add_metadata_from_tree, &udata);
00348 
00349             xmmsv_coll_idlist_append (coll, entry);
00350         } else {
00351             xmmsv_get_string (b, &buf);
00352             xmms_log_error ("couldn't add %s to collection!", buf);
00353         }
00354 
00355         xmmsv_unref (a);
00356         n = g_list_delete_link (n, n);
00357     }
00358 
00359     xmms_medialib_end (session);
00360     xmms_object_unref (xform);
00361 
00362     return coll;
00363 }
00364 
00365 /** Remove the given collection from the DAG.
00366 *
00367 * If to be removed from ALL namespaces, then all matching collections are removed.
00368 *
00369 * @param dag  The collection DAG.
00370 * @param name  The name of the collection to remove.
00371 * @param namespace  The namespace where the collection to remove is (can be ALL).
00372 * @param err  If an error occurs, a message is stored in it.
00373 * @returns  True on success, false otherwise.
00374 */
00375 void
00376 xmms_collection_client_remove (xmms_coll_dag_t *dag, const gchar *name,
00377                                const gchar *namespace, xmms_error_t *err)
00378 {
00379     guint nsid;
00380     gboolean retval = FALSE;
00381     guint i;
00382 
00383     nsid = xmms_collection_get_namespace_id (namespace);
00384     if (nsid == XMMS_COLLECTION_NSID_INVALID) {
00385         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
00386         return;
00387     }
00388 
00389     g_mutex_lock (dag->mutex);
00390 
00391     /* Unreference the matching collection(s) */
00392     if (nsid == XMMS_COLLECTION_NSID_ALL) {
00393         for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
00394             retval = xmms_collection_unreference (dag, name, i) || retval;
00395         }
00396     } else {
00397         retval = xmms_collection_unreference (dag, name, nsid);
00398     }
00399 
00400     g_mutex_unlock (dag->mutex);
00401 
00402     if (retval == FALSE) {
00403         xmms_error_set (err, XMMS_ERROR_NOENT, "Failed to remove this collection!");
00404     }
00405 
00406 }
00407 
00408 /** Save the given collection in the DAG under the given name in the given namespace.
00409  *
00410  * @param dag  The collection DAG in which to save the collection.
00411  * @param name  The name under which to save the collection.
00412  * @param namespace  The namespace in which to save th collection.
00413  * @param coll  The collection structure to save.
00414  * @param err  If an error occurs, a message is stored in it.
00415  * @returns  True on success, false otherwise.
00416  */
00417 void
00418 xmms_collection_client_save (xmms_coll_dag_t *dag, const gchar *name, const gchar *namespace,
00419                              xmmsv_coll_t *coll, xmms_error_t *err)
00420 {
00421     xmmsv_coll_t *existing;
00422     guint nsid;
00423     const gchar *alias;
00424     gchar *newkey = NULL;
00425 
00426     nsid = xmms_collection_get_namespace_id (namespace);
00427     if (nsid == XMMS_COLLECTION_NSID_INVALID) {
00428         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
00429         return;
00430     } else if (nsid == XMMS_COLLECTION_NSID_ALL) {
00431         xmms_error_set (err, XMMS_ERROR_GENERIC, "cannot save collection in all namespaces");
00432         return;
00433     }
00434 
00435     /* Validate collection structure */
00436     if (!xmms_collection_validate (dag, coll, name, namespace)) {
00437         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection structure");
00438         return;
00439     }
00440 
00441     g_mutex_lock (dag->mutex);
00442 
00443     /* Unreference previously saved collection */
00444     existing = xmms_collection_get_pointer (dag, name, nsid);
00445     if (existing != NULL) {
00446         /* Rebind reference pointers to the new collection */
00447         coll_rebind_infos_t infos = { name, namespace, existing, coll };
00448         xmms_collection_apply_to_all_collections (dag, rebind_references, &infos);
00449     }
00450 
00451     /* Link references in newly saved collection to actual operators */
00452     xmms_collection_apply_to_collection (dag, coll, bind_all_references, NULL);
00453 
00454     /* Update existing collection in the table */
00455     if (existing != NULL) {
00456         while ((alias = xmms_collection_find_alias (dag, nsid,
00457                                                     existing, NULL)) != NULL) {
00458             newkey = g_strdup (alias);
00459 
00460             /* update all pairs pointing to the old coll */
00461             xmms_collection_dag_replace (dag, nsid, newkey, coll);
00462             xmmsv_coll_ref (coll);
00463 
00464             XMMS_COLLECTION_CHANGED_MSG (XMMS_COLLECTION_CHANGED_UPDATE,
00465                                          newkey,
00466                                          namespace);
00467         }
00468 
00469     /* Save new collection in the table */
00470     } else {
00471         newkey = g_strdup (name);
00472         xmms_collection_dag_replace (dag, nsid, newkey, coll);
00473         xmmsv_coll_ref (coll);
00474 
00475         XMMS_COLLECTION_CHANGED_MSG (XMMS_COLLECTION_CHANGED_ADD,
00476                                      newkey,
00477                                      namespace);
00478     }
00479 
00480     g_mutex_unlock (dag->mutex);
00481 
00482     /* If updating a playlist, trigger PLAYLIST_CHANGED */
00483     if (nsid == XMMS_COLLECTION_NSID_PLAYLISTS) {
00484         XMMS_PLAYLIST_COLLECTION_CHANGED_MSG (dag->playlist, newkey);
00485     }
00486 
00487 }
00488 
00489 
00490 /** Retrieve the structure of a given collection.
00491  *
00492  * If looking in ALL namespaces, only the collection first found is returned!
00493  *
00494  * @param dag  The collection DAG.
00495  * @param name  The name of the collection to retrieve.
00496  * @param namespace  The namespace in which to look for the collection.
00497  * @param err  If an error occurs, a message is stored in it.
00498  * @returns  The collection structure if found, NULL otherwise.
00499  */
00500 xmmsv_coll_t *
00501 xmms_collection_client_get (xmms_coll_dag_t *dag, const gchar *name,
00502                             const gchar *namespace, xmms_error_t *err)
00503 {
00504     xmmsv_coll_t *coll = NULL;
00505     guint nsid;
00506 
00507     nsid = xmms_collection_get_namespace_id (namespace);
00508     if (nsid == XMMS_COLLECTION_NSID_INVALID) {
00509         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
00510         return NULL;
00511     }
00512 
00513     g_mutex_lock (dag->mutex);
00514 
00515     coll = xmms_collection_get_pointer (dag, name, nsid);
00516 
00517     /* Not found! */
00518     if (coll == NULL) {
00519         xmms_error_set (err, XMMS_ERROR_NOENT, "no such collection");
00520 
00521     /* New reference, will be freed after being put in the return message */
00522     } else {
00523         xmmsv_coll_ref (coll);
00524     }
00525 
00526     g_mutex_unlock (dag->mutex);
00527 
00528     return coll;
00529 }
00530 
00531 
00532 /** Synchronize collection data to the database (i.e. to disk).
00533  *
00534  * @param dag  The collection DAG.
00535  * @param err  If an error occurs, a message is stored in it.
00536  */
00537 
00538 void
00539 xmms_collection_sync (xmms_coll_dag_t *dag)
00540 {
00541     g_return_if_fail (dag);
00542 
00543     g_mutex_lock (dag->mutex);
00544 
00545     xmms_collection_dag_save (dag);
00546 
00547     g_mutex_unlock (dag->mutex);
00548 }
00549 
00550 
00551 void
00552 xmms_collection_client_sync (xmms_coll_dag_t *dag, xmms_error_t *err)
00553 {
00554     xmms_collection_sync (dag);
00555 }
00556 
00557 
00558 /** Lists the collections in the given namespace.
00559  *
00560  * @param dag  The collection DAG.
00561  * @param namespace  The namespace to list collections from (can be ALL).
00562  * @param err  If an error occurs, a message is stored in it.
00563  * @returns A newly allocated GList with the list of collection names.
00564  * Remember that it is only the LIST that is copied. Not the entries.
00565  * The entries are however referenced, and must be unreffed!
00566  */
00567 GList *
00568 xmms_collection_client_list (xmms_coll_dag_t *dag, const gchar *namespace,
00569                              xmms_error_t *err)
00570 {
00571     GList *r = NULL;
00572     guint nsid;
00573 
00574     nsid = xmms_collection_get_namespace_id (namespace);
00575     if (nsid == XMMS_COLLECTION_NSID_INVALID) {
00576         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
00577         return NULL;
00578     }
00579 
00580     g_mutex_lock (dag->mutex);
00581 
00582     /* Get the list of collections in the given namespace */
00583     xmms_collection_foreach_in_namespace (dag, nsid, prepend_key_string, &r);
00584 
00585     g_mutex_unlock (dag->mutex);
00586 
00587     return r;
00588 }
00589 
00590 
00591 /** Find all collections in the given namespace that contain a given media.
00592  *
00593  * @param dag  The collection DAG.
00594  * @param mid  The id of the media.
00595  * @param namespace  The namespace in which to look for collections.
00596  * @param err  If an error occurs, a message is stored in it.
00597  * @returns A newly allocated GList with the names of the matching collections.
00598  */
00599 GList *
00600 xmms_collection_client_find (xmms_coll_dag_t *dag, gint32 mid, const gchar *namespace,
00601                              xmms_error_t *err)
00602 {
00603     GHashTable *mediainfo;
00604     GList *ret = NULL;
00605     guint nsid;
00606     gchar *open_name;
00607     GHashTable *match_table;
00608     xmmsv_coll_t *coll;
00609 
00610     /* Verify namespace */
00611     nsid = xmms_collection_get_namespace_id (namespace);
00612     if (nsid == XMMS_COLLECTION_NSID_INVALID) {
00613         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
00614         return NULL;
00615     }
00616     if (nsid == XMMS_COLLECTION_NSID_ALL) {
00617         xmms_error_set (err, XMMS_ERROR_INVAL, "cannot search in all namespaces");
00618         return NULL;
00619     }
00620 
00621     /* Prepare the match table of all collections for the given namespace */
00622     match_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
00623     xmms_collection_foreach_in_namespace (dag, nsid, build_match_table, match_table);
00624 
00625     /* Get all infos for the given mid */
00626     mediainfo = xmms_collection_media_info (mid, err);
00627 
00628     /* While not all collections have been checked, check next */
00629     while (g_hash_table_find (match_table, find_unchecked, &open_name) != NULL) {
00630         coll_find_state_t *match = g_new (coll_find_state_t, 1);
00631         coll = xmms_collection_get_pointer (dag, open_name, nsid);
00632         if (xmms_collection_media_match (dag, mediainfo, coll, nsid, match_table)) {
00633             *match = XMMS_COLLECTION_FIND_STATE_MATCH;
00634         } else {
00635             *match = XMMS_COLLECTION_FIND_STATE_NOMATCH;
00636         }
00637         g_hash_table_replace (match_table, g_strdup (open_name), match);
00638     }
00639 
00640     /* List matching collections */
00641     g_hash_table_foreach (match_table, build_list_matches, &ret);
00642     g_hash_table_destroy (match_table);
00643 
00644     g_hash_table_destroy (mediainfo);
00645 
00646     return ret;
00647 }
00648 
00649 
00650 /** Rename a collection in a given namespace.
00651  *
00652  * @param dag  The collection DAG.
00653  * @param from_name  The name of the collection to rename.
00654  * @param to_name  The new name of the collection.
00655  * @param namespace  The namespace to consider (cannot be ALL).
00656  * @param err  If an error occurs, a message is stored in it.
00657  * @return True if a collection was found and renamed.
00658  */
00659 void
00660 xmms_collection_client_rename (xmms_coll_dag_t *dag, const gchar *from_name,
00661                                const gchar *to_name, const gchar *namespace,
00662                                xmms_error_t *err)
00663 {
00664     gboolean retval;
00665     guint nsid;
00666     xmmsv_coll_t *from_coll, *to_coll;
00667 
00668     nsid = xmms_collection_get_namespace_id (namespace);
00669     if (nsid == XMMS_COLLECTION_NSID_INVALID) {
00670         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection namespace");
00671         return;
00672     } else if (nsid == XMMS_COLLECTION_NSID_ALL) {
00673         xmms_error_set (err, XMMS_ERROR_GENERIC, "cannot rename collection in all namespaces");
00674         return;
00675     }
00676 
00677     g_mutex_lock (dag->mutex);
00678 
00679     from_coll = xmms_collection_get_pointer (dag, from_name, nsid);
00680     to_coll   = xmms_collection_get_pointer (dag, to_name, nsid);
00681 
00682     /* Input validation */
00683     if (from_coll == NULL) {
00684         xmms_error_set (err, XMMS_ERROR_NOENT, "no such collection");
00685         retval = FALSE;
00686 
00687     } else if (to_coll != NULL) {
00688         xmms_error_set (err, XMMS_ERROR_NOENT, "a collection already exists with the target name");
00689         retval = FALSE;
00690 
00691     /* Update collection name everywhere */
00692     } else {
00693         GTree *dict;
00694 
00695         /* insert new pair in hashtable */
00696         xmms_collection_dag_replace (dag, nsid, g_strdup (to_name), from_coll);
00697         xmmsv_coll_ref (from_coll);
00698 
00699         /* remove old pair from hashtable */
00700         g_hash_table_remove (dag->collrefs[nsid], from_name);
00701 
00702         /* update name in all reference operators */
00703         coll_rename_infos_t infos = { from_name, to_name, namespace };
00704         xmms_collection_apply_to_all_collections (dag, rename_references, &infos);
00705 
00706         /* Send _RENAME signal */
00707         dict = xmms_collection_changed_msg_new (XMMS_COLLECTION_CHANGED_RENAME,
00708                                                 from_name, namespace);
00709         g_tree_insert (dict, (gpointer) "newname", xmmsv_new_string (to_name));
00710         xmms_collection_changed_msg_send (dag, dict);
00711 
00712         retval = TRUE;
00713     }
00714 
00715     g_mutex_unlock (dag->mutex);
00716 
00717 }
00718 
00719 
00720 /** Find the ids of the media matched by a collection.
00721  *
00722  * @param dag  The collection DAG.
00723  * @param coll  The collection used to match media.
00724  * @param lim_start  The beginning index of the LIMIT statement (0 to disable).
00725  * @param lim_len  The number of entries of the LIMIT statement (0 to disable).
00726  * @param order  The list of properties to order by (empty to disable).
00727  * @param err  If an error occurs, a message is stored in it.
00728  * @return A list of media ids.
00729  */
00730 GList *
00731 xmms_collection_query_ids (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
00732                            gint32 lim_start, gint32 lim_len, xmmsv_t *order,
00733                            xmms_error_t *err)
00734 {
00735     GList *res, *n;
00736     xmmsv_t *fetch, *group, *idval;
00737 
00738     /* no grouping, fetch only id */
00739     group = xmmsv_new_list ();
00740     fetch = xmmsv_new_list ();
00741     idval = xmmsv_new_string ("id");
00742     xmmsv_list_append (fetch, idval);
00743 
00744     res = xmms_collection_client_query_infos (dag, coll, lim_start, lim_len, order, fetch, group, err);
00745 
00746     /* FIXME: get an uint list directly ! (we're getting ints here actually) */
00747     for (n = res; n; n = n->next) {
00748         xmms_medialib_entry_t id;
00749         xmmsv_t *id_val, *cmdval = n->data;
00750 
00751         xmmsv_dict_get (cmdval, "id", &id_val);
00752         xmmsv_get_int (id_val, &id);
00753         n->data = xmmsv_new_int (id);
00754 
00755         xmmsv_unref (cmdval);
00756     }
00757 
00758     xmmsv_unref (group);
00759     xmmsv_unref (fetch);
00760     xmmsv_unref (idval);
00761 
00762     return res;
00763 }
00764 
00765 
00766 GList *
00767 xmms_collection_client_query_ids (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
00768                                   gint32 lim_start, gint32 lim_len, xmmsv_t *order,
00769                                   xmms_error_t *err)
00770 {
00771     return xmms_collection_query_ids (dag, coll, lim_start, lim_len, order, err);
00772 }
00773 /** Find the properties of the media matched by a collection.
00774  *
00775  * @param dag  The collection DAG.
00776  * @param coll  The collection used to match media.
00777  * @param lim_start  The beginning index of the LIMIT statement (0 to disable).
00778  * @param lim_len  The number of entries of the LIMIT statement (0 to disable).
00779  * @param order  The list of properties to order by, prefix by '-' to invert (empty to disable).
00780  * @param fetch  The list of properties to be retrieved.
00781  * @param group  The list of properties to group by (empty to disable).
00782  * @param err  If an error occurs, a message is stored in it.
00783  * @return A list of property dicts for each entry.
00784  */
00785 GList *
00786 xmms_collection_client_query_infos (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
00787                                     gint32 lim_start, gint32 lim_len, xmmsv_t *order,
00788                                     xmmsv_t *fetch, xmmsv_t *group, xmms_error_t *err)
00789 {
00790     GList *res = NULL;
00791     GString *query;
00792 
00793     /* check that fetch is not empty */
00794     if (xmmsv_list_get_size (fetch) == 0) {
00795         xmms_error_set (err, XMMS_ERROR_INVAL, "fetch list must not be empty!");
00796         return NULL;
00797     }
00798 
00799     /* check for invalid property strings */
00800     if (!check_string_list (order)) {
00801         xmms_error_set (err, XMMS_ERROR_NOENT, "invalid order list!");
00802         return NULL;
00803     }
00804     if (!check_string_list (fetch)) {
00805         xmms_error_set (err, XMMS_ERROR_NOENT, "invalid fetch list!");
00806         return NULL;
00807     }
00808     if (!check_string_list (group)) {
00809         xmms_error_set (err, XMMS_ERROR_NOENT, "invalid group list!");
00810         return NULL;
00811     }
00812 
00813     /* validate the collection to query */
00814     if (!xmms_collection_validate (dag, coll, NULL, NULL)) {
00815         if (err) {
00816             xmms_error_set (err, XMMS_ERROR_INVAL, "invalid collection structure");
00817         }
00818         return NULL;
00819     }
00820 
00821     g_mutex_lock (dag->mutex);
00822 
00823     query = xmms_collection_get_query (dag, coll, lim_start, lim_len,
00824                                        order, fetch, group);
00825 
00826     g_mutex_unlock (dag->mutex);
00827 
00828     XMMS_DBG ("COLLECTIONS: query_infos with %s", query->str);
00829 
00830     /* Run the query */
00831     xmms_medialib_session_t *session = xmms_medialib_begin ();
00832     res = xmms_medialib_select (session, query->str, err);
00833     xmms_medialib_end (session);
00834 
00835     g_string_free (query, TRUE);
00836 
00837     return res;
00838 }
00839 
00840 /**
00841  * Update a reference to point to a new collection.
00842  *
00843  * @param dag  The collection DAG.
00844  * @param name The name of the reference to update.
00845  * @param nsid The namespace in which to locate the reference.
00846  * @param newtarget The new collection pointed to by the reference.
00847  */
00848 void
00849 xmms_collection_update_pointer (xmms_coll_dag_t *dag, const gchar *name,
00850                                 guint nsid, xmmsv_coll_t *newtarget)
00851 {
00852     xmms_collection_dag_replace (dag, nsid, g_strdup (name), newtarget);
00853     xmmsv_coll_ref (newtarget);
00854 }
00855 
00856 /** Update the DAG to update the value of the pair with the given key. */
00857 void
00858 xmms_collection_dag_replace (xmms_coll_dag_t *dag,
00859                              xmms_collection_namespace_id_t nsid,
00860                              gchar *key, xmmsv_coll_t *newcoll)
00861 {
00862     g_hash_table_replace (dag->collrefs[nsid], key, newcoll);
00863 }
00864 
00865 /** Find the collection structure corresponding to the given name in the given namespace.
00866  *
00867  * @param dag  The collection DAG.
00868  * @param collname  The name of the collection to find.
00869  * @param nsid  The namespace id.
00870  * @returns  The collection structure if found, NULL otherwise.
00871  */
00872 xmmsv_coll_t *
00873 xmms_collection_get_pointer (xmms_coll_dag_t *dag, const gchar *collname,
00874                              guint nsid)
00875 {
00876     gint i;
00877     xmmsv_coll_t *coll = NULL;
00878 
00879     if (nsid == XMMS_COLLECTION_NSID_ALL) {
00880         for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES && coll == NULL; ++i) {
00881             coll = g_hash_table_lookup (dag->collrefs[i], collname);
00882         }
00883     } else {
00884         coll = g_hash_table_lookup (dag->collrefs[nsid], collname);
00885     }
00886 
00887     return coll;
00888 }
00889 
00890 /** Extract an attribute from a collection as an integer.
00891  *
00892  * @param coll  The collection to extract the attribute from.
00893  * @param attrname  The name of the attribute.
00894  * @param val  The integer value of the attribute will be saved in this pointer.
00895  * @return TRUE if attribute correctly read, FALSE otherwise
00896  */
00897 gboolean
00898 xmms_collection_get_int_attr (xmmsv_coll_t *coll, const gchar *attrname, gint *val)
00899 {
00900     gboolean retval = FALSE;
00901     gint buf;
00902     gchar *str;
00903     gchar *endptr;
00904 
00905     if (xmmsv_coll_attribute_get (coll, attrname, &str)) {
00906         buf = strtol (str, &endptr, 10);
00907 
00908         /* Valid integer string */
00909         if (*endptr == '\0') {
00910             *val = buf;
00911             retval = TRUE;
00912         }
00913     }
00914 
00915     return retval;
00916 }
00917 
00918 /** Set the attribute of a collection as an integer.
00919  *
00920  * @param coll  The collection in which to set the attribute.
00921  * @param attrname  The name of the attribute.
00922  * @param newval  The new value of the attribute.
00923  * @return TRUE if attribute successfully saved, FALSE otherwise.
00924  */
00925 gboolean
00926 xmms_collection_set_int_attr (xmmsv_coll_t *coll, const gchar *attrname,
00927                               gint newval)
00928 {
00929     gboolean retval = FALSE;
00930     gchar str[XMMS_MAX_INT_ATTRIBUTE_LEN + 1];
00931     gint written;
00932 
00933     written = g_snprintf (str, sizeof (str), "%d", newval);
00934     if (written < XMMS_MAX_INT_ATTRIBUTE_LEN) {
00935         xmmsv_coll_attribute_set (coll, attrname, str);
00936         retval = TRUE;
00937     }
00938 
00939     return retval;
00940 }
00941 
00942 
00943 /**
00944  * Reverse-search the list of collections in the given namespace to
00945  * find the first pair whose value matches the argument.  If key is
00946  * not NULL, any pair with the same key will be ignored.
00947  *
00948  * @param dag  The collection DAG.
00949  * @param nsid  The id of the namespace to consider.
00950  * @param value  The value of the pair to find.
00951  * @param key  If not NULL, ignore any pair with that key.
00952  * @return The key of the found pair.
00953  */
00954 const gchar *
00955 xmms_collection_find_alias (xmms_coll_dag_t *dag, guint nsid,
00956                             xmmsv_coll_t *value, const gchar *key)
00957 {
00958     const gchar *otherkey = NULL;
00959     coll_table_pair_t search_pair = { key, value };
00960 
00961     if (g_hash_table_find (dag->collrefs[nsid], value_match_save_key,
00962                            &search_pair) != NULL) {
00963         otherkey = search_pair.key;
00964     }
00965 
00966     return otherkey;
00967 }
00968 
00969 
00970 /**
00971  * Get a random media entry from the given collection.
00972  *
00973  * @param dag  The collection DAG.
00974  * @param source  The collection to query.
00975  * @return  A random media from the source collection, or 0 if none found.
00976  */
00977 xmms_medialib_entry_t
00978 xmms_collection_get_random_media (xmms_coll_dag_t *dag, xmmsv_coll_t *source)
00979 {
00980     GList *res;
00981     xmms_medialib_entry_t mid = 0;
00982     xmmsv_t *rorder = xmmsv_new_list ();
00983     xmmsv_t *randval = xmmsv_new_string ("~RANDOM()");
00984 
00985     /* FIXME: Temporary hack to allow custom ordering functions */
00986     xmmsv_list_append (rorder, randval);
00987 
00988     res = xmms_collection_query_ids (dag, source, 0, 1, rorder, NULL);
00989 
00990     if (res != NULL) {
00991         xmmsv_t *val = (xmmsv_t *) res->data;
00992         xmmsv_get_int (val, &mid);
00993         xmmsv_unref (val);
00994         g_list_free (res);
00995     }
00996 
00997     xmmsv_unref (rorder);
00998     xmmsv_unref (randval);
00999 
01000     return mid;
01001 }
01002 
01003 /** @} */
01004 
01005 
01006 
01007 /** Free the collection DAG and other memory in the xmms_coll_dag_t
01008  *
01009  *  This will free all collections in the DAG!
01010  */
01011 static void
01012 xmms_collection_destroy (xmms_object_t *object)
01013 {
01014     gint i;
01015     xmms_coll_dag_t *dag = (xmms_coll_dag_t *)object;
01016 
01017     g_return_if_fail (dag);
01018 
01019     xmms_coll_sync_shutdown ();
01020     xmms_collection_dag_save (dag);
01021 
01022     g_mutex_free (dag->mutex);
01023 
01024     for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
01025         g_hash_table_destroy (dag->collrefs[i]);  /* dag is freed here */
01026     }
01027 
01028     xmms_collection_unregister_ipc_commands ();
01029 }
01030 
01031 /** Validate the given collection against a DAG.
01032  *
01033  * @param dag  The collection DAG.
01034  * @param coll  The collection to validate.
01035  * @param save_name  The name under which the collection will be saved (NULL
01036  *                   if none).
01037  * @param save_namespace  The namespace in which the collection will be
01038  *                        saved (NULL if none).
01039  * @returns  True if the collection is valid, false otherwise.
01040  */
01041 static gboolean
01042 xmms_collection_validate (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
01043                           const gchar *save_name, const gchar *save_namespace)
01044 {
01045     /* Special validation checks for the Playlists namespace */
01046     if (save_namespace != NULL &&
01047         strcmp (save_namespace, XMMS_COLLECTION_NS_PLAYLISTS) == 0) {
01048         /* only accept idlists */
01049         if (xmmsv_coll_get_type (coll) != XMMS_COLLECTION_TYPE_IDLIST &&
01050             xmmsv_coll_get_type (coll) != XMMS_COLLECTION_TYPE_QUEUE &&
01051             xmmsv_coll_get_type (coll) != XMMS_COLLECTION_TYPE_PARTYSHUFFLE) {
01052             return FALSE;
01053         }
01054     }
01055 
01056     /* Standard checking of the whole coll DAG */
01057     return xmms_collection_validate_recurs (dag, coll, save_name,
01058                                             save_namespace);
01059 }
01060 
01061 /**
01062  * Internal recursive validation function used to validate the whole
01063  * graph of a collection.
01064  */
01065 static gboolean
01066 xmms_collection_validate_recurs (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
01067                                  const gchar *save_name, const gchar *save_namespace)
01068 {
01069     guint num_operands = 0;
01070     xmmsv_coll_t *op, *ref;
01071     gchar *attr, *attr2;
01072     gboolean valid = TRUE;
01073     xmmsv_coll_type_t type;
01074     xmms_collection_namespace_id_t nsid;
01075 
01076     /* count operands */
01077     num_operands = xmmsv_list_get_size (xmmsv_coll_operands_get (coll));
01078 
01079     /* analyse by type */
01080     type = xmmsv_coll_get_type (coll);
01081     switch (type) {
01082     case XMMS_COLLECTION_TYPE_REFERENCE:
01083         /* zero or one (bound in DAG) operand */
01084         if (num_operands > 1) {
01085             return FALSE;
01086         }
01087 
01088         /* check if referenced collection exists */
01089         xmmsv_coll_attribute_get (coll, "reference", &attr);
01090         if (attr == NULL) {
01091             return FALSE;
01092         } else if (strcmp (attr, "All Media") != 0) {
01093             xmmsv_coll_attribute_get (coll, "namespace", &attr2);
01094 
01095             if (attr2 == NULL) {
01096                 return FALSE;
01097             }
01098 
01099             nsid = xmms_collection_get_namespace_id (attr2);
01100             if (nsid == XMMS_COLLECTION_NSID_INVALID) {
01101                 return FALSE;
01102             }
01103 
01104             g_mutex_lock (dag->mutex);
01105             ref = xmms_collection_get_pointer (dag, attr, nsid);
01106             if (ref == NULL) {
01107                 g_mutex_unlock (dag->mutex);
01108                 return FALSE;
01109             }
01110 
01111             if (save_name && save_namespace) {
01112                 /* self-reference is of course forbidden */
01113                 if (strcmp (attr, save_name) == 0 &&
01114                     strcmp (attr2, save_namespace) == 0) {
01115 
01116                     g_mutex_unlock (dag->mutex);
01117                     return FALSE;
01118 
01119                 /* check if the referenced coll references this one (loop!) */
01120                 } else if (xmms_collection_has_reference_to (dag, ref, save_name,
01121                                                              save_namespace)) {
01122                     g_mutex_unlock (dag->mutex);
01123                     return FALSE;
01124                 }
01125             }
01126 
01127             g_mutex_unlock (dag->mutex);
01128         } else {
01129             /* "All Media" reference, so no referenced coll pointer */
01130             ref = NULL;
01131         }
01132 
01133         /* ensure that the operand is consistent with the reference infos */
01134         if (num_operands == 1) {
01135             xmmsv_t *val;
01136             xmmsv_list_get (xmmsv_coll_operands_get (coll), 0, &val);
01137             xmmsv_get_coll (val, &op);
01138 
01139             if (op != ref) {
01140                 return FALSE;
01141             }
01142         }
01143         break;
01144 
01145     case XMMS_COLLECTION_TYPE_UNION:
01146     case XMMS_COLLECTION_TYPE_INTERSECTION:
01147         /* need operand(s) */
01148         if (num_operands == 0) {
01149             return FALSE;
01150         }
01151         break;
01152 
01153     case XMMS_COLLECTION_TYPE_COMPLEMENT:
01154         /* one operand */
01155         if (num_operands != 1) {
01156             return FALSE;
01157         }
01158         break;
01159 
01160     case XMMS_COLLECTION_TYPE_HAS:
01161         /* one operand */
01162         if (num_operands != 1) {
01163             return FALSE;
01164         }
01165 
01166         /* "field" attribute */
01167         /* with valid value */
01168         if (!xmmsv_coll_attribute_get (coll, "field", &attr)) {
01169             return FALSE;
01170         }
01171         break;
01172 
01173     case XMMS_COLLECTION_TYPE_EQUALS:
01174     case XMMS_COLLECTION_TYPE_MATCH:
01175     case XMMS_COLLECTION_TYPE_SMALLER:
01176     case XMMS_COLLECTION_TYPE_GREATER:
01177         /* one operand */
01178         if (num_operands != 1) {
01179             return FALSE;
01180         }
01181 
01182         /* "field"/"value" attributes */
01183         /* with valid values */
01184         if (!xmmsv_coll_attribute_get (coll, "field", &attr)) {
01185             return FALSE;
01186         }
01187         /* FIXME: valid fields?
01188         else if (...) {
01189             return FALSE;
01190         }
01191         */
01192 
01193         if (!xmmsv_coll_attribute_get (coll, "value", &attr)) {
01194             return FALSE;
01195         }
01196         break;
01197 
01198     case XMMS_COLLECTION_TYPE_IDLIST:
01199     case XMMS_COLLECTION_TYPE_QUEUE:
01200         /* no operand */
01201         if (num_operands > 0) {
01202             return FALSE;
01203         }
01204         break;
01205 
01206     case XMMS_COLLECTION_TYPE_PARTYSHUFFLE:
01207         /* one operand */
01208         if (num_operands != 1) {
01209             return FALSE;
01210         }
01211         break;
01212 
01213     /* invalid type */
01214     default:
01215         return FALSE;
01216         break;
01217     }
01218 
01219 
01220     /* recurse in operands */
01221     if (num_operands > 0 && type != XMMS_COLLECTION_TYPE_REFERENCE) {
01222         xmmsv_list_iter_t *iter;
01223         xmmsv_get_list_iter (xmmsv_coll_operands_get (coll), &iter);
01224 
01225         for (xmmsv_list_iter_first (iter);
01226              valid && xmmsv_list_iter_valid (iter);
01227              xmmsv_list_iter_next (iter)) {
01228 
01229             xmmsv_t *val;
01230             xmmsv_list_iter_entry (iter, &val);
01231             xmmsv_get_coll (val, &op);
01232 
01233             if (!xmms_collection_validate_recurs (dag, op, save_name,
01234                                                   save_namespace)) {
01235                 valid = FALSE;
01236             }
01237         }
01238 
01239         xmmsv_list_iter_explicit_destroy (iter);
01240     }
01241 
01242     return valid;
01243 }
01244 
01245 /** Try to unreference a collection from a given namespace.
01246  *
01247  * @param dag  The collection DAG.
01248  * @param name  The name of the collection to remove.
01249  * @param nsid  The namespace in which to look for the collection (yes, redundant).
01250  * @returns  TRUE if a collection was removed, FALSE otherwise.
01251  */
01252 static gboolean
01253 xmms_collection_unreference (xmms_coll_dag_t *dag, const gchar *name, guint nsid)
01254 {
01255     xmmsv_coll_t *existing, *active_pl;
01256     gboolean retval = FALSE;
01257 
01258     existing  = g_hash_table_lookup (dag->collrefs[nsid], name);
01259     active_pl = g_hash_table_lookup (dag->collrefs[XMMS_COLLECTION_NSID_PLAYLISTS],
01260                                      XMMS_ACTIVE_PLAYLIST);
01261 
01262     /* Unref if collection exists, and is not pointed at by _active playlist */
01263     if (existing != NULL && existing != active_pl) {
01264         const gchar *matchkey;
01265         const gchar *nsname = xmms_collection_get_namespace_string (nsid);
01266         coll_rebind_infos_t infos = { name, nsname, existing, NULL };
01267 
01268         /* FIXME: if reference pointed to by a label, we should update
01269          * the label to point to the ref'd operator instead ! */
01270 
01271         /* Strip all references to the deleted coll, bind operator directly */
01272         xmms_collection_apply_to_all_collections (dag, strip_references, &infos);
01273 
01274         /* Remove all pairs pointing to that collection */
01275         while ((matchkey = xmms_collection_find_alias (dag, nsid,
01276                                                        existing, NULL)) != NULL) {
01277 
01278             XMMS_COLLECTION_CHANGED_MSG (XMMS_COLLECTION_CHANGED_REMOVE,
01279                                          matchkey,
01280                                          nsname);
01281 
01282             g_hash_table_remove (dag->collrefs[nsid], matchkey);
01283         }
01284 
01285         retval = TRUE;
01286     }
01287 
01288     return retval;
01289 }
01290 
01291 /** Find the namespace id corresponding to a namespace string.
01292  *
01293  * @param namespace  The namespace string.
01294  * @returns  The namespace id.
01295  */
01296 xmms_collection_namespace_id_t
01297 xmms_collection_get_namespace_id (const gchar *namespace)
01298 {
01299     guint nsid;
01300 
01301     if (strcmp (namespace, XMMS_COLLECTION_NS_ALL) == 0) {
01302         nsid = XMMS_COLLECTION_NSID_ALL;
01303     } else if (strcmp (namespace, XMMS_COLLECTION_NS_COLLECTIONS) == 0) {
01304         nsid = XMMS_COLLECTION_NSID_COLLECTIONS;
01305     } else if (strcmp (namespace, XMMS_COLLECTION_NS_PLAYLISTS) == 0) {
01306         nsid = XMMS_COLLECTION_NSID_PLAYLISTS;
01307     } else {
01308         nsid = XMMS_COLLECTION_NSID_INVALID;
01309     }
01310 
01311     return nsid;
01312 }
01313 
01314 /** Find the namespace name (string) corresponding to a namespace id.
01315  *
01316  * @param nsid  The namespace id.
01317  * @returns  The namespace name (string).
01318  */
01319 const gchar *
01320 xmms_collection_get_namespace_string (xmms_collection_namespace_id_t nsid)
01321 {
01322     const gchar *name;
01323 
01324     switch (nsid) {
01325     case XMMS_COLLECTION_NSID_ALL:
01326         name = XMMS_COLLECTION_NS_ALL;
01327         break;
01328     case XMMS_COLLECTION_NSID_COLLECTIONS:
01329         name = XMMS_COLLECTION_NS_COLLECTIONS;
01330         break;
01331     case XMMS_COLLECTION_NSID_PLAYLISTS:
01332         name = XMMS_COLLECTION_NS_PLAYLISTS;
01333         break;
01334 
01335     case XMMS_COLLECTION_NSID_INVALID:
01336     default:
01337         name = NULL;
01338         break;
01339     }
01340 
01341     return name;
01342 }
01343 
01344 
01345 /** Check whether a collection structure contains a reference to a given collection.
01346  *
01347  * @param dag  The collection DAG.
01348  * @param coll  The collection to inspect for reference.
01349  * @param tg_name  The name of the collection to find a reference to.
01350  * @param tg_ns  The namespace of the collection to find a reference to.
01351  * @returns  True if the collection contains a reference to the given
01352  *           collection, false otherwise
01353  */
01354 static gboolean
01355 xmms_collection_has_reference_to (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
01356                                   const gchar *tg_name, const gchar *tg_ns)
01357 {
01358     coll_refcheck_t check = { tg_name, tg_ns, FALSE };
01359     xmms_collection_apply_to_collection (dag, coll, check_for_reference, &check);
01360 
01361     return check.found;
01362 }
01363 
01364 
01365 /** Apply a function to all the collections in a given namespace.
01366  *
01367  * @param dag  The collection DAG.
01368  * @param nsid  The namespace id.
01369  * @param f  The function to apply to all the collections.
01370  * @param udata  Additional user data parameter passed to the function.
01371  */
01372 void
01373 xmms_collection_foreach_in_namespace (xmms_coll_dag_t *dag, guint nsid, GHFunc f, void *udata)
01374 {
01375     gint i;
01376 
01377     if (nsid == XMMS_COLLECTION_NSID_ALL) {
01378         for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
01379             g_hash_table_foreach (dag->collrefs[i], f, udata);
01380         }
01381     } else if (nsid != XMMS_COLLECTION_NSID_INVALID) {
01382         g_hash_table_foreach (dag->collrefs[nsid], f, udata);
01383     }
01384 }
01385 
01386 /** Apply a function of type #FuncApplyToColl to all the collections in all namespaces.
01387  *
01388  * @param dag  The collection DAG.
01389  * @param f  The function to apply to all the collections.
01390  * @param udata  Additional user data parameter passed to the function.
01391  */
01392 void
01393 xmms_collection_apply_to_all_collections (xmms_coll_dag_t *dag,
01394                                           FuncApplyToColl f, void *udata)
01395 {
01396     gint i;
01397     coll_call_infos_t callinfos = { dag, f, udata };
01398 
01399     for (i = 0; i < XMMS_COLLECTION_NUM_NAMESPACES; ++i) {
01400         g_hash_table_foreach (dag->collrefs[i], call_apply_to_coll, &callinfos);
01401     }
01402 }
01403 
01404 /** Apply a function of type #FuncApplyToColl to the given collection.
01405  *
01406  * @param dag  The collection DAG.
01407  * @param coll  The collection on which to apply the function.
01408  * @param f  The function to apply to all the collections.
01409  * @param udata  Additional user data parameter passed to the function.
01410  */
01411 void
01412 xmms_collection_apply_to_collection (xmms_coll_dag_t *dag,
01413                                      xmmsv_coll_t *coll,
01414                                      FuncApplyToColl f, void *udata)
01415 {
01416     xmms_collection_apply_to_collection_recurs (dag, coll, NULL, f, udata);
01417 }
01418 
01419 /* Internal function used for recursion (parent param, NULL by default) */
01420 static void
01421 xmms_collection_apply_to_collection_recurs (xmms_coll_dag_t *dag,
01422                                             xmmsv_coll_t *coll,
01423                                             xmmsv_coll_t *parent,
01424                                             FuncApplyToColl f, void *udata)
01425 {
01426     xmmsv_coll_t *op;
01427 
01428     /* Apply the function to the operator. */
01429     f (dag, coll, parent, udata);
01430 
01431     /* Recurse into the operands (if not a reference) */
01432     if (xmmsv_coll_get_type (coll) != XMMS_COLLECTION_TYPE_REFERENCE) {
01433         xmmsv_list_iter_t *iter;
01434         xmmsv_get_list_iter (xmmsv_coll_operands_get (coll), &iter);
01435 
01436         for (xmmsv_list_iter_first (iter);
01437              xmmsv_list_iter_valid (iter);
01438              xmmsv_list_iter_next (iter)) {
01439 
01440             xmmsv_t *val;
01441             xmmsv_list_iter_entry (iter, &val);
01442 
01443             xmmsv_get_coll (val, &op);
01444 
01445             xmms_collection_apply_to_collection_recurs (dag, op, coll, f,
01446                                                         udata);
01447         }
01448 
01449         xmmsv_list_iter_explicit_destroy (iter);
01450     }
01451 }
01452 
01453 
01454 /**
01455  * Work-around function to call a function on the value of the pair.
01456  */
01457 static void
01458 call_apply_to_coll (gpointer name, gpointer coll, gpointer udata)
01459 {
01460     coll_call_infos_t *callinfos = (coll_call_infos_t*)udata;
01461 
01462     xmms_collection_apply_to_collection (callinfos->dag, coll,
01463                                          callinfos->func, callinfos->udata);
01464 }
01465 
01466 /**
01467  * Prepend the key string (name) to the udata list.
01468  */
01469 static void
01470 prepend_key_string (gpointer key, gpointer value, gpointer udata)
01471 {
01472     GList **list = (GList**)udata;
01473     *list = g_list_prepend (*list, xmmsv_new_string (key));
01474 }
01475 
01476 /**
01477  * Returns TRUE if the value of the pair is equal to the value stored
01478  * in the udata structure, and save the corresponding key in that
01479  * structure.
01480  */
01481 static gboolean
01482 value_match_save_key (gpointer key, gpointer val, gpointer udata)
01483 {
01484     gboolean found = FALSE;
01485     coll_table_pair_t *pair = (coll_table_pair_t*)udata;
01486     xmmsv_coll_t *coll = (xmmsv_coll_t*)val;
01487 
01488     /* value matching and key not ignored, found! */
01489     if ((coll == pair->value) &&
01490         (pair->key == NULL || strcmp (pair->key, key) != 0)) {
01491         pair->key = key;
01492         found = TRUE;
01493     }
01494 
01495     return found;
01496 }
01497 
01498 /**
01499  * If a reference, add the operator of the pointed collection as an
01500  * operand.
01501  */
01502 void
01503 bind_all_references (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata)
01504 {
01505     if (xmmsv_coll_get_type (coll) == XMMS_COLLECTION_TYPE_REFERENCE) {
01506         xmmsv_coll_t *target;
01507         gchar *target_name;
01508         gchar *target_namespace;
01509         gint   target_nsid;
01510 
01511         xmmsv_coll_attribute_get (coll, "reference", &target_name);
01512         xmmsv_coll_attribute_get (coll, "namespace", &target_namespace);
01513         if (target_name == NULL || target_namespace == NULL ||
01514             strcmp (target_name, "All Media") == 0) {
01515             return;
01516         }
01517 
01518         target_nsid = xmms_collection_get_namespace_id (target_namespace);
01519         if (target_nsid == XMMS_COLLECTION_NSID_INVALID) {
01520             return;
01521         }
01522 
01523         target = xmms_collection_get_pointer (dag, target_name, target_nsid);
01524         if (target == NULL) {
01525             return;
01526         }
01527 
01528         xmmsv_coll_add_operand (coll, target);
01529     }
01530 }
01531 
01532 /**
01533  * If a reference, rebind the given operator to the new operator
01534  * representing the referenced collection (pointers and so are in the
01535  * udata structure).
01536  */
01537 static void
01538 rebind_references (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata)
01539 {
01540     if (xmmsv_coll_get_type (coll) == XMMS_COLLECTION_TYPE_REFERENCE) {
01541         coll_rebind_infos_t *infos;
01542 
01543         gchar *target_name = NULL;
01544         gchar *target_namespace = NULL;
01545 
01546         infos = (coll_rebind_infos_t*)udata;
01547 
01548         /* FIXME: Or only compare operand vs oldtarget ? */
01549 
01550         xmmsv_coll_attribute_get (coll, "reference", &target_name);
01551         xmmsv_coll_attribute_get (coll, "namespace", &target_namespace);
01552         if (strcmp (infos->name, target_name) != 0 ||
01553             strcmp (infos->namespace, target_namespace) != 0) {
01554             return;
01555         }
01556 
01557         xmmsv_coll_remove_operand (coll, infos->oldtarget);
01558         xmmsv_coll_add_operand (coll, infos->newtarget);
01559     }
01560 }
01561 
01562 /**
01563  * If a reference with matching name, rename it according to the
01564  * rename infos in the udata structure.
01565  */
01566 static void
01567 rename_references (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata)
01568 {
01569     if (xmmsv_coll_get_type (coll) == XMMS_COLLECTION_TYPE_REFERENCE) {
01570         coll_rename_infos_t *infos;
01571 
01572         gchar *target_name = NULL;
01573         gchar *target_namespace = NULL;
01574 
01575         infos = (coll_rename_infos_t*)udata;
01576 
01577         xmmsv_coll_attribute_get (coll, "reference", &target_name);
01578         xmmsv_coll_attribute_get (coll, "namespace", &target_namespace);
01579         if (strcmp (infos->oldname, target_name) == 0 &&
01580             strcmp (infos->namespace, target_namespace) == 0) {
01581             xmmsv_coll_attribute_set (coll, "reference", infos->newname);
01582         }
01583     }
01584 }
01585 
01586 /**
01587  * Strip reference operators to the given collection by rebinding the
01588  * parent directly to the pointed operator.
01589  */
01590 static void
01591 strip_references (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata)
01592 {
01593     xmmsv_coll_t *op;
01594     coll_rebind_infos_t *infos;
01595     gchar *target_name = NULL;
01596     gchar *target_namespace = NULL;
01597     xmmsv_list_iter_t *iter;
01598     xmmsv_t *tmp;
01599 
01600     infos = (coll_rebind_infos_t*)udata;
01601 
01602     xmmsv_get_list_iter (xmmsv_coll_operands_get (coll), &iter);
01603     for (xmmsv_list_iter_first (iter);
01604          xmmsv_list_iter_valid (iter);
01605          xmmsv_list_iter_next (iter)) {
01606 
01607         xmmsv_list_iter_entry (iter, &tmp);
01608         xmmsv_get_coll (tmp, &op);
01609 
01610         /* Skip if not potential reference */
01611         if (xmmsv_coll_get_type (op) != XMMS_COLLECTION_TYPE_REFERENCE) {
01612             continue;
01613         }
01614 
01615         xmmsv_coll_attribute_get (op, "reference", &target_name);
01616         xmmsv_coll_attribute_get (op, "namespace", &target_namespace);
01617         if (strcmp (infos->name, target_name) != 0 ||
01618             strcmp (infos->namespace, target_namespace) != 0) {
01619             continue;
01620         }
01621 
01622         /* Rebind coll to ref'd operand directly, effectively strip reference */
01623         /* FIXME: Do we really need to do this _clear? */
01624         xmmsv_list_clear (xmmsv_coll_operands_get (op));
01625 
01626         xmmsv_list_iter_remove (iter);
01627 
01628         tmp = xmmsv_new_coll (infos->oldtarget);
01629         xmmsv_list_iter_insert (iter, tmp);
01630         xmmsv_unref (tmp);
01631     }
01632     xmmsv_list_iter_explicit_destroy (iter);
01633 }
01634 
01635 /**
01636  * Check if the current operator is a reference to a given collection,
01637  * and if so, update the structure passed as userdata.
01638  */
01639 static void
01640 check_for_reference (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, xmmsv_coll_t *parent, void *udata)
01641 {
01642     coll_refcheck_t *check = (coll_refcheck_t*)udata;
01643     if (xmmsv_coll_get_type (coll) == XMMS_COLLECTION_TYPE_REFERENCE && !check->found) {
01644         gchar *target_name, *target_namespace;
01645 
01646         xmmsv_coll_attribute_get (coll, "reference", &target_name);
01647         xmmsv_coll_attribute_get (coll, "namespace", &target_namespace);
01648         if (strcmp (check->target_name, target_name) == 0 &&
01649             strcmp (check->target_namespace, target_namespace) == 0) {
01650             check->found = TRUE;
01651         } else {
01652             xmmsv_coll_t *op;
01653             xmmsv_t *tmp;
01654 
01655             if (xmmsv_list_get (xmmsv_coll_operands_get (coll), 0, &tmp)) {
01656                 xmmsv_get_coll (tmp, &op);
01657                 xmms_collection_apply_to_collection_recurs (dag, op, coll,
01658                                                             check_for_reference,
01659                                                             udata);
01660             }
01661         }
01662     }
01663 }
01664 
01665 
01666 /** Forwarding function to fix type warnings.
01667  *
01668  * @param coll  The collection to unref.
01669  */
01670 static void
01671 coll_unref (void *coll)
01672 {
01673     xmmsv_coll_unref (coll);
01674 }
01675 
01676 
01677 
01678 /* ============  FIND / COLLECTION MATCH FUNCTIONS ============ */
01679 
01680 /* Generate a build_match hashtable, states initialized to UNCHECKED. */
01681 static void
01682 build_match_table (gpointer key, gpointer value, gpointer udata)
01683 {
01684     GHashTable *match_table = udata;
01685     coll_find_state_t *match = g_new (coll_find_state_t, 1);
01686     *match = XMMS_COLLECTION_FIND_STATE_UNCHECKED;
01687     g_hash_table_replace (match_table, g_strdup (key), match);
01688 }
01689 
01690 /* Return the first unchecked element from the match_table, set the
01691  * udata pointer to contain the key of that element.
01692  */
01693 static gboolean
01694 find_unchecked (gpointer name, gpointer value, gpointer udata)
01695 {
01696     coll_find_state_t *match = value;
01697     gchar **open = udata;
01698     *open = name;
01699     return (*match == XMMS_COLLECTION_FIND_STATE_UNCHECKED);
01700 }
01701 
01702 /* Build a list of all matched entries of the match_table in the udata
01703  * pointer.
01704  */
01705 static void
01706 build_list_matches (gpointer key, gpointer value, gpointer udata)
01707 {
01708     gchar *coll_name = key;
01709     coll_find_state_t *state = value;
01710     GList **list = udata;
01711     if (*state == XMMS_COLLECTION_FIND_STATE_MATCH) {
01712         *list = g_list_prepend (*list, xmmsv_new_string (coll_name));
01713     }
01714 }
01715 
01716 /** Determine whether the mediainfos match the given collection.
01717  *
01718  * @param dag  The collection DAG.
01719  * @param mediainfo  The properties of the media to match against.
01720  * @param coll  The collection to match with the mediainfos.
01721  * @param nsid  The namespace id of the collection.
01722  * @param match_table  The match_table for all collections in that namespace.
01723  * @return  TRUE if the collection matches, FALSE otherwise.
01724  */
01725 static gboolean
01726 xmms_collection_media_match (xmms_coll_dag_t *dag, GHashTable *mediainfo,
01727                              xmmsv_coll_t *coll, guint nsid,
01728                              GHashTable *match_table)
01729 {
01730     gboolean match = FALSE;
01731     xmmsv_coll_t *op;
01732     gchar *attr1 = NULL, *attr2 = NULL;
01733     xmmsv_t *val;
01734     xmms_medialib_entry_t entry, id;
01735     xmmsv_list_iter_t *iter;
01736 
01737     switch (xmmsv_coll_get_type (coll)) {
01738     case XMMS_COLLECTION_TYPE_REFERENCE:
01739         if (xmmsv_coll_attribute_get (coll, "reference", &attr1)) {
01740             if (strcmp (attr1, "All Media") == 0) {
01741                 match = TRUE;
01742             } else if (xmmsv_coll_attribute_get (coll, "namespace", &attr2)) {
01743                 match = xmms_collection_media_match_reference (dag, mediainfo,
01744                                                                coll, nsid,
01745                                                                match_table,
01746                                                                attr1, attr2);
01747             }
01748         }
01749         break;
01750 
01751     case XMMS_COLLECTION_TYPE_UNION:
01752         /* if ANY matches */
01753         xmmsv_get_list_iter (xmmsv_coll_operands_get (coll), &iter);
01754 
01755         for (xmmsv_list_iter_first (iter);
01756              !match && xmmsv_list_iter_valid (iter);
01757              xmmsv_list_iter_next (iter)) {
01758 
01759             xmmsv_list_iter_entry (iter, &val);
01760             xmmsv_get_coll (val, &op);
01761 
01762             match = xmms_collection_media_match (dag, mediainfo, op,
01763                                                  nsid, match_table);
01764         }
01765         xmmsv_list_iter_explicit_destroy (iter);
01766         break;
01767 
01768     case XMMS_COLLECTION_TYPE_INTERSECTION:
01769         /* if ALL match */
01770         match = TRUE;
01771         xmmsv_get_list_iter (xmmsv_coll_operands_get (coll), &iter);
01772 
01773         for (xmmsv_list_iter_first (iter);
01774              match && xmmsv_list_iter_valid (iter);
01775              xmmsv_list_iter_next (iter)) {
01776 
01777             xmmsv_list_iter_entry (iter, &val);
01778             xmmsv_get_coll (val, &op);
01779 
01780             match = xmms_collection_media_match (dag, mediainfo, op,
01781                                                  nsid, match_table);
01782         }
01783         xmmsv_list_iter_explicit_destroy (iter);
01784         break;
01785 
01786     case XMMS_COLLECTION_TYPE_COMPLEMENT:
01787         /* invert result from operand */
01788         match = !xmms_collection_media_match_operand (dag, mediainfo, coll,
01789                                                       nsid, match_table);
01790         break;
01791 
01792     case XMMS_COLLECTION_TYPE_HAS:
01793         match = xmms_collection_media_filter_has (dag, mediainfo, coll,
01794                                                   nsid, match_table);
01795         break;
01796 
01797     case XMMS_COLLECTION_TYPE_EQUALS:
01798         match = xmms_collection_media_filter_equals (dag, mediainfo, coll,
01799                                                     nsid, match_table);
01800         break;
01801 
01802     case XMMS_COLLECTION_TYPE_MATCH:
01803         match = xmms_collection_media_filter_match (dag, mediainfo, coll,
01804                                                        nsid, match_table);
01805         break;
01806 
01807     case XMMS_COLLECTION_TYPE_SMALLER:
01808         match = xmms_collection_media_filter_smaller (dag, mediainfo, coll,
01809                                                       nsid, match_table);
01810         break;
01811 
01812     case XMMS_COLLECTION_TYPE_GREATER:
01813         match = xmms_collection_media_filter_greater (dag, mediainfo, coll,
01814                                                       nsid, match_table);
01815         break;
01816 
01817     case XMMS_COLLECTION_TYPE_IDLIST:
01818     case XMMS_COLLECTION_TYPE_QUEUE:
01819     case XMMS_COLLECTION_TYPE_PARTYSHUFFLE:
01820         /* check if id in idlist */
01821         val = g_hash_table_lookup (mediainfo, "id");
01822         if (val != NULL) {
01823             xmmsv_get_int (val, &id);
01824 
01825             xmmsv_get_list_iter (xmmsv_coll_idlist_get (coll), &iter);
01826             for (xmmsv_list_iter_first (iter);
01827                  xmmsv_list_iter_valid (iter);
01828                  xmmsv_list_iter_next (iter)) {
01829 
01830                 xmmsv_list_iter_entry_int (iter, &entry);
01831                 if (entry == id) {
01832                     match = TRUE;
01833                     break;
01834                 }
01835             }
01836             xmmsv_list_iter_explicit_destroy (iter);
01837         }
01838         break;
01839 
01840     /* invalid type */
01841     default:
01842         XMMS_DBG ("invalid collection operator in xmms_collection_media_match");
01843         g_assert_not_reached ();
01844         break;
01845     }
01846 
01847     return match;
01848 }
01849 
01850 /** Determine whether the mediainfos match the given reference operator.
01851  *
01852  * @param dag  The collection DAG.
01853  * @param mediainfo  The properties of the media to match against.
01854  * @param coll  The collection (ref op) to match with the mediainfos.
01855  * @param nsid  The namespace id of the collection.
01856  * @param match_table  The match_table for all collections in that namespace.
01857  * @param refname  The name of the referenced collection.
01858  * @param refns  The namespace of the referenced collection.
01859  * @return  TRUE if the collection matches, FALSE otherwise.
01860  */
01861 static gboolean
01862 xmms_collection_media_match_reference (xmms_coll_dag_t *dag, GHashTable *mediainfo,
01863                                        xmmsv_coll_t *coll, guint nsid,
01864                                        GHashTable *match_table,
01865                                        const gchar *refname, const gchar *refns)
01866 {
01867     gboolean match;
01868     guint refnsid;
01869     coll_find_state_t *matchstate;
01870 
01871     /* Same NS, should be in the match table */
01872     refnsid = xmms_collection_get_namespace_id (refns);
01873     if (refnsid == nsid) {
01874         matchstate = g_hash_table_lookup (match_table, refname);
01875         if (*matchstate == XMMS_COLLECTION_FIND_STATE_UNCHECKED) {
01876             /* Check ref'd collection match status and save it */
01877             matchstate = g_new (coll_find_state_t, 1);
01878             match = xmms_collection_media_match_operand (dag,
01879                                                          mediainfo,
01880                                                          coll, nsid,
01881                                                          match_table);
01882 
01883             if (match) {
01884                 *matchstate = XMMS_COLLECTION_FIND_STATE_MATCH;
01885             } else {
01886                 *matchstate = XMMS_COLLECTION_FIND_STATE_NOMATCH;
01887             }
01888 
01889             g_hash_table_replace (match_table, g_strdup (refname), matchstate);
01890 
01891         } else {
01892             match = (*matchstate == XMMS_COLLECTION_FIND_STATE_MATCH);
01893         }
01894 
01895     /* In another NS, just check if it matches */
01896     } else {
01897         match = xmms_collection_media_match_operand (dag, mediainfo, coll,
01898                                                      nsid, match_table);
01899     }
01900 
01901     return match;
01902 }
01903 
01904 /** Determine whether the mediainfos match the first operand of the
01905  * given operator.
01906  *
01907  * @param dag  The collection DAG.
01908  * @param mediainfo  The properties of the media to match against.
01909  * @param coll  Match the mediainfos with the operand of that collection.
01910  * @param nsid  The namespace id of the collection.
01911  * @param match_table  The match_table for all collections in that namespace.
01912  * @return  TRUE if the collection matches, FALSE otherwise.
01913  */
01914 static gboolean
01915 xmms_collection_media_match_operand (xmms_coll_dag_t *dag, GHashTable *mediainfo,
01916                                      xmmsv_coll_t *coll, guint nsid,
01917                                      GHashTable *match_table)
01918 {
01919     xmmsv_coll_t *op;
01920     xmmsv_t *tmp;
01921     gboolean match = FALSE;
01922 
01923     if (xmmsv_list_get (xmmsv_coll_operands_get (coll), 0, &tmp)) {
01924         xmmsv_get_coll (tmp, &op);
01925 
01926         match = xmms_collection_media_match (dag, mediainfo, op, nsid, match_table);
01927     }
01928 
01929     return match;
01930 }
01931 
01932 /** Get all the properties for the given media.
01933  *
01934  * @param mid  The id of the media.
01935  * @return  A HashTable with all the properties.
01936  */
01937 static GHashTable *
01938 xmms_collection_media_info (xmms_medialib_entry_t mid, xmms_error_t *err)
01939 {
01940     GList *res;
01941     GList *n;
01942     GHashTable *infos;
01943     gchar *name;
01944     const gchar *buf;
01945     xmmsv_t *cmdval;
01946     xmmsv_t *value;
01947     guint state;
01948 
01949     /* FIXME: could probably reuse tree from medialib_info directly. ignores sources? */
01950     res = xmms_medialib_info_list (NULL, mid, err);
01951 
01952     /* Transform the list into a HashMap */
01953     infos = g_hash_table_new_full (g_str_hash, g_str_equal,
01954                                    g_free, (GDestroyNotify) xmmsv_unref);
01955     for (state = 0, n = res; n; state = (state + 1) % 3, n = n->next) {
01956         switch (state) {
01957         case 0:  /* source */
01958             break;
01959 
01960         case 1:  /* prop name */
01961             cmdval = n->data;
01962             xmmsv_get_string (cmdval, &buf);
01963             name = g_strdup (buf);
01964             break;
01965 
01966         case 2:  /* prop value */
01967             value = xmmsv_ref (n->data);
01968 
01969             /* Only insert the first source */
01970             if (g_hash_table_lookup (infos, name) == NULL) {
01971                 g_hash_table_replace (infos, name, value);
01972             }
01973             break;
01974         }
01975 
01976         xmmsv_unref (n->data);
01977     }
01978 
01979     g_list_free (res);
01980 
01981     return infos;
01982 }
01983 
01984 /** Get the string associated to the property of the mediainfo
01985  *  identified by the "field" attribute of the collection.
01986  *
01987  * @return  The property value as a string.
01988  */
01989 static gboolean
01990 filter_get_mediainfo_field_string (xmmsv_coll_t *coll,
01991                                    GHashTable *mediainfo, gchar **val)
01992 {
01993     gboolean retval = FALSE;
01994     gchar *attr;
01995     xmmsv_t *cmdval;
01996 
01997     if (xmmsv_coll_attribute_get (coll, "field", &attr)) {
01998         cmdval = g_hash_table_lookup (mediainfo, attr);
01999         if (cmdval != NULL) {
02000             switch (xmmsv_get_type (cmdval)) {
02001             case XMMSV_TYPE_STRING:
02002             {
02003                 const gchar *s;
02004                 xmmsv_get_string (cmdval, &s);
02005                 *val = g_strdup (s);
02006                 retval = TRUE;
02007                 break;
02008             }
02009             case XMMSV_TYPE_INT32:
02010             {
02011                 gint i;
02012                 xmmsv_get_int (cmdval, &i);
02013                 *val = g_strdup_printf ("%d", i);
02014                 retval = TRUE;
02015                 break;
02016             }
02017             default:
02018                 break;
02019             }
02020         }
02021     }
02022 
02023     return retval;
02024 }
02025 
02026 /** Get the integer associated to the property of the mediainfo
02027  *  identified by the "field" attribute of the collection.
02028  *
02029  * @return  The property value as an integer.
02030  */
02031 static gboolean
02032 filter_get_mediainfo_field_int (xmmsv_coll_t *coll, GHashTable *mediainfo, gint *val)
02033 {
02034     gboolean retval = FALSE;
02035     gchar *attr;
02036     xmmsv_t *cmdval;
02037 
02038     if (xmmsv_coll_attribute_get (coll, "field", &attr)) {
02039         cmdval = g_hash_table_lookup (mediainfo, attr);
02040         if (cmdval != NULL && xmmsv_get_type (cmdval) == XMMSV_TYPE_INT32) {
02041             xmmsv_get_int (cmdval, val);
02042             retval = TRUE;
02043         }
02044     }
02045 
02046     return retval;
02047 }
02048 
02049 /* Get the string value of the "value" attribute of the collection. */
02050 static gboolean
02051 filter_get_operator_value_string (xmmsv_coll_t *coll, const gchar **val)
02052 {
02053     gchar *attr;
02054     gboolean valid;
02055 
02056     valid = xmmsv_coll_attribute_get (coll, "value", &attr);
02057     if (valid) {
02058         *val = attr;
02059     }
02060 
02061     return valid;
02062 }
02063 
02064 /* Get the integer value of the "value" attribute of the collection. */
02065 static gboolean
02066 filter_get_operator_value_int (xmmsv_coll_t *coll, gint *val)
02067 {
02068     gint buf;
02069     gboolean valid;
02070 
02071     valid = xmms_collection_get_int_attr (coll, "value", &buf);
02072     if (valid) {
02073         *val = buf;
02074     }
02075 
02076     return valid;
02077 }
02078 
02079 /* Check whether the given operator has the "case-sensitive" attribute
02080  * or not. */
02081 static gboolean
02082 filter_get_operator_case (xmmsv_coll_t *coll, gboolean *val)
02083 {
02084     gchar *attr;
02085 
02086     if (xmmsv_coll_attribute_get (coll, "case-sensitive", &attr)) {
02087         *val = (strcmp (attr, "true") == 0);
02088     }
02089     else {
02090         *val = FALSE;
02091     }
02092 
02093     return TRUE;
02094 }
02095 
02096 /* Check whether the HAS filter operator matches the mediainfo. */
02097 static gboolean
02098 xmms_collection_media_filter_has (xmms_coll_dag_t *dag, GHashTable *mediainfo,
02099                                   xmmsv_coll_t *coll, guint nsid,
02100                                   GHashTable *match_table)
02101 {
02102     gboolean match = FALSE;
02103     gchar *mediaval;
02104 
02105     /* If operator matches, recurse upwards in the operand */
02106     if (filter_get_mediainfo_field_string (coll, mediainfo, &mediaval)) {
02107         match = xmms_collection_media_match_operand (dag, mediainfo, coll,
02108                                                      nsid, match_table);
02109 
02110         g_free (mediaval);
02111     }
02112 
02113     return match;
02114 }
02115 
02116 /* Check whether the MATCH filter operator matches the mediainfo. */
02117 static gboolean
02118 xmms_collection_media_filter_equals (xmms_coll_dag_t *dag, GHashTable *mediainfo,
02119                                     xmmsv_coll_t *coll, guint nsid,
02120                                     GHashTable *match_table)
02121 {
02122     gboolean match = FALSE;
02123     gchar *mediaval = NULL;
02124     const gchar *opval;
02125     gboolean case_sens;
02126 
02127     if (filter_get_mediainfo_field_string (coll, mediainfo, &mediaval) &&
02128         filter_get_operator_value_string (coll, &opval) &&
02129         filter_get_operator_case (coll, &case_sens)) {
02130 
02131         if (case_sens) {
02132             match = (strcmp (mediaval, opval) == 0);
02133         } else {
02134             match = (g_ascii_strcasecmp (mediaval, opval) == 0);
02135         }
02136     }
02137 
02138     /* If operator matches, recurse upwards in the operand */
02139     if (match) {
02140         match = xmms_collection_media_match_operand (dag, mediainfo, coll,
02141                                                      nsid, match_table);
02142     }
02143 
02144     if (mediaval != NULL) {
02145         g_free (mediaval);
02146     }
02147 
02148     return match;
02149 }
02150 
02151 /* Check whether the MATCH filter operator matches the mediainfo. */
02152 static gboolean
02153 xmms_collection_media_filter_match (xmms_coll_dag_t *dag, GHashTable *mediainfo,
02154                                        xmmsv_coll_t *coll, guint nsid,
02155                                        GHashTable *match_table)
02156 {
02157     gboolean match = FALSE;
02158     gchar *buf, *opval, *mediaval;
02159     const gchar *s;
02160     gboolean case_sens;
02161 
02162     if (filter_get_mediainfo_field_string (coll, mediainfo, &buf) &&
02163         filter_get_operator_value_string (coll, &s) &&
02164         filter_get_operator_case (coll, &case_sens)) {
02165 
02166         /* Prepare values */
02167         if (case_sens) {
02168             opval = g_strdup (s);
02169             mediaval = g_strdup (buf);
02170         } else {
02171             opval = g_utf8_strdown (s, -1);
02172             mediaval = g_utf8_strdown (buf, -1);
02173         }
02174 
02175         match = g_pattern_match_simple (opval, mediaval);
02176 
02177         g_free (buf);
02178         g_free (opval);
02179         g_free (mediaval);
02180 
02181         /* If operator matches, recurse upwards in the operand */
02182         if (match) {
02183             match = xmms_collection_media_match_operand (dag, mediainfo, coll,
02184                                                          nsid, match_table);
02185         }
02186     }
02187 
02188     return match;
02189 }
02190 
02191 /* Check whether the SMALLER filter operator matches the mediainfo. */
02192 static gboolean
02193 xmms_collection_media_filter_smaller (xmms_coll_dag_t *dag, GHashTable *mediainfo,
02194                                       xmmsv_coll_t *coll, guint nsid,
02195                                       GHashTable *match_table)
02196 {
02197     gboolean match = FALSE;
02198     gint mediaval;
02199     gint opval;
02200 
02201     /* If operator matches, recurse upwards in the operand */
02202     if (filter_get_mediainfo_field_int (coll, mediainfo, &mediaval) &&
02203         filter_get_operator_value_int (coll, &opval) &&
02204         (mediaval < opval) ) {
02205 
02206         match = xmms_collection_media_match_operand (dag, mediainfo, coll,
02207                                                      nsid, match_table);
02208     }
02209 
02210     return match;
02211 }
02212 
02213 /* Check whether the GREATER filter operator matches the mediainfo. */
02214 static gboolean
02215 xmms_collection_media_filter_greater (xmms_coll_dag_t *dag, GHashTable *mediainfo,
02216                                       xmmsv_coll_t *coll, guint nsid,
02217                                       GHashTable *match_table)
02218 {
02219     gboolean match = FALSE;
02220     gint mediaval;
02221     gint opval;
02222 
02223     /* If operator matches, recurse upwards in the operand */
02224     if (filter_get_mediainfo_field_int (coll, mediainfo, &mediaval) &&
02225         filter_get_operator_value_int (coll, &opval) &&
02226         (mediaval > opval) ) {
02227 
02228         match = xmms_collection_media_match_operand (dag, mediainfo, coll,
02229                                                      nsid, match_table);
02230     }
02231 
02232     return match;
02233 }