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