XMMS2
src/xmms/playlist.c
Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2011 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  */
00016 
00017 
00018 /** @file
00019  *  Controls playlist
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 #include <ctype.h>
00029 
00030 #include "xmmspriv/xmms_playlist.h"
00031 #include "xmms/xmms_ipc.h"
00032 #include "xmms/xmms_config.h"
00033 #include "xmmspriv/xmms_medialib.h"
00034 #include "xmmspriv/xmms_collection.h"
00035 #include "xmms/xmms_log.h"
00036 /*
00037 #include "xmms/plsplugins.h"
00038 #include "xmms/util.h"
00039 #include "xmms/signal_xmms.h"
00040 #include "xmms/ipc.h"
00041 #include "xmms/mediainfo.h"
00042 #include "xmms/magic.h"
00043 */
00044 static void xmms_playlist_destroy (xmms_object_t *object);
00045 static void xmms_playlist_client_shuffle (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
00046 static void xmms_playlist_client_clear (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
00047 static void xmms_playlist_client_sort (xmms_playlist_t *playlist, const gchar *plname, xmmsv_t *property, xmms_error_t *err);
00048 static GList * xmms_playlist_client_list_entries (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
00049 static gchar *xmms_playlist_client_current_active (xmms_playlist_t *playlist, xmms_error_t *err);
00050 static void xmms_playlist_destroy (xmms_object_t *object);
00051 
00052 static void xmms_playlist_client_add_id (xmms_playlist_t *playlist, const gchar *plname, xmms_medialib_entry_t file, xmms_error_t *error);
00053 static void xmms_playlist_client_add_url (xmms_playlist_t *playlist, const gchar *plname, const gchar *nurl, xmms_error_t *err);
00054 static void xmms_playlist_client_add_idlist (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *coll, xmms_error_t *err);
00055 static void xmms_playlist_client_add_collection (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *coll, xmmsv_t *order, xmms_error_t *err);
00056 static GTree * xmms_playlist_client_current_pos (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
00057 static gint xmms_playlist_client_set_next (xmms_playlist_t *playlist, gint32 pos, xmms_error_t *error);
00058 static void xmms_playlist_client_remove_entry (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, xmms_error_t *err);
00059 static gboolean xmms_playlist_remove_unlocked (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *plcoll, guint pos, xmms_error_t *err);
00060 static void xmms_playlist_client_move_entry (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, gint32 newpos, xmms_error_t *err);
00061 static gint xmms_playlist_client_set_next_rel (xmms_playlist_t *playlist, gint32 pos, xmms_error_t *error);
00062 static gint xmms_playlist_set_current_position_do (xmms_playlist_t *playlist, guint32 pos, xmms_error_t *err);
00063 
00064 static void xmms_playlist_client_insert_url (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, const gchar *url, xmms_error_t *error);
00065 static void xmms_playlist_client_insert_id (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, xmms_medialib_entry_t file, xmms_error_t *error);
00066 static void xmms_playlist_client_insert_collection (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, xmmsv_coll_t *coll, xmmsv_t *order, xmms_error_t *error);
00067 static void xmms_playlist_client_radd (xmms_playlist_t *playlist, const gchar *plname, const gchar *path, xmms_error_t *error);
00068 static void xmms_playlist_client_rinsert (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, const gchar *path, xmms_error_t *error);
00069 
00070 static void xmms_playlist_client_load (xmms_playlist_t *, const gchar *, xmms_error_t *);
00071 
00072 static xmmsv_coll_t *xmms_playlist_get_coll (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *error);
00073 static const gchar *xmms_playlist_canonical_name (xmms_playlist_t *playlist, const gchar *plname);
00074 static gint xmms_playlist_coll_get_currpos (xmmsv_coll_t *plcoll);
00075 static gint xmms_playlist_coll_get_size (xmmsv_coll_t *plcoll);
00076 
00077 static void xmms_playlist_update_queue (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *coll);
00078 static void xmms_playlist_update_partyshuffle (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *coll);
00079 static void xmms_playlist_register_ipc_commands (xmms_object_t *playlist_object);
00080 
00081 static void xmms_playlist_current_pos_msg_send (xmms_playlist_t *playlist, GTree *dict);
00082 static GTree * xmms_playlist_current_pos_msg_new (xmms_playlist_t *playlist, guint32 pos, const gchar *plname);
00083 
00084 #define XMMS_PLAYLIST_CHANGED_MSG(type, id, name) xmms_playlist_changed_msg_send (playlist, xmms_playlist_changed_msg_new (playlist, type, id, name))
00085 #define XMMS_PLAYLIST_CURRPOS_MSG(pos, name) xmms_playlist_current_pos_msg_send (playlist, xmms_playlist_current_pos_msg_new (playlist, pos, name))
00086 
00087 
00088 /** @defgroup Playlist Playlist
00089   * @ingroup XMMSServer
00090   * @brief This is the playlist control.
00091   *
00092   * A playlist is a central thing in the XMMS server, it
00093   * tells us what to do after we played the following entry
00094   * @{
00095   */
00096 
00097 /** Playlist structure */
00098 struct xmms_playlist_St {
00099     xmms_object_t object;
00100 
00101     /* playlists are in the collection DAG */
00102     xmms_coll_dag_t *colldag;
00103 
00104     gboolean repeat_one;
00105     gboolean repeat_all;
00106 
00107     GMutex *mutex;
00108 
00109     xmms_mediainfo_reader_t *mediainfordr;
00110 
00111     gboolean update_flag;
00112     xmms_medialib_t *medialib;
00113 };
00114 
00115 #include "playlist_ipc.c"
00116 
00117 static void
00118 on_playlist_r_all_changed (xmms_object_t *object, xmmsv_t *_data,
00119                            gpointer udata)
00120 {
00121     xmms_playlist_t *playlist = udata;
00122     gint value;
00123 
00124     value = xmms_config_property_get_int ((xmms_config_property_t *) object);
00125 
00126     g_mutex_lock (playlist->mutex);
00127     playlist->repeat_all = !!value;
00128     g_mutex_unlock (playlist->mutex);
00129 }
00130 
00131 static void
00132 on_playlist_r_one_changed (xmms_object_t *object, xmmsv_t *_data,
00133                            gpointer udata)
00134 {
00135     xmms_playlist_t *playlist = udata;
00136     gint value;
00137 
00138     value = xmms_config_property_get_int ((xmms_config_property_t *) object);
00139 
00140     g_mutex_lock (playlist->mutex);
00141     playlist->repeat_one = !!value;
00142     g_mutex_unlock (playlist->mutex);
00143 }
00144 
00145 
00146 static void
00147 on_playlist_updated (xmms_object_t *object, const gchar *plname)
00148 {
00149     xmmsv_coll_t *plcoll;
00150     xmms_playlist_t *playlist = (xmms_playlist_t*)object;
00151 
00152     /* Already in an update process, quit */
00153     if (playlist->update_flag) {
00154         return;
00155     }
00156 
00157     plcoll = xmms_playlist_get_coll (playlist, plname, NULL);
00158     if (plcoll == NULL) {
00159         return;
00160     } else {
00161         /* Run the update function if appropriate */
00162         switch (xmmsv_coll_get_type (plcoll)) {
00163         case XMMS_COLLECTION_TYPE_QUEUE:
00164             xmms_playlist_update_queue (playlist, plname, plcoll);
00165             break;
00166 
00167         case XMMS_COLLECTION_TYPE_PARTYSHUFFLE:
00168             xmms_playlist_update_partyshuffle (playlist, plname, plcoll);
00169             break;
00170 
00171         default:
00172             break;
00173         }
00174     }
00175 }
00176 
00177 static void
00178 on_playlist_updated_pos (xmms_object_t *object, xmmsv_t *val, gpointer udata)
00179 {
00180     XMMS_DBG ("PLAYLIST: updated pos!");
00181     on_playlist_updated (object, XMMS_ACTIVE_PLAYLIST);
00182 }
00183 
00184 static void
00185 on_playlist_updated_chg (xmms_object_t *object, xmmsv_t *val, gpointer udata)
00186 {
00187     const gchar *plname = NULL;
00188     xmmsv_t *pl_val;
00189 
00190     XMMS_DBG ("PLAYLIST: updated chg!");
00191 
00192     xmmsv_dict_get (val, "name", &pl_val);
00193     if (pl_val != NULL) {
00194         xmmsv_get_string (pl_val, &plname);
00195     } else {
00196         /* FIXME: occurs? */
00197         XMMS_DBG ("PLAYLIST: updated_chg, NULL playlist!");
00198         g_assert_not_reached ();
00199     }
00200 
00201     on_playlist_updated (object, plname);
00202 }
00203 
00204 static void
00205 xmms_playlist_update_queue (xmms_playlist_t *playlist, const gchar *plname,
00206                             xmmsv_coll_t *coll)
00207 {
00208     gint history, currpos;
00209 
00210     XMMS_DBG ("PLAYLIST: update-queue!");
00211 
00212     if (!xmms_collection_get_int_attr (coll, "history", &history)) {
00213         history = 0;
00214     }
00215 
00216     playlist->update_flag = TRUE;
00217     currpos = xmms_playlist_coll_get_currpos (coll);
00218     while (currpos > history) {
00219         xmms_playlist_remove_unlocked (playlist, plname, coll, 0, NULL);
00220         currpos = xmms_playlist_coll_get_currpos (coll);
00221     }
00222     playlist->update_flag = FALSE;
00223 }
00224 
00225 static void
00226 xmms_playlist_update_partyshuffle (xmms_playlist_t *playlist,
00227                                    const gchar *plname, xmmsv_coll_t *coll)
00228 {
00229     gint history, upcoming, currpos, size;
00230     xmmsv_coll_t *src;
00231     xmmsv_t *tmp;
00232 
00233     XMMS_DBG ("PLAYLIST: update-partyshuffle!");
00234 
00235     if (!xmms_collection_get_int_attr (coll, "history", &history)) {
00236         history = 0;
00237     }
00238 
00239     if (!xmms_collection_get_int_attr (coll, "upcoming", &upcoming)) {
00240         upcoming = XMMS_DEFAULT_PARTYSHUFFLE_UPCOMING;
00241     }
00242 
00243     playlist->update_flag = TRUE;
00244     currpos = xmms_playlist_coll_get_currpos (coll);
00245     while (currpos > history) {
00246         xmms_playlist_remove_unlocked (playlist, plname, coll, 0, NULL);
00247         currpos = xmms_playlist_coll_get_currpos (coll);
00248     }
00249 
00250     if (!xmmsv_list_get (xmmsv_coll_operands_get (coll), 0, &tmp)) {
00251         XMMS_DBG ("Cannot find party shuffle operand!");
00252         return;
00253     }
00254     xmmsv_get_coll (tmp, &src);
00255 
00256     currpos = xmms_playlist_coll_get_currpos (coll);
00257     size = xmms_playlist_coll_get_size (coll);
00258     while (size < currpos + 1 + upcoming) {
00259         xmms_medialib_entry_t randentry;
00260         randentry = xmms_collection_get_random_media (playlist->colldag, src);
00261         if (randentry == 0) {
00262             break;  /* No media found in the collection, give up */
00263         }
00264         /* FIXME: add_collection might yield better perf here. */
00265         xmms_playlist_add_entry_unlocked (playlist, plname, coll, randentry, NULL);
00266 
00267         currpos = xmms_playlist_coll_get_currpos (coll);
00268         size = xmms_playlist_coll_get_size (coll);
00269     }
00270     playlist->update_flag = FALSE;
00271 }
00272 
00273 /**
00274  * Initializes a new xmms_playlist_t.
00275  */
00276 xmms_playlist_t *
00277 xmms_playlist_init (void)
00278 {
00279     xmms_playlist_t *ret;
00280     xmms_config_property_t *val;
00281 
00282     ret = xmms_object_new (xmms_playlist_t, xmms_playlist_destroy);
00283     ret->mutex = g_mutex_new ();
00284 
00285     xmms_playlist_register_ipc_commands (XMMS_OBJECT (ret));
00286 
00287     val = xmms_config_property_register ("playlist.repeat_one", "0",
00288                                          on_playlist_r_one_changed, ret);
00289     ret->repeat_one = xmms_config_property_get_int (val);
00290 
00291     val = xmms_config_property_register ("playlist.repeat_all", "0",
00292                                       on_playlist_r_all_changed, ret);
00293     ret->repeat_all = xmms_config_property_get_int (val);
00294 
00295     ret->update_flag = FALSE;
00296 
00297     xmms_object_connect (XMMS_OBJECT (ret),
00298                          XMMS_IPC_SIGNAL_PLAYLIST_CHANGED,
00299                          on_playlist_updated_chg, ret);
00300 
00301     xmms_object_connect (XMMS_OBJECT (ret),
00302                          XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS,
00303                          on_playlist_updated_pos, ret);
00304 
00305 
00306     ret->medialib = xmms_medialib_init (ret);
00307     ret->colldag = xmms_collection_init (ret);
00308     ret->mediainfordr = xmms_mediainfo_reader_start ();
00309 
00310     return ret;
00311 }
00312 
00313 static gboolean
00314 xmms_playlist_advance_do (xmms_playlist_t *playlist)
00315 {
00316     gint size, currpos;
00317     gboolean ret = TRUE;
00318     xmmsv_coll_t *plcoll;
00319     char *jumplist;
00320     xmms_error_t err;
00321     xmms_playlist_t *buffer = playlist;
00322     guint newpos;
00323 
00324     xmms_error_reset (&err);
00325 
00326     plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, NULL);
00327     if (plcoll == NULL) {
00328         ret = FALSE;
00329     } else if ((size = xmms_playlist_coll_get_size (plcoll)) == 0) {
00330         if (xmmsv_coll_attribute_get (plcoll, "jumplist", &jumplist)) {
00331             xmms_playlist_client_load (buffer, jumplist, &err);
00332             if (xmms_error_isok (&err)) {
00333                 ret = xmms_playlist_advance_do (playlist);
00334             } else {
00335                 ret = FALSE;
00336             }
00337         } else {
00338             ret = FALSE;
00339         }
00340     } else if (!playlist->repeat_one) {
00341         currpos = xmms_playlist_coll_get_currpos (plcoll);
00342         currpos++;
00343 
00344         if (currpos == size && !playlist->repeat_all &&
00345             xmmsv_coll_attribute_get (plcoll, "jumplist", &jumplist)) {
00346 
00347             xmms_collection_set_int_attr (plcoll, "position", -1);
00348             XMMS_PLAYLIST_CURRPOS_MSG (-1, XMMS_ACTIVE_PLAYLIST);
00349 
00350             xmms_playlist_client_load (buffer, jumplist, &err);
00351             if (xmms_error_isok (&err)) {
00352                 ret = xmms_playlist_advance_do (playlist);
00353             } else {
00354                 ret = FALSE;
00355             }
00356         } else {
00357             newpos = currpos%size;
00358             xmms_collection_set_int_attr (plcoll, "position", newpos);
00359             XMMS_PLAYLIST_CURRPOS_MSG (newpos, XMMS_ACTIVE_PLAYLIST);
00360             ret = (currpos != size) || playlist->repeat_all;
00361         }
00362     }
00363 
00364     return ret;
00365 }
00366 
00367 /**
00368  * Go to next song in playlist according to current playlist mode.
00369  * xmms_playlist_current_entry is to be used to retrieve the entry.
00370  *
00371  * @sa xmms_playlist_current_entry
00372  *
00373  * @returns FALSE if end of playlist is reached, TRUE otherwise.
00374  */
00375 gboolean
00376 xmms_playlist_advance (xmms_playlist_t *playlist)
00377 {
00378     gboolean ret;
00379 
00380     g_return_val_if_fail (playlist, FALSE);
00381 
00382     g_mutex_lock (playlist->mutex);
00383     ret = xmms_playlist_advance_do (playlist);
00384     g_mutex_unlock (playlist->mutex);
00385 
00386     return ret;
00387 }
00388 
00389 /**
00390  * Retrieve the currently active xmms_medialib_entry_t.
00391  *
00392  */
00393 xmms_medialib_entry_t
00394 xmms_playlist_current_entry (xmms_playlist_t *playlist)
00395 {
00396     gint size, currpos;
00397     xmmsv_coll_t *plcoll;
00398     xmms_medialib_entry_t ent = 0;
00399 
00400     g_return_val_if_fail (playlist, 0);
00401 
00402     g_mutex_lock (playlist->mutex);
00403 
00404     plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, NULL);
00405     if (plcoll == NULL) {
00406         /* FIXME: What happens? */
00407         g_mutex_unlock (playlist->mutex);
00408         return 0;
00409     }
00410 
00411     currpos = xmms_playlist_coll_get_currpos (plcoll);
00412     size = xmms_playlist_coll_get_size (plcoll);
00413 
00414     if (currpos == -1 && (size > 0)) {
00415         currpos = 0;
00416         xmms_collection_set_int_attr (plcoll, "position", currpos);
00417         XMMS_PLAYLIST_CURRPOS_MSG (0, XMMS_ACTIVE_PLAYLIST);
00418     }
00419 
00420     if (currpos < size) {
00421         xmmsv_coll_idlist_get_index (plcoll, currpos, &ent);
00422     } else {
00423         ent = 0;
00424     }
00425 
00426     g_mutex_unlock (playlist->mutex);
00427 
00428     return ent;
00429 }
00430 
00431 
00432 /**
00433  * Retrieve the position of the currently active xmms_medialib_entry_t
00434  *
00435  */
00436 GTree *
00437 xmms_playlist_client_current_pos (xmms_playlist_t *playlist, const gchar *plname,
00438                                   xmms_error_t *err)
00439 {
00440     guint32 pos;
00441     xmmsv_coll_t *plcoll;
00442     GTree *dict;
00443 
00444     g_return_val_if_fail (playlist, 0);
00445 
00446     g_mutex_lock (playlist->mutex);
00447 
00448     plcoll = xmms_playlist_get_coll (playlist, plname, err);
00449     if (plcoll == NULL) {
00450         g_mutex_unlock (playlist->mutex);
00451         xmms_error_set (err, XMMS_ERROR_INVAL, "no such playlist");
00452         return 0;
00453     }
00454 
00455     pos = xmms_playlist_coll_get_currpos (plcoll);
00456     if (pos == -1) {
00457         xmms_error_set (err, XMMS_ERROR_GENERIC, "no current entry");
00458     }
00459 
00460     g_mutex_unlock (playlist->mutex);
00461 
00462     dict = xmms_playlist_current_pos_msg_new (playlist, pos, plname);
00463 
00464     return dict;
00465 }
00466 
00467 /**
00468  * Retrieve a copy of the name of the currently active playlist.
00469  *
00470  */
00471 static gchar *
00472 xmms_playlist_client_current_active (xmms_playlist_t *playlist, xmms_error_t *err)
00473 {
00474     gchar *name = NULL;
00475     xmmsv_coll_t *active_coll;
00476 
00477     g_return_val_if_fail (playlist, 0);
00478 
00479     g_mutex_lock (playlist->mutex);
00480 
00481     active_coll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
00482     if (active_coll != NULL) {
00483         const gchar *alias;
00484 
00485         alias = xmms_collection_find_alias (playlist->colldag,
00486                                             XMMS_COLLECTION_NSID_PLAYLISTS,
00487                                             active_coll, XMMS_ACTIVE_PLAYLIST);
00488         if (alias == NULL) {
00489             xmms_error_set (err, XMMS_ERROR_GENERIC, "active playlist not referenced!");
00490         } else {
00491             name = g_strdup (alias);
00492         }
00493     } else {
00494         xmms_error_set (err, XMMS_ERROR_GENERIC, "no active playlist");
00495     }
00496 
00497     g_mutex_unlock (playlist->mutex);
00498 
00499     return name;
00500 }
00501 
00502 
00503 static void
00504 xmms_playlist_client_load (xmms_playlist_t *playlist, const gchar *name, xmms_error_t *err)
00505 {
00506     xmmsv_coll_t *plcoll, *active_coll;
00507 
00508     if (strcmp (name, XMMS_ACTIVE_PLAYLIST) == 0) {
00509         xmms_error_set (err, XMMS_ERROR_INVAL, "invalid playlist to load");
00510         return;
00511     }
00512 
00513     active_coll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
00514     if (active_coll == NULL) {
00515         xmms_error_set (err, XMMS_ERROR_GENERIC, "no active playlist");
00516         return;
00517     }
00518 
00519     plcoll = xmms_playlist_get_coll (playlist, name, err);
00520     if (plcoll == NULL) {
00521         xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist");
00522         return;
00523     }
00524 
00525     if (active_coll == plcoll) {
00526         XMMS_DBG ("Not loading %s playlist, already active!", name);
00527         return;
00528     }
00529 
00530     XMMS_DBG ("Loading new playlist! %s", name);
00531     xmms_collection_update_pointer (playlist->colldag, XMMS_ACTIVE_PLAYLIST,
00532                                     XMMS_COLLECTION_NSID_PLAYLISTS, plcoll);
00533 
00534     xmms_object_emit_f (XMMS_OBJECT (playlist),
00535                         XMMS_IPC_SIGNAL_PLAYLIST_LOADED,
00536                         XMMSV_TYPE_STRING,
00537                         name);
00538 }
00539 
00540 static inline void
00541 swap_entries (xmmsv_coll_t *coll, gint i, gint j)
00542 {
00543     xmms_medialib_entry_t tmp, tmp2;
00544 
00545     xmmsv_coll_idlist_get_index (coll, i, &tmp);
00546     xmmsv_coll_idlist_get_index (coll, j, &tmp2);
00547 
00548     xmmsv_coll_idlist_set_index (coll, i, tmp2);
00549     xmmsv_coll_idlist_set_index (coll, j, tmp);
00550 }
00551 
00552 
00553 /**
00554  * Shuffle the playlist.
00555  *
00556  */
00557 static void
00558 xmms_playlist_client_shuffle (xmms_playlist_t *playlist, const gchar *plname,
00559                               xmms_error_t *err)
00560 {
00561     guint j,i;
00562     gint len, currpos;
00563     xmmsv_coll_t *plcoll;
00564 
00565     g_return_if_fail (playlist);
00566 
00567     g_mutex_lock (playlist->mutex);
00568 
00569     plcoll = xmms_playlist_get_coll (playlist, plname, err);
00570     if (plcoll == NULL) {
00571         xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist");
00572         g_mutex_unlock (playlist->mutex);
00573         return;
00574     }
00575 
00576     currpos = xmms_playlist_coll_get_currpos (plcoll);
00577     len = xmms_playlist_coll_get_size (plcoll);
00578     if (len > 1) {
00579         /* put current at top and exclude from shuffling */
00580         if (currpos != -1) {
00581             swap_entries (plcoll, 0, currpos);
00582             currpos = 0;
00583             xmms_collection_set_int_attr (plcoll, "position", currpos);
00584         }
00585 
00586         /* knuth <3 */
00587         for (i = currpos + 1; i < len; i++) {
00588             j = g_random_int_range (i, len);
00589 
00590             if (i != j) {
00591                 swap_entries (plcoll, i, j);
00592             }
00593         }
00594 
00595     }
00596 
00597     XMMS_PLAYLIST_CHANGED_MSG (XMMS_PLAYLIST_CHANGED_SHUFFLE, 0, plname);
00598     XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
00599 
00600     g_mutex_unlock (playlist->mutex);
00601 }
00602 
00603 static gboolean
00604 xmms_playlist_remove_unlocked (xmms_playlist_t *playlist, const gchar *plname,
00605                                xmmsv_coll_t *plcoll, guint pos, xmms_error_t *err)
00606 {
00607     gint currpos;
00608     GTree *dict;
00609 
00610     g_return_val_if_fail (playlist, FALSE);
00611 
00612     currpos = xmms_playlist_coll_get_currpos (plcoll);
00613 
00614     if (!xmmsv_coll_idlist_remove (plcoll, pos)) {
00615         if (err) xmms_error_set (err, XMMS_ERROR_NOENT, "Entry was not in list!");
00616         return FALSE;
00617     }
00618 
00619     dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_REMOVE, 0, plname);
00620     g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
00621     xmms_playlist_changed_msg_send (playlist, dict);
00622 
00623     /* decrease currentpos if removed entry was before or if it's
00624      * the current entry, but only if currentpos is a valid entry.
00625      */
00626     if (currpos != -1 && pos <= currpos) {
00627         currpos--;
00628         xmms_collection_set_int_attr (plcoll, "position", currpos);
00629         XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
00630     }
00631 
00632     return TRUE;
00633 }
00634 
00635 typedef struct {
00636     xmms_playlist_t *pls;
00637     xmms_medialib_entry_t entry;
00638 } playlist_remove_info_t;
00639 
00640 static void
00641 remove_from_playlist (gpointer key, gpointer value, gpointer udata)
00642 {
00643     playlist_remove_info_t *rminfo = (playlist_remove_info_t *) udata;
00644     guint32 i;
00645     xmms_medialib_entry_t val;
00646     gint size;
00647     xmmsv_coll_t *plcoll = (xmmsv_coll_t *) value;
00648 
00649     size = xmms_playlist_coll_get_size (plcoll);
00650     for (i = 0; i < size; i++) {
00651         if (xmmsv_coll_idlist_get_index (plcoll, i, &val) && val == rminfo->entry) {
00652             XMMS_DBG ("removing entry on pos %d in %s", i, (gchar *)key);
00653             xmms_playlist_remove_unlocked (rminfo->pls, (gchar *)key, plcoll, i, NULL);
00654             i--; /* reset it */
00655         }
00656     }
00657 }
00658 
00659 
00660 
00661 /**
00662  * Remove all additions of entry in the playlist
00663  *
00664  * @param playlist the playlist to remove entries from
00665  * @param entry the playlist entry to remove
00666  *
00667  * @sa xmms_playlist_remove
00668  */
00669 gboolean
00670 xmms_playlist_remove_by_entry (xmms_playlist_t *playlist,
00671                                xmms_medialib_entry_t entry)
00672 {
00673     playlist_remove_info_t rminfo;
00674     g_return_val_if_fail (playlist, FALSE);
00675 
00676     g_mutex_lock (playlist->mutex);
00677 
00678     rminfo.pls = playlist;
00679     rminfo.entry = entry;
00680 
00681     xmms_collection_foreach_in_namespace (playlist->colldag,
00682                                           XMMS_COLLECTION_NSID_PLAYLISTS,
00683                                           remove_from_playlist, &rminfo);
00684 
00685     g_mutex_unlock (playlist->mutex);
00686 
00687     return TRUE;
00688 }
00689 
00690 /**
00691  * Remove an entry from playlist.
00692  *
00693  */
00694 void
00695 xmms_playlist_client_remove_entry (xmms_playlist_t *playlist,
00696                                    const gchar *plname,
00697                                    gint32 pos, xmms_error_t *err)
00698 {
00699     gboolean ret = FALSE;
00700     xmmsv_coll_t *plcoll;
00701 
00702     g_return_if_fail (playlist);
00703 
00704     g_mutex_lock (playlist->mutex);
00705     plcoll = xmms_playlist_get_coll (playlist, plname, err);
00706     if (plcoll != NULL) {
00707         ret = xmms_playlist_remove_unlocked (playlist, plname, plcoll, pos, err);
00708     }
00709     g_mutex_unlock (playlist->mutex);
00710 }
00711 
00712 
00713 /**
00714  * Move an entry in playlist
00715  *
00716  */
00717 static void
00718 xmms_playlist_client_move_entry (xmms_playlist_t *playlist,
00719                                  const gchar *plname, gint32 pos,
00720                                  gint32 newpos, xmms_error_t *err)
00721 {
00722     GTree *dict;
00723     xmms_medialib_entry_t id;
00724     gint currpos, size;
00725     gint64 ipos, inewpos;
00726     xmmsv_coll_t *plcoll;
00727 
00728     g_return_if_fail (playlist);
00729 
00730     XMMS_DBG ("Moving %d, to %d", pos, newpos);
00731 
00732     g_mutex_lock (playlist->mutex);
00733 
00734     plcoll = xmms_playlist_get_coll (playlist, plname, err);
00735     if (plcoll == NULL) {
00736         /* FIXME: happens ? */
00737         g_mutex_unlock (playlist->mutex);
00738         return;
00739     }
00740 
00741     currpos = xmms_playlist_coll_get_currpos (plcoll);
00742     size = xmms_playlist_coll_get_size (plcoll);
00743 
00744     if (size == 0 || newpos > (size - 1)) {
00745         xmms_error_set (err, XMMS_ERROR_NOENT,
00746                         "Cannot move entry outside playlist");
00747         g_mutex_unlock (playlist->mutex);
00748         return;
00749     }
00750 
00751     if (!xmmsv_coll_idlist_move (plcoll, pos, newpos)) {
00752         xmms_error_set (err, XMMS_ERROR_NOENT, "Entry was not in list!");
00753         g_mutex_unlock (playlist->mutex);
00754         return;
00755     }
00756 
00757     /* Update the current position pointer */
00758     ipos = pos;
00759     inewpos = newpos;
00760     if (inewpos <= currpos && ipos > currpos)
00761         currpos++;
00762     else if (inewpos >= currpos && ipos < currpos)
00763         currpos--;
00764     else if (ipos == currpos)
00765         currpos = inewpos;
00766 
00767     xmms_collection_set_int_attr (plcoll, "position", currpos);
00768 
00769     xmmsv_coll_idlist_get_index (plcoll, newpos, &id);
00770 
00771     dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_MOVE, id, plname);
00772     g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
00773     g_tree_insert (dict, (gpointer) "newposition", xmmsv_new_int (newpos));
00774     xmms_playlist_changed_msg_send (playlist, dict);
00775 
00776     XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
00777 
00778     g_mutex_unlock (playlist->mutex);
00779 
00780     return;
00781 
00782 }
00783 
00784 /**
00785  * Insert an entry into the playlist at given position.
00786  * Creates a #xmms_medialib_entry for you and insert it
00787  * in the list.
00788  *
00789  * @param playlist the playlist to add it URL to.
00790  * @param pos the position where the entry is inserted.
00791  * @param url the URL to add.
00792  * @param err an #xmms_error_t that should be defined upon error.
00793  * @return TRUE on success and FALSE otherwise.
00794  *
00795  */
00796 static void
00797 xmms_playlist_client_insert_url (xmms_playlist_t *playlist, const gchar *plname,
00798                                  gint32 pos, const gchar *url, xmms_error_t *err)
00799 {
00800     xmms_medialib_entry_t entry = 0;
00801     xmms_medialib_session_t *session = xmms_medialib_begin_write ();
00802 
00803     entry = xmms_medialib_entry_new_encoded (session, url, err);
00804     xmms_medialib_end (session);
00805 
00806     if (!entry) {
00807         return;
00808     }
00809 
00810     xmms_playlist_client_insert_id (playlist, plname, pos, entry, err);
00811 }
00812 
00813 /**
00814   * Convenient function for inserting a directory at a given position
00815   * in the playlist, It will dive down the URL you feed it and
00816   * recursivly insert all files.
00817   *
00818   * @param playlist the playlist to add it URL to.
00819   * @param plname the name of the playlist to modify.
00820   * @param pos a position in the playlist.
00821   * @param nurl the URL of an directory you want to add
00822   * @param err an #xmms_error_t that should be defined upon error.
00823   */
00824 static void
00825 xmms_playlist_client_rinsert (xmms_playlist_t *playlist, const gchar *plname, gint32 pos,
00826                               const gchar *path, xmms_error_t *err)
00827 {
00828     /* we actually just call the medialib function, but keep
00829      * the ipc method here for not confusing users / developers
00830      */
00831     xmms_medialib_insert_recursive (playlist->medialib, plname, pos, path, err);
00832 }
00833 
00834 /**
00835  * Insert an xmms_medialib_entry to the playlist at given position.
00836  *
00837  * @param playlist the playlist to add the entry to.
00838  * @param pos the position where the entry is inserted.
00839  * @param file the #xmms_medialib_entry to add.
00840  * @param error Upon error this will be set.
00841  * @returns TRUE on success and FALSE otherwise.
00842  */
00843 static void
00844 xmms_playlist_client_insert_id (xmms_playlist_t *playlist, const gchar *plname,
00845                                 gint32 pos, xmms_medialib_entry_t file,
00846                                 xmms_error_t *err)
00847 {
00848     if (!xmms_medialib_check_id (file)) {
00849         xmms_error_set (err, XMMS_ERROR_NOENT,
00850                         "That is not a valid medialib id!");
00851         return;
00852     }
00853 
00854     xmms_playlist_insert_entry (playlist, plname, pos, file, err);
00855 }
00856 
00857 static void
00858 xmms_playlist_client_insert_collection (xmms_playlist_t *playlist, const gchar *plname,
00859                                         gint32 pos, xmmsv_coll_t *coll,
00860                                         xmmsv_t *order, xmms_error_t *err)
00861 {
00862     GList *res;
00863 
00864     res = xmms_collection_query_ids (playlist->colldag, coll, 0, 0, order, err);
00865 
00866     while (res) {
00867         xmmsv_t *val = (xmmsv_t*) res->data;
00868         gint id;
00869         xmmsv_get_int (val, &id);
00870         xmms_playlist_client_insert_id (playlist, plname, pos, id, err);
00871         xmmsv_unref (val);
00872 
00873         res = g_list_delete_link (res, res);
00874         pos++;
00875     }
00876 
00877 }
00878 
00879 /**
00880  * Insert an entry at a given position in the playlist without
00881  * validating it.
00882  *
00883  * @internal
00884  */
00885 void
00886 xmms_playlist_insert_entry (xmms_playlist_t *playlist, const gchar *plname,
00887                             guint32 pos, xmms_medialib_entry_t file,
00888                             xmms_error_t *err)
00889 {
00890     GTree *dict;
00891     gint currpos;
00892     gint len;
00893     xmmsv_coll_t *plcoll;
00894 
00895     g_mutex_lock (playlist->mutex);
00896 
00897     plcoll = xmms_playlist_get_coll (playlist, plname, err);
00898     if (plcoll == NULL) {
00899         /* FIXME: happens ? */
00900         g_mutex_unlock (playlist->mutex);
00901         return;
00902     }
00903 
00904     len = xmms_playlist_coll_get_size (plcoll);
00905     if (pos > len) {
00906         xmms_error_set (err, XMMS_ERROR_GENERIC,
00907                         "Could not insert entry outside of playlist!");
00908         g_mutex_unlock (playlist->mutex);
00909         return;
00910     }
00911     xmmsv_coll_idlist_insert (plcoll, pos, file);
00912 
00913     /** propagate the MID ! */
00914     dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_INSERT, file, plname);
00915     g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
00916     xmms_playlist_changed_msg_send (playlist, dict);
00917 
00918     /** update position once client is familiar with the new item. */
00919     currpos = xmms_playlist_coll_get_currpos (plcoll);
00920     if (pos <= currpos) {
00921         currpos++;
00922         xmms_collection_set_int_attr (plcoll, "position", currpos);
00923         XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
00924     }
00925 
00926     g_mutex_unlock (playlist->mutex);
00927 }
00928 
00929 /**
00930   * Convenient function for adding a URL to the playlist,
00931   * Creates a #xmms_medialib_entry_t for you and adds it
00932   * to the list.
00933   *
00934   * @param playlist the playlist to add it URL to.
00935   * @param plname the name of the playlist to modify.
00936   * @param nurl the URL to add
00937   * @param err an #xmms_error_t that should be defined upon error.
00938   * @return TRUE on success and FALSE otherwise.
00939   */
00940 void
00941 xmms_playlist_client_add_url (xmms_playlist_t *playlist, const gchar *plname,
00942                               const gchar *nurl, xmms_error_t *err)
00943 {
00944     xmms_medialib_entry_t entry = 0;
00945     xmms_medialib_session_t *session = xmms_medialib_begin_write ();
00946 
00947     entry = xmms_medialib_entry_new_encoded (session, nurl, err);
00948     xmms_medialib_end (session);
00949 
00950     if (entry) {
00951         xmms_playlist_add_entry (playlist, plname, entry, err);
00952     }
00953 
00954 }
00955 
00956 /**
00957   * Convenient function for adding a directory to the playlist,
00958   * It will dive down the URL you feed it and recursivly add
00959   * all files there.
00960   *
00961   * @param playlist the playlist to add it URL to.
00962   * @param plname the name of the playlist to modify.
00963   * @param nurl the URL of an directory you want to add
00964   * @param err an #xmms_error_t that should be defined upon error.
00965   */
00966 static void
00967 xmms_playlist_client_radd (xmms_playlist_t *playlist, const gchar *plname,
00968                            const gchar *path, xmms_error_t *err)
00969 {
00970     /* we actually just call the medialib function, but keep
00971      * the ipc method here for not confusing users / developers
00972      */
00973     xmms_medialib_add_recursive (playlist->medialib, plname, path, err);
00974 }
00975 
00976 /** Adds a xmms_medialib_entry to the playlist.
00977  *
00978  *  This will append or prepend the entry according to
00979  *  the option.
00980  *  This function will wake xmms_playlist_wait.
00981  *
00982  * @param playlist the playlist to add the entry to.
00983  * @param plname the name of the playlist to modify.
00984  * @param file the #xmms_medialib_entry_t to add
00985  * @param err Upon error this will be set.
00986  * @returns TRUE on success
00987  */
00988 
00989 void
00990 xmms_playlist_client_add_id (xmms_playlist_t *playlist, const gchar *plname,
00991                              xmms_medialib_entry_t file, xmms_error_t *err)
00992 {
00993     if (!xmms_medialib_check_id (file)) {
00994         xmms_error_set (err, XMMS_ERROR_NOENT,
00995                         "That is not a valid medialib id!");
00996         return;
00997     }
00998 
00999     xmms_playlist_add_entry (playlist, plname, file, err);
01000 }
01001 
01002 void
01003 xmms_playlist_client_add_idlist (xmms_playlist_t *playlist,
01004                                  const gchar *plname,
01005                                  xmmsv_coll_t *coll, xmms_error_t *err)
01006 {
01007     xmms_medialib_entry_t entry;
01008     xmmsv_list_iter_t *it;
01009 
01010     xmmsv_get_list_iter (xmmsv_coll_idlist_get (coll), &it);
01011     for (xmmsv_list_iter_first (it);
01012          xmmsv_list_iter_valid (it);
01013          xmmsv_list_iter_next (it)) {
01014 
01015         xmmsv_list_iter_entry_int (it, &entry);
01016         if (!xmms_medialib_check_id (entry)) {
01017             xmms_error_set (err, XMMS_ERROR_NOENT,
01018                             "Idlist contains invalid medialib id!");
01019             xmmsv_list_iter_explicit_destroy (it);
01020             return;
01021         }
01022     }
01023 
01024     for (xmmsv_list_iter_first (it);
01025          xmmsv_list_iter_valid (it);
01026          xmmsv_list_iter_next (it)) {
01027 
01028         xmmsv_list_iter_entry_int (it, &entry);
01029         xmms_playlist_add_entry (playlist, plname, entry, err);
01030     }
01031     xmmsv_list_iter_explicit_destroy (it);
01032 
01033 }
01034 
01035 void
01036 xmms_playlist_client_add_collection (xmms_playlist_t *playlist, const gchar *plname,
01037                                      xmmsv_coll_t *coll, xmmsv_t *order,
01038                                      xmms_error_t *err)
01039 {
01040     GList *res;
01041 
01042     res = xmms_collection_query_ids (playlist->colldag, coll, 0, 0, order, err);
01043 
01044     while (res) {
01045         xmmsv_t *val = (xmmsv_t*) res->data;
01046         gint id;
01047         xmmsv_get_int (val, &id);
01048         xmms_playlist_add_entry (playlist, plname, id, err);
01049         xmmsv_unref (val);
01050 
01051         res = g_list_delete_link (res, res);
01052     }
01053 
01054 }
01055 
01056 /**
01057  * Add an entry to the playlist without validating it.
01058  *
01059  * @internal
01060  */
01061 void
01062 xmms_playlist_add_entry (xmms_playlist_t *playlist, const gchar *plname,
01063                          xmms_medialib_entry_t file, xmms_error_t *err)
01064 {
01065     xmmsv_coll_t *plcoll;
01066 
01067     g_mutex_lock (playlist->mutex);
01068 
01069     plcoll = xmms_playlist_get_coll (playlist, plname, err);
01070     if (plcoll != NULL) {
01071         xmms_playlist_add_entry_unlocked (playlist, plname, plcoll, file, err);
01072     }
01073 
01074     g_mutex_unlock (playlist->mutex);
01075 
01076 }
01077 
01078 /**
01079  * Add an entry to the playlist without locking the mutex.
01080  */
01081 void
01082 xmms_playlist_add_entry_unlocked (xmms_playlist_t *playlist,
01083                                   const gchar *plname,
01084                                   xmmsv_coll_t *plcoll,
01085                                   xmms_medialib_entry_t file,
01086                                   xmms_error_t *err)
01087 {
01088     gint prev_size;
01089     GTree *dict;
01090 
01091     prev_size = xmms_playlist_coll_get_size (plcoll);
01092     xmmsv_coll_idlist_append (plcoll, file);
01093 
01094     /** propagate the MID ! */
01095     dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_ADD, file, plname);
01096     g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (prev_size));
01097     xmms_playlist_changed_msg_send (playlist, dict);
01098 }
01099 
01100 /** Clear the playlist */
01101 static void
01102 xmms_playlist_client_clear (xmms_playlist_t *playlist, const gchar *plname,
01103                             xmms_error_t *err)
01104 {
01105     xmmsv_coll_t *plcoll;
01106 
01107     g_return_if_fail (playlist);
01108 
01109     g_mutex_lock (playlist->mutex);
01110 
01111     plcoll = xmms_playlist_get_coll (playlist, plname, err);
01112     if (plcoll == NULL) {
01113         g_mutex_unlock (playlist->mutex);
01114         return;
01115     }
01116 
01117     xmmsv_coll_idlist_clear (plcoll);
01118     xmms_collection_set_int_attr (plcoll, "position", -1);
01119 
01120     XMMS_PLAYLIST_CHANGED_MSG (XMMS_PLAYLIST_CHANGED_CLEAR, 0, plname);
01121     g_mutex_unlock (playlist->mutex);
01122 
01123 }
01124 
01125 
01126 /** Set the nextentry pointer in the playlist.
01127  *
01128  *  This will set the pointer for the next entry to be
01129  *  returned by xmms_playlist_advance. This function
01130  *  will also wake xmms_playlist_wait
01131  */
01132 
01133 static gint
01134 xmms_playlist_set_current_position_do (xmms_playlist_t *playlist, guint32 pos,
01135                                        xmms_error_t *err)
01136 {
01137     gint size;
01138     xmms_medialib_entry_t mid;
01139     xmmsv_coll_t *plcoll;
01140     char *jumplist;
01141 
01142     g_return_val_if_fail (playlist, FALSE);
01143 
01144     plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
01145     if (plcoll == NULL) {
01146         return 0;
01147     }
01148 
01149     size = xmms_playlist_coll_get_size (plcoll);
01150 
01151     if (pos == size &&
01152         xmmsv_coll_attribute_get (plcoll, "jumplist", &jumplist)) {
01153 
01154         xmms_collection_set_int_attr (plcoll, "position", 0);
01155         XMMS_PLAYLIST_CURRPOS_MSG (0, XMMS_ACTIVE_PLAYLIST);
01156 
01157         xmms_playlist_client_load (playlist, jumplist, err);
01158         if (xmms_error_iserror (err)) {
01159             return 0;
01160         }
01161 
01162         plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
01163         if (plcoll == NULL) {
01164             return 0;
01165         }
01166     } else if (pos < size) {
01167         XMMS_DBG ("newpos! %d", pos);
01168         xmms_collection_set_int_attr (plcoll, "position", pos);
01169         XMMS_PLAYLIST_CURRPOS_MSG (pos, XMMS_ACTIVE_PLAYLIST);
01170     } else {
01171         xmms_error_set (err, XMMS_ERROR_INVAL,
01172                         "Can't set pos outside the current playlist!");
01173         return 0;
01174     }
01175 
01176     xmmsv_coll_idlist_get_index (plcoll, pos, &mid);
01177 
01178     return mid;
01179 }
01180 
01181 gint
01182 xmms_playlist_client_set_next (xmms_playlist_t *playlist, gint32 pos,
01183                                xmms_error_t *err)
01184 {
01185     xmms_medialib_entry_t mid;
01186     g_return_val_if_fail (playlist, FALSE);
01187 
01188     g_mutex_lock (playlist->mutex);
01189     mid = xmms_playlist_set_current_position_do (playlist, pos, err);
01190     g_mutex_unlock (playlist->mutex);
01191 
01192     return mid;
01193 }
01194 
01195 static gint
01196 xmms_playlist_client_set_next_rel (xmms_playlist_t *playlist, gint32 pos,
01197                                    xmms_error_t *err)
01198 {
01199     gint currpos, newpos, size;
01200     xmms_medialib_entry_t mid = 0;
01201     xmmsv_coll_t *plcoll;
01202 
01203     g_return_val_if_fail (playlist, FALSE);
01204 
01205     g_mutex_lock (playlist->mutex);
01206 
01207     plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
01208     if (plcoll != NULL) {
01209         currpos = xmms_playlist_coll_get_currpos (plcoll);
01210 
01211         if (playlist->repeat_all) {
01212             newpos = pos + currpos;
01213             size = (gint) xmmsv_coll_idlist_get_size (plcoll);
01214 
01215             if (size > 0) {
01216                 newpos %= size;
01217                 if (newpos < 0) {
01218                     newpos += size;
01219                 }
01220             }
01221 
01222             mid = xmms_playlist_set_current_position_do (playlist, newpos, err);
01223         } else {
01224             if (currpos + pos >= 0) {
01225                 mid = xmms_playlist_set_current_position_do (playlist,
01226                                                              currpos + pos,
01227                                                              err);
01228             } else {
01229                 xmms_error_set (err, XMMS_ERROR_INVAL,
01230                                 "Can't set pos outside the current playlist!");
01231             }
01232         }
01233     }
01234 
01235     g_mutex_unlock (playlist->mutex);
01236 
01237     return mid;
01238 }
01239 
01240 typedef struct {
01241     xmms_medialib_entry_t id;
01242     guint position;
01243     GList *val;  /* List of (xmmsv_t *) prop values */
01244     gboolean current;
01245 } sortdata_t;
01246 
01247 
01248 /**
01249  * Sort helper function.
01250  * Performs a case insesitive comparation between two entries.
01251  * We compare each pair of values in the list of prop values.
01252  */
01253 static gint
01254 xmms_playlist_entry_compare (gconstpointer a, gconstpointer b, gpointer user_data)
01255 {
01256     GList *n1, *n2;
01257     xmmsv_t *val1, *val2, *properties, *propval;
01258     xmmsv_list_iter_t *propit;
01259     sortdata_t *data1 = (sortdata_t *) a;
01260     sortdata_t *data2 = (sortdata_t *) b;
01261     int s1, s2, res;
01262     const gchar *propstr, *str1, *str2;
01263 
01264     properties = (xmmsv_t *) user_data;
01265     for (n1 = data1->val, n2 = data2->val, xmmsv_get_list_iter (properties, &propit);
01266          n1 && n2 && xmmsv_list_iter_valid (propit);
01267          n1 = n1->next, n2 = n2->next, xmmsv_list_iter_next (propit)) {
01268 
01269         xmmsv_list_iter_entry (propit, &propval);
01270         xmmsv_get_string (propval, &propstr);
01271         if (propstr[0] == '-') {
01272             val2 = n1->data;
01273             val1 = n2->data;
01274         } else {
01275             val1 = n1->data;
01276             val2 = n2->data;
01277         }
01278 
01279         if (!val1) {
01280             if (!val2)
01281                 continue;
01282             else
01283                 return -1;
01284         }
01285 
01286         if (!val2) {
01287             return 1;
01288         }
01289 
01290         if (xmmsv_get_type (val1) == XMMSV_TYPE_STRING &&
01291             xmmsv_get_type (val2) == XMMSV_TYPE_STRING) {
01292             xmmsv_get_string (val1, &str1);
01293             xmmsv_get_string (val2, &str2);
01294             res = g_utf8_collate (str1, str2);
01295             /* keep comparing next pair if equal */
01296             if (res == 0)
01297                 continue;
01298             else
01299                 return res;
01300         }
01301 
01302         if (xmmsv_get_type (val1) == XMMSV_TYPE_INT32 &&
01303             xmmsv_get_type (val2) == XMMSV_TYPE_INT32)
01304         {
01305             xmmsv_get_int (val1, &s1);
01306             xmmsv_get_int (val2, &s2);
01307 
01308             if (s1 < s2)
01309                 return -1;
01310             else if (s1 > s2)
01311                 return 1;
01312             else
01313                 continue;  /* equal, compare next pair of properties */
01314         }
01315 
01316         XMMS_DBG ("Types in compare function differ to much");
01317 
01318         return 0;
01319     }
01320 
01321     /* all pairs matched, really equal! */
01322     return 0;
01323 }
01324 
01325 /**
01326  * Unwind helper function.
01327  * Frees the sortdata elements.
01328  */
01329 static void
01330 xmms_playlist_sorted_free (gpointer data, gpointer userdata)
01331 {
01332     GList *n;
01333     sortdata_t *sorted = (sortdata_t *) data;
01334 
01335     for (n = sorted->val; n; n = n->next) {
01336         if (n->data) {
01337             xmmsv_unref (n->data);
01338         }
01339     }
01340     g_list_free (sorted->val);
01341     g_free (sorted);
01342 }
01343 
01344 /**
01345  * Unwind helper function.
01346  * Fills the playlist with the new sorted data.
01347  */
01348 static void
01349 xmms_playlist_sorted_unwind (gpointer data, gpointer userdata)
01350 {
01351     gint size;
01352     sortdata_t *sorted = (sortdata_t *) data;
01353     xmmsv_coll_t *playlist = (xmmsv_coll_t *)userdata;
01354 
01355     xmmsv_coll_idlist_append (playlist, sorted->id);
01356 
01357     if (sorted->current) {
01358         size = xmmsv_coll_idlist_get_size (playlist);
01359         xmms_collection_set_int_attr (playlist, "position", size - 1);
01360     }
01361 
01362     xmms_playlist_sorted_free (sorted, NULL);
01363 }
01364 
01365 /** Sorts the playlist by properties.
01366  *
01367  *  This will sort the list.
01368  *  @param playlist The playlist to sort.
01369  *  @param properties Tells xmms_playlist_sort which properties it
01370  *  should use when sorting.
01371  *  @param err An #xmms_error_t - needed since xmms_playlist_sort is an ipc
01372  *  method handler.
01373  */
01374 
01375 static void
01376 xmms_playlist_client_sort (xmms_playlist_t *playlist, const gchar *plname,
01377                            xmmsv_t *properties, xmms_error_t *err)
01378 {
01379     guint32 i;
01380     GList *tmp = NULL, *n;
01381     sortdata_t *data;
01382     const gchar *str;
01383     xmmsv_t *val;
01384     xmms_medialib_session_t *session;
01385     gboolean list_changed = FALSE;
01386     xmmsv_coll_t *plcoll;
01387     gint currpos, size;
01388     xmmsv_t *valstr;
01389     xmmsv_list_iter_t *propit;
01390 
01391     g_return_if_fail (playlist);
01392     g_return_if_fail (properties);
01393 
01394     g_mutex_lock (playlist->mutex);
01395 
01396     plcoll = xmms_playlist_get_coll (playlist, plname, err);
01397     if (plcoll == NULL) {
01398         xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist!");
01399         g_mutex_unlock (playlist->mutex);
01400         return;
01401     }
01402 
01403     /* check for invalid property strings */
01404     if (!check_string_list (properties)) {
01405         xmms_error_set (err, XMMS_ERROR_NOENT,
01406                         "invalid list of properties to sort by!");
01407         g_mutex_unlock (playlist->mutex);
01408         return;
01409     }
01410 
01411     if (xmmsv_list_get_size (properties) < 1) {
01412         xmms_error_set (err, XMMS_ERROR_NOENT,
01413                         "empty list of properties to sort by!");
01414         g_mutex_unlock (playlist->mutex);
01415         return;
01416     }
01417 
01418     /* in debug, show the first ordering property */
01419     xmmsv_list_get (properties, 0, &valstr);
01420     xmmsv_get_string (valstr, &str);
01421     XMMS_DBG ("Sorting on %s (and maybe more)", str);
01422 
01423     currpos = xmms_playlist_coll_get_currpos (plcoll);
01424     size = xmms_playlist_coll_get_size (plcoll);
01425 
01426     /* check whether we need to do any sorting at all */
01427     if (size < 2) {
01428         g_mutex_unlock (playlist->mutex);
01429         return;
01430     }
01431 
01432     session = xmms_medialib_begin ();
01433 
01434     xmmsv_get_list_iter (properties, &propit);
01435     for (i = 0; i < size; i++) {
01436         data = g_new (sortdata_t, 1);
01437 
01438         xmmsv_coll_idlist_get_index (plcoll, i, &data->id);
01439         data->position = i;
01440 
01441         /* save the list of values corresponding to the list of sort props */
01442         data->val = NULL;
01443         for (xmmsv_list_iter_first (propit);
01444              xmmsv_list_iter_valid (propit);
01445              xmmsv_list_iter_next (propit)) {
01446 
01447             xmmsv_list_iter_entry (propit, &valstr);
01448             xmmsv_get_string (valstr, &str);
01449             if (str[0] == '-')
01450                 str++;
01451 
01452             val = xmms_medialib_entry_property_get_value (session,
01453                                                           data->id,
01454                                                           str);
01455 
01456             if (val && xmmsv_get_type (val) == XMMSV_TYPE_STRING) {
01457                 gchar *casefold;
01458                 /* replace val by casefolded-string (beware of new/free order)*/
01459                 xmmsv_get_string (val, &str);
01460                 casefold = g_utf8_casefold (str, strlen (str));
01461                 xmmsv_unref (val);
01462 
01463                 val = xmmsv_new_string (casefold);
01464                 g_free (casefold);
01465             }
01466 
01467             data->val = g_list_prepend (data->val, val);
01468         }
01469         data->val = g_list_reverse (data->val);
01470 
01471         data->current = (currpos == i);
01472 
01473         tmp = g_list_prepend (tmp, data);
01474     }
01475 
01476     xmms_medialib_end (session);
01477 
01478     tmp = g_list_reverse (tmp);
01479     tmp = g_list_sort_with_data (tmp, xmms_playlist_entry_compare, properties);
01480 
01481     /* check whether there was any change */
01482     for (i = 0, n = tmp; n; i++, n = g_list_next (n)) {
01483         if (((sortdata_t*)n->data)->position != i) {
01484             list_changed = TRUE;
01485             break;
01486         }
01487     }
01488 
01489     if (!list_changed) {
01490         g_list_foreach (tmp, xmms_playlist_sorted_free, NULL);
01491         g_list_free (tmp);
01492         g_mutex_unlock (playlist->mutex);
01493         return;
01494     }
01495 
01496     xmmsv_coll_idlist_clear (plcoll);
01497     g_list_foreach (tmp, xmms_playlist_sorted_unwind, plcoll);
01498 
01499     g_list_free (tmp);
01500 
01501     XMMS_PLAYLIST_CHANGED_MSG (XMMS_PLAYLIST_CHANGED_SORT, 0, plname);
01502     XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
01503 
01504     g_mutex_unlock (playlist->mutex);
01505 }
01506 
01507 
01508 /** List a playlist */
01509 static GList *
01510 xmms_playlist_client_list_entries (xmms_playlist_t *playlist, const gchar *plname,
01511                                    xmms_error_t *err)
01512 {
01513     GList *entries = NULL;
01514     xmmsv_coll_t *plcoll;
01515     xmms_medialib_entry_t entry;
01516     xmmsv_list_iter_t *it;
01517 
01518     g_return_val_if_fail (playlist, NULL);
01519 
01520     g_mutex_lock (playlist->mutex);
01521 
01522     plcoll = xmms_playlist_get_coll (playlist, plname, err);
01523     if (plcoll == NULL) {
01524         g_mutex_unlock (playlist->mutex);
01525         return NULL;
01526     }
01527 
01528     xmmsv_get_list_iter (xmmsv_coll_idlist_get (plcoll), &it);
01529     for (xmmsv_list_iter_first (it);
01530          xmmsv_list_iter_valid (it);
01531          xmmsv_list_iter_next (it)) {
01532 
01533         xmmsv_list_iter_entry_int (it, &entry);
01534         entries = g_list_prepend (entries, xmmsv_new_int (entry));
01535     }
01536     xmmsv_list_iter_explicit_destroy (it);
01537 
01538     g_mutex_unlock (playlist->mutex);
01539 
01540     entries = g_list_reverse (entries);
01541 
01542     return entries;
01543 }
01544 
01545 /** returns pointer to mediainfo reader. */
01546 xmms_mediainfo_reader_t *
01547 xmms_playlist_mediainfo_reader_get (xmms_playlist_t *playlist)
01548 {
01549     g_return_val_if_fail (playlist, NULL);
01550 
01551     return playlist->mediainfordr;
01552 }
01553 
01554 /** @} */
01555 
01556 /** Free the playlist and other memory in the xmms_playlist_t
01557  *
01558  *  This will free all entries in the list!
01559  */
01560 
01561 static void
01562 xmms_playlist_destroy (xmms_object_t *object)
01563 {
01564     xmms_config_property_t *val;
01565     xmms_playlist_t *playlist = (xmms_playlist_t *)object;
01566 
01567     g_return_if_fail (playlist);
01568 
01569     g_mutex_free (playlist->mutex);
01570 
01571     val = xmms_config_lookup ("playlist.repeat_one");
01572     xmms_config_property_callback_remove (val, on_playlist_r_one_changed, playlist);
01573     val = xmms_config_lookup ("playlist.repeat_all");
01574     xmms_config_property_callback_remove (val, on_playlist_r_all_changed, playlist);
01575 
01576     xmms_object_unref (playlist->colldag);
01577     xmms_object_unref (playlist->mediainfordr);
01578 
01579     xmms_playlist_unregister_ipc_commands ();
01580 }
01581 
01582 
01583 static xmmsv_coll_t *
01584 xmms_playlist_get_coll (xmms_playlist_t *playlist, const gchar *plname,
01585                         xmms_error_t *error)
01586 {
01587     xmmsv_coll_t *coll;
01588     coll = xmms_collection_get_pointer (playlist->colldag, plname,
01589                                         XMMS_COLLECTION_NSID_PLAYLISTS);
01590 
01591     if (coll == NULL && error != NULL) {
01592         xmms_error_set (error, XMMS_ERROR_INVAL, "invalid playlist name");
01593     }
01594 
01595     return coll;
01596 }
01597 
01598 /**
01599  *  Retrieve the canonical name of a playlist. Assumes the playlist
01600  * name is valid.
01601  */
01602 static const gchar *
01603 xmms_playlist_canonical_name (xmms_playlist_t *playlist, const gchar *plname)
01604 {
01605     const gchar *fullname;
01606 
01607     if (strcmp (plname, XMMS_ACTIVE_PLAYLIST) == 0) {
01608         xmmsv_coll_t *coll;
01609         coll = xmms_collection_get_pointer (playlist->colldag, plname,
01610                                             XMMS_COLLECTION_NSID_PLAYLISTS);
01611         fullname = xmms_collection_find_alias (playlist->colldag,
01612                                                XMMS_COLLECTION_NSID_PLAYLISTS,
01613                                                coll, plname);
01614     } else {
01615         fullname = plname;
01616     }
01617 
01618     return fullname;
01619 }
01620 
01621 /** Get the current position in the given playlist (set to -1 if absent). */
01622 static gint
01623 xmms_playlist_coll_get_currpos (xmmsv_coll_t *plcoll)
01624 {
01625     gint currpos;
01626 
01627     /* If absent, set to -1 and save it */
01628     if (!xmms_collection_get_int_attr (plcoll, "position", &currpos)) {
01629         currpos = -1;
01630         xmms_collection_set_int_attr (plcoll, "position", currpos);
01631     }
01632 
01633     return currpos;
01634 }
01635 
01636 /** Get the size of the given playlist (compute and update it if absent). */
01637 static gint
01638 xmms_playlist_coll_get_size (xmmsv_coll_t *plcoll)
01639 {
01640     return xmmsv_coll_idlist_get_size (plcoll);
01641 }
01642 
01643 
01644 GTree *
01645 xmms_playlist_changed_msg_new (xmms_playlist_t *playlist,
01646                                xmms_playlist_changed_actions_t type,
01647                                xmms_medialib_entry_t id, const gchar *plname)
01648 {
01649     GTree *dict;
01650     const gchar *tmp;
01651 
01652     dict = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
01653                             NULL, (GDestroyNotify) xmmsv_unref);
01654 
01655     g_tree_insert (dict, (gpointer) "type", xmmsv_new_int (type));
01656 
01657     if (id) {
01658         g_tree_insert (dict, (gpointer) "id", xmmsv_new_int (id));
01659     }
01660 
01661     tmp = xmms_playlist_canonical_name (playlist, plname);
01662     g_tree_insert (dict, (gpointer) "name", xmmsv_new_string (tmp));
01663 
01664     return dict;
01665 }
01666 
01667 GTree *
01668 xmms_playlist_current_pos_msg_new (xmms_playlist_t *playlist,
01669                                    guint32 pos, const gchar *plname)
01670 {
01671     GTree *dict;
01672     const gchar *tmp;
01673 
01674     dict = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
01675                             NULL, (GDestroyNotify) xmmsv_unref);
01676 
01677     g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
01678 
01679     tmp = xmms_playlist_canonical_name (playlist, plname);
01680     g_tree_insert (dict, (gpointer) "name", xmmsv_new_string (tmp));
01681 
01682     return dict;
01683 }
01684 
01685 void
01686 xmms_playlist_changed_msg_send (xmms_playlist_t *playlist, GTree *dict)
01687 {
01688     xmmsv_t *type_val;
01689     xmmsv_t *pl_val;
01690     gint type;
01691     const gchar *plname;
01692 
01693     g_return_if_fail (playlist);
01694     g_return_if_fail (dict);
01695 
01696     /* If local playlist change, trigger a COLL_CHANGED signal */
01697     type_val = g_tree_lookup (dict, "type");
01698     pl_val = g_tree_lookup (dict, "name");
01699     if (type_val != NULL && xmmsv_get_int (type_val, &type) &&
01700         type != XMMS_PLAYLIST_CHANGED_UPDATE &&
01701         pl_val != NULL && xmmsv_get_string (pl_val, &plname)) {
01702         XMMS_COLLECTION_PLAYLIST_CHANGED_MSG (playlist->colldag, plname);
01703     }
01704 
01705     xmms_object_emit_f (XMMS_OBJECT (playlist),
01706                         XMMS_IPC_SIGNAL_PLAYLIST_CHANGED,
01707                         XMMSV_TYPE_DICT,
01708                         dict);
01709 
01710     g_tree_destroy (dict);
01711 }
01712 
01713 static void
01714 xmms_playlist_current_pos_msg_send (xmms_playlist_t *playlist,
01715                                    GTree *dict)
01716 {
01717     g_return_if_fail (playlist);
01718 
01719     g_return_if_fail (dict);
01720 
01721     xmms_object_emit_f (XMMS_OBJECT (playlist),
01722                         XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS,
01723                         XMMSV_TYPE_DICT,
01724                         dict);
01725 
01726     g_tree_destroy (dict);
01727 }