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