• Main Page
  • Related Pages
  • Modules
  • Data Structures
  • Files
  • File List
  • Globals

src/xmms/collection.c

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

Generated on Wed Feb 9 2011 for XMMS2 by  doxygen 1.7.1