XMMS2
playlist.c
Go to the documentation of this file.
1 /* XMMS2 - X Music Multiplexer System
2  * Copyright (C) 2003-2011 XMMS2 Team
3  *
4  * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  */
16 
17 
18 /** @file
19  * Controls playlist
20  */
21 
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <glib.h>
27 #include <math.h>
28 #include <ctype.h>
29 
30 #include "xmmspriv/xmms_playlist.h"
31 #include "xmms/xmms_ipc.h"
32 #include "xmms/xmms_config.h"
33 #include "xmmspriv/xmms_medialib.h"
35 #include "xmms/xmms_log.h"
36 /*
37 #include "xmms/plsplugins.h"
38 #include "xmms/util.h"
39 #include "xmms/signal_xmms.h"
40 #include "xmms/ipc.h"
41 #include "xmms/mediainfo.h"
42 #include "xmms/magic.h"
43 */
44 static void xmms_playlist_destroy (xmms_object_t *object);
45 static void xmms_playlist_client_shuffle (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
46 static void xmms_playlist_client_clear (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
47 static void xmms_playlist_client_sort (xmms_playlist_t *playlist, const gchar *plname, xmmsv_t *property, xmms_error_t *err);
48 static GList * xmms_playlist_client_list_entries (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
49 static gchar *xmms_playlist_client_current_active (xmms_playlist_t *playlist, xmms_error_t *err);
50 static void xmms_playlist_destroy (xmms_object_t *object);
51 
52 static void xmms_playlist_client_add_id (xmms_playlist_t *playlist, const gchar *plname, xmms_medialib_entry_t file, xmms_error_t *error);
53 static void xmms_playlist_client_add_url (xmms_playlist_t *playlist, const gchar *plname, const gchar *nurl, xmms_error_t *err);
54 static void xmms_playlist_client_add_idlist (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *coll, xmms_error_t *err);
55 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);
56 static GTree * xmms_playlist_client_current_pos (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *err);
57 static gint xmms_playlist_client_set_next (xmms_playlist_t *playlist, gint32 pos, xmms_error_t *error);
58 static void xmms_playlist_client_remove_entry (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, xmms_error_t *err);
59 static gboolean xmms_playlist_remove_unlocked (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *plcoll, guint pos, xmms_error_t *err);
60 static void xmms_playlist_client_move_entry (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, gint32 newpos, xmms_error_t *err);
61 static gint xmms_playlist_client_set_next_rel (xmms_playlist_t *playlist, gint32 pos, xmms_error_t *error);
62 static gint xmms_playlist_set_current_position_do (xmms_playlist_t *playlist, guint32 pos, xmms_error_t *err);
63 
64 static void xmms_playlist_client_insert_url (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, const gchar *url, xmms_error_t *error);
65 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);
66 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);
67 static void xmms_playlist_client_radd (xmms_playlist_t *playlist, const gchar *plname, const gchar *path, xmms_error_t *error);
68 static void xmms_playlist_client_rinsert (xmms_playlist_t *playlist, const gchar *plname, gint32 pos, const gchar *path, xmms_error_t *error);
69 
70 static void xmms_playlist_client_load (xmms_playlist_t *, const gchar *, xmms_error_t *);
71 
72 static xmmsv_coll_t *xmms_playlist_get_coll (xmms_playlist_t *playlist, const gchar *plname, xmms_error_t *error);
73 static const gchar *xmms_playlist_canonical_name (xmms_playlist_t *playlist, const gchar *plname);
74 static gint xmms_playlist_coll_get_currpos (xmmsv_coll_t *plcoll);
75 static gint xmms_playlist_coll_get_size (xmmsv_coll_t *plcoll);
76 
77 static void xmms_playlist_update_queue (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *coll);
78 static void xmms_playlist_update_partyshuffle (xmms_playlist_t *playlist, const gchar *plname, xmmsv_coll_t *coll);
79 static void xmms_playlist_register_ipc_commands (xmms_object_t *playlist_object);
80 
81 static void xmms_playlist_current_pos_msg_send (xmms_playlist_t *playlist, GTree *dict);
82 static GTree * xmms_playlist_current_pos_msg_new (xmms_playlist_t *playlist, guint32 pos, const gchar *plname);
83 
84 #define XMMS_PLAYLIST_CHANGED_MSG(type, id, name) xmms_playlist_changed_msg_send (playlist, xmms_playlist_changed_msg_new (playlist, type, id, name))
85 #define XMMS_PLAYLIST_CURRPOS_MSG(pos, name) xmms_playlist_current_pos_msg_send (playlist, xmms_playlist_current_pos_msg_new (playlist, pos, name))
86 
87 
88 /** @defgroup Playlist Playlist
89  * @ingroup XMMSServer
90  * @brief This is the playlist control.
91  *
92  * A playlist is a central thing in the XMMS server, it
93  * tells us what to do after we played the following entry
94  * @{
95  */
96 
97 /** Playlist structure */
98 struct xmms_playlist_St {
99  xmms_object_t object;
100 
101  /* playlists are in the collection DAG */
102  xmms_coll_dag_t *colldag;
103 
104  gboolean repeat_one;
105  gboolean repeat_all;
106 
107  GMutex *mutex;
108 
109  xmms_mediainfo_reader_t *mediainfordr;
110 
111  gboolean update_flag;
112  xmms_medialib_t *medialib;
113 };
114 
115 #include "playlist_ipc.c"
116 
117 static void
118 on_playlist_r_all_changed (xmms_object_t *object, xmmsv_t *_data,
119  gpointer udata)
120 {
121  xmms_playlist_t *playlist = udata;
122  gint value;
123 
125 
126  g_mutex_lock (playlist->mutex);
127  playlist->repeat_all = !!value;
128  g_mutex_unlock (playlist->mutex);
129 }
130 
131 static void
132 on_playlist_r_one_changed (xmms_object_t *object, xmmsv_t *_data,
133  gpointer udata)
134 {
135  xmms_playlist_t *playlist = udata;
136  gint value;
137 
139 
140  g_mutex_lock (playlist->mutex);
141  playlist->repeat_one = !!value;
142  g_mutex_unlock (playlist->mutex);
143 }
144 
145 
146 static void
147 on_playlist_updated (xmms_object_t *object, const gchar *plname)
148 {
149  xmmsv_coll_t *plcoll;
150  xmms_playlist_t *playlist = (xmms_playlist_t*)object;
151 
152  /* Already in an update process, quit */
153  if (playlist->update_flag) {
154  return;
155  }
156 
157  plcoll = xmms_playlist_get_coll (playlist, plname, NULL);
158  if (plcoll == NULL) {
159  return;
160  } else {
161  /* Run the update function if appropriate */
162  switch (xmmsv_coll_get_type (plcoll)) {
164  xmms_playlist_update_queue (playlist, plname, plcoll);
165  break;
166 
168  xmms_playlist_update_partyshuffle (playlist, plname, plcoll);
169  break;
170 
171  default:
172  break;
173  }
174  }
175 }
176 
177 static void
178 on_playlist_updated_pos (xmms_object_t *object, xmmsv_t *val, gpointer udata)
179 {
180  XMMS_DBG ("PLAYLIST: updated pos!");
181  on_playlist_updated (object, XMMS_ACTIVE_PLAYLIST);
182 }
183 
184 static void
185 on_playlist_updated_chg (xmms_object_t *object, xmmsv_t *val, gpointer udata)
186 {
187  const gchar *plname = NULL;
188  xmmsv_t *pl_val;
189 
190  XMMS_DBG ("PLAYLIST: updated chg!");
191 
192  xmmsv_dict_get (val, "name", &pl_val);
193  if (pl_val != NULL) {
194  xmmsv_get_string (pl_val, &plname);
195  } else {
196  /* FIXME: occurs? */
197  XMMS_DBG ("PLAYLIST: updated_chg, NULL playlist!");
198  g_assert_not_reached ();
199  }
200 
201  on_playlist_updated (object, plname);
202 }
203 
204 static void
205 xmms_playlist_update_queue (xmms_playlist_t *playlist, const gchar *plname,
206  xmmsv_coll_t *coll)
207 {
208  gint history, currpos;
209 
210  XMMS_DBG ("PLAYLIST: update-queue!");
211 
212  if (!xmms_collection_get_int_attr (coll, "history", &history)) {
213  history = 0;
214  }
215 
216  playlist->update_flag = TRUE;
217  currpos = xmms_playlist_coll_get_currpos (coll);
218  while (currpos > history) {
219  xmms_playlist_remove_unlocked (playlist, plname, coll, 0, NULL);
220  currpos = xmms_playlist_coll_get_currpos (coll);
221  }
222  playlist->update_flag = FALSE;
223 }
224 
225 static void
226 xmms_playlist_update_partyshuffle (xmms_playlist_t *playlist,
227  const gchar *plname, xmmsv_coll_t *coll)
228 {
229  gint history, upcoming, currpos, size;
230  xmmsv_coll_t *src;
231  xmmsv_t *tmp;
232 
233  XMMS_DBG ("PLAYLIST: update-partyshuffle!");
234 
235  if (!xmms_collection_get_int_attr (coll, "history", &history)) {
236  history = 0;
237  }
238 
239  if (!xmms_collection_get_int_attr (coll, "upcoming", &upcoming)) {
241  }
242 
243  playlist->update_flag = TRUE;
244  currpos = xmms_playlist_coll_get_currpos (coll);
245  while (currpos > history) {
246  xmms_playlist_remove_unlocked (playlist, plname, coll, 0, NULL);
247  currpos = xmms_playlist_coll_get_currpos (coll);
248  }
249 
250  if (!xmmsv_list_get (xmmsv_coll_operands_get (coll), 0, &tmp)) {
251  XMMS_DBG ("Cannot find party shuffle operand!");
252  return;
253  }
254  xmmsv_get_coll (tmp, &src);
255 
256  currpos = xmms_playlist_coll_get_currpos (coll);
257  size = xmms_playlist_coll_get_size (coll);
258  while (size < currpos + 1 + upcoming) {
259  xmms_medialib_entry_t randentry;
260  randentry = xmms_collection_get_random_media (playlist->colldag, src);
261  if (randentry == 0) {
262  break; /* No media found in the collection, give up */
263  }
264  /* FIXME: add_collection might yield better perf here. */
265  xmms_playlist_add_entry_unlocked (playlist, plname, coll, randentry, NULL);
266 
267  currpos = xmms_playlist_coll_get_currpos (coll);
268  size = xmms_playlist_coll_get_size (coll);
269  }
270  playlist->update_flag = FALSE;
271 }
272 
273 /**
274  * Initializes a new xmms_playlist_t.
275  */
278 {
279  xmms_playlist_t *ret;
281 
282  ret = xmms_object_new (xmms_playlist_t, xmms_playlist_destroy);
283  ret->mutex = g_mutex_new ();
284 
285  xmms_playlist_register_ipc_commands (XMMS_OBJECT (ret));
286 
287  val = xmms_config_property_register ("playlist.repeat_one", "0",
288  on_playlist_r_one_changed, ret);
289  ret->repeat_one = xmms_config_property_get_int (val);
290 
291  val = xmms_config_property_register ("playlist.repeat_all", "0",
292  on_playlist_r_all_changed, ret);
293  ret->repeat_all = xmms_config_property_get_int (val);
294 
295  ret->update_flag = FALSE;
296 
299  on_playlist_updated_chg, ret);
300 
303  on_playlist_updated_pos, ret);
304 
305 
306  ret->medialib = xmms_medialib_init (ret);
307  ret->colldag = xmms_collection_init (ret);
308  ret->mediainfordr = xmms_mediainfo_reader_start ();
309 
310  return ret;
311 }
312 
313 static gboolean
314 xmms_playlist_advance_do (xmms_playlist_t *playlist)
315 {
316  gint size, currpos;
317  gboolean ret = TRUE;
318  xmmsv_coll_t *plcoll;
319  char *jumplist;
320  xmms_error_t err;
321  xmms_playlist_t *buffer = playlist;
322  guint newpos;
323 
324  xmms_error_reset (&err);
325 
326  plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, NULL);
327  if (plcoll == NULL) {
328  ret = FALSE;
329  } else if ((size = xmms_playlist_coll_get_size (plcoll)) == 0) {
330  if (xmmsv_coll_attribute_get (plcoll, "jumplist", &jumplist)) {
331  xmms_playlist_client_load (buffer, jumplist, &err);
332  if (xmms_error_isok (&err)) {
333  ret = xmms_playlist_advance_do (playlist);
334  } else {
335  ret = FALSE;
336  }
337  } else {
338  ret = FALSE;
339  }
340  } else if (!playlist->repeat_one) {
341  currpos = xmms_playlist_coll_get_currpos (plcoll);
342  currpos++;
343 
344  if (currpos == size && !playlist->repeat_all &&
345  xmmsv_coll_attribute_get (plcoll, "jumplist", &jumplist)) {
346 
347  xmms_collection_set_int_attr (plcoll, "position", -1);
349 
350  xmms_playlist_client_load (buffer, jumplist, &err);
351  if (xmms_error_isok (&err)) {
352  ret = xmms_playlist_advance_do (playlist);
353  } else {
354  ret = FALSE;
355  }
356  } else {
357  newpos = currpos%size;
358  xmms_collection_set_int_attr (plcoll, "position", newpos);
360  ret = (currpos != size) || playlist->repeat_all;
361  }
362  }
363 
364  return ret;
365 }
366 
367 /**
368  * Go to next song in playlist according to current playlist mode.
369  * xmms_playlist_current_entry is to be used to retrieve the entry.
370  *
371  * @sa xmms_playlist_current_entry
372  *
373  * @returns FALSE if end of playlist is reached, TRUE otherwise.
374  */
375 gboolean
377 {
378  gboolean ret;
379 
380  g_return_val_if_fail (playlist, FALSE);
381 
382  g_mutex_lock (playlist->mutex);
383  ret = xmms_playlist_advance_do (playlist);
384  g_mutex_unlock (playlist->mutex);
385 
386  return ret;
387 }
388 
389 /**
390  * Retrieve the currently active xmms_medialib_entry_t.
391  *
392  */
395 {
396  gint size, currpos;
397  xmmsv_coll_t *plcoll;
398  xmms_medialib_entry_t ent = 0;
399 
400  g_return_val_if_fail (playlist, 0);
401 
402  g_mutex_lock (playlist->mutex);
403 
404  plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, NULL);
405  if (plcoll == NULL) {
406  /* FIXME: What happens? */
407  g_mutex_unlock (playlist->mutex);
408  return 0;
409  }
410 
411  currpos = xmms_playlist_coll_get_currpos (plcoll);
412  size = xmms_playlist_coll_get_size (plcoll);
413 
414  if (currpos == -1 && (size > 0)) {
415  currpos = 0;
416  xmms_collection_set_int_attr (plcoll, "position", currpos);
418  }
419 
420  if (currpos < size) {
421  xmmsv_coll_idlist_get_index (plcoll, currpos, &ent);
422  } else {
423  ent = 0;
424  }
425 
426  g_mutex_unlock (playlist->mutex);
427 
428  return ent;
429 }
430 
431 
432 /**
433  * Retrieve the position of the currently active xmms_medialib_entry_t
434  *
435  */
436 GTree *
437 xmms_playlist_client_current_pos (xmms_playlist_t *playlist, const gchar *plname,
438  xmms_error_t *err)
439 {
440  guint32 pos;
441  xmmsv_coll_t *plcoll;
442  GTree *dict;
443 
444  g_return_val_if_fail (playlist, 0);
445 
446  g_mutex_lock (playlist->mutex);
447 
448  plcoll = xmms_playlist_get_coll (playlist, plname, err);
449  if (plcoll == NULL) {
450  g_mutex_unlock (playlist->mutex);
451  xmms_error_set (err, XMMS_ERROR_INVAL, "no such playlist");
452  return 0;
453  }
454 
455  pos = xmms_playlist_coll_get_currpos (plcoll);
456  if (pos == -1) {
457  xmms_error_set (err, XMMS_ERROR_GENERIC, "no current entry");
458  }
459 
460  g_mutex_unlock (playlist->mutex);
461 
462  dict = xmms_playlist_current_pos_msg_new (playlist, pos, plname);
463 
464  return dict;
465 }
466 
467 /**
468  * Retrieve a copy of the name of the currently active playlist.
469  *
470  */
471 static gchar *
472 xmms_playlist_client_current_active (xmms_playlist_t *playlist, xmms_error_t *err)
473 {
474  gchar *name = NULL;
475  xmmsv_coll_t *active_coll;
476 
477  g_return_val_if_fail (playlist, 0);
478 
479  g_mutex_lock (playlist->mutex);
480 
481  active_coll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
482  if (active_coll != NULL) {
483  const gchar *alias;
484 
485  alias = xmms_collection_find_alias (playlist->colldag,
487  active_coll, XMMS_ACTIVE_PLAYLIST);
488  if (alias == NULL) {
489  xmms_error_set (err, XMMS_ERROR_GENERIC, "active playlist not referenced!");
490  } else {
491  name = g_strdup (alias);
492  }
493  } else {
494  xmms_error_set (err, XMMS_ERROR_GENERIC, "no active playlist");
495  }
496 
497  g_mutex_unlock (playlist->mutex);
498 
499  return name;
500 }
501 
502 
503 static void
504 xmms_playlist_client_load (xmms_playlist_t *playlist, const gchar *name, xmms_error_t *err)
505 {
506  xmmsv_coll_t *plcoll, *active_coll;
507 
508  if (strcmp (name, XMMS_ACTIVE_PLAYLIST) == 0) {
509  xmms_error_set (err, XMMS_ERROR_INVAL, "invalid playlist to load");
510  return;
511  }
512 
513  active_coll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
514  if (active_coll == NULL) {
515  xmms_error_set (err, XMMS_ERROR_GENERIC, "no active playlist");
516  return;
517  }
518 
519  plcoll = xmms_playlist_get_coll (playlist, name, err);
520  if (plcoll == NULL) {
521  xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist");
522  return;
523  }
524 
525  if (active_coll == plcoll) {
526  XMMS_DBG ("Not loading %s playlist, already active!", name);
527  return;
528  }
529 
530  XMMS_DBG ("Loading new playlist! %s", name);
533 
534  xmms_object_emit_f (XMMS_OBJECT (playlist),
537  name);
538 }
539 
540 static inline void
541 swap_entries (xmmsv_coll_t *coll, gint i, gint j)
542 {
543  xmms_medialib_entry_t tmp, tmp2;
544 
545  xmmsv_coll_idlist_get_index (coll, i, &tmp);
546  xmmsv_coll_idlist_get_index (coll, j, &tmp2);
547 
548  xmmsv_coll_idlist_set_index (coll, i, tmp2);
549  xmmsv_coll_idlist_set_index (coll, j, tmp);
550 }
551 
552 
553 /**
554  * Shuffle the playlist.
555  *
556  */
557 static void
558 xmms_playlist_client_shuffle (xmms_playlist_t *playlist, const gchar *plname,
559  xmms_error_t *err)
560 {
561  guint j,i;
562  gint len, currpos;
563  xmmsv_coll_t *plcoll;
564 
565  g_return_if_fail (playlist);
566 
567  g_mutex_lock (playlist->mutex);
568 
569  plcoll = xmms_playlist_get_coll (playlist, plname, err);
570  if (plcoll == NULL) {
571  xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist");
572  g_mutex_unlock (playlist->mutex);
573  return;
574  }
575 
576  currpos = xmms_playlist_coll_get_currpos (plcoll);
577  len = xmms_playlist_coll_get_size (plcoll);
578  if (len > 1) {
579  /* put current at top and exclude from shuffling */
580  if (currpos != -1) {
581  swap_entries (plcoll, 0, currpos);
582  currpos = 0;
583  xmms_collection_set_int_attr (plcoll, "position", currpos);
584  }
585 
586  /* knuth <3 */
587  for (i = currpos + 1; i < len; i++) {
588  j = g_random_int_range (i, len);
589 
590  if (i != j) {
591  swap_entries (plcoll, i, j);
592  }
593  }
594 
595  }
596 
598  XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
599 
600  g_mutex_unlock (playlist->mutex);
601 }
602 
603 static gboolean
604 xmms_playlist_remove_unlocked (xmms_playlist_t *playlist, const gchar *plname,
605  xmmsv_coll_t *plcoll, guint pos, xmms_error_t *err)
606 {
607  gint currpos;
608  GTree *dict;
609 
610  g_return_val_if_fail (playlist, FALSE);
611 
612  currpos = xmms_playlist_coll_get_currpos (plcoll);
613 
614  if (!xmmsv_coll_idlist_remove (plcoll, pos)) {
615  if (err) xmms_error_set (err, XMMS_ERROR_NOENT, "Entry was not in list!");
616  return FALSE;
617  }
618 
619  dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_REMOVE, 0, plname);
620  g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
621  xmms_playlist_changed_msg_send (playlist, dict);
622 
623  /* decrease currentpos if removed entry was before or if it's
624  * the current entry, but only if currentpos is a valid entry.
625  */
626  if (currpos != -1 && pos <= currpos) {
627  currpos--;
628  xmms_collection_set_int_attr (plcoll, "position", currpos);
629  XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
630  }
631 
632  return TRUE;
633 }
634 
635 typedef struct {
636  xmms_playlist_t *pls;
637  xmms_medialib_entry_t entry;
638 } playlist_remove_info_t;
639 
640 static void
641 remove_from_playlist (gpointer key, gpointer value, gpointer udata)
642 {
643  playlist_remove_info_t *rminfo = (playlist_remove_info_t *) udata;
644  guint32 i;
646  gint size;
647  xmmsv_coll_t *plcoll = (xmmsv_coll_t *) value;
648 
649  size = xmms_playlist_coll_get_size (plcoll);
650  for (i = 0; i < size; i++) {
651  if (xmmsv_coll_idlist_get_index (plcoll, i, &val) && val == rminfo->entry) {
652  XMMS_DBG ("removing entry on pos %d in %s", i, (gchar *)key);
653  xmms_playlist_remove_unlocked (rminfo->pls, (gchar *)key, plcoll, i, NULL);
654  i--; /* reset it */
655  }
656  }
657 }
658 
659 
660 
661 /**
662  * Remove all additions of entry in the playlist
663  *
664  * @param playlist the playlist to remove entries from
665  * @param entry the playlist entry to remove
666  *
667  * @sa xmms_playlist_remove
668  */
669 gboolean
671  xmms_medialib_entry_t entry)
672 {
673  playlist_remove_info_t rminfo;
674  g_return_val_if_fail (playlist, FALSE);
675 
676  g_mutex_lock (playlist->mutex);
677 
678  rminfo.pls = playlist;
679  rminfo.entry = entry;
680 
681  xmms_collection_foreach_in_namespace (playlist->colldag,
683  remove_from_playlist, &rminfo);
684 
685  g_mutex_unlock (playlist->mutex);
686 
687  return TRUE;
688 }
689 
690 /**
691  * Remove an entry from playlist.
692  *
693  */
694 void
695 xmms_playlist_client_remove_entry (xmms_playlist_t *playlist,
696  const gchar *plname,
697  gint32 pos, xmms_error_t *err)
698 {
699  gboolean ret = FALSE;
700  xmmsv_coll_t *plcoll;
701 
702  g_return_if_fail (playlist);
703 
704  g_mutex_lock (playlist->mutex);
705  plcoll = xmms_playlist_get_coll (playlist, plname, err);
706  if (plcoll != NULL) {
707  ret = xmms_playlist_remove_unlocked (playlist, plname, plcoll, pos, err);
708  }
709  g_mutex_unlock (playlist->mutex);
710 }
711 
712 
713 /**
714  * Move an entry in playlist
715  *
716  */
717 static void
718 xmms_playlist_client_move_entry (xmms_playlist_t *playlist,
719  const gchar *plname, gint32 pos,
720  gint32 newpos, xmms_error_t *err)
721 {
722  GTree *dict;
724  gint currpos, size;
725  gint64 ipos, inewpos;
726  xmmsv_coll_t *plcoll;
727 
728  g_return_if_fail (playlist);
729 
730  XMMS_DBG ("Moving %d, to %d", pos, newpos);
731 
732  g_mutex_lock (playlist->mutex);
733 
734  plcoll = xmms_playlist_get_coll (playlist, plname, err);
735  if (plcoll == NULL) {
736  /* FIXME: happens ? */
737  g_mutex_unlock (playlist->mutex);
738  return;
739  }
740 
741  currpos = xmms_playlist_coll_get_currpos (plcoll);
742  size = xmms_playlist_coll_get_size (plcoll);
743 
744  if (size == 0 || newpos > (size - 1)) {
745  xmms_error_set (err, XMMS_ERROR_NOENT,
746  "Cannot move entry outside playlist");
747  g_mutex_unlock (playlist->mutex);
748  return;
749  }
750 
751  if (!xmmsv_coll_idlist_move (plcoll, pos, newpos)) {
752  xmms_error_set (err, XMMS_ERROR_NOENT, "Entry was not in list!");
753  g_mutex_unlock (playlist->mutex);
754  return;
755  }
756 
757  /* Update the current position pointer */
758  ipos = pos;
759  inewpos = newpos;
760  if (inewpos <= currpos && ipos > currpos)
761  currpos++;
762  else if (inewpos >= currpos && ipos < currpos)
763  currpos--;
764  else if (ipos == currpos)
765  currpos = inewpos;
766 
767  xmms_collection_set_int_attr (plcoll, "position", currpos);
768 
769  xmmsv_coll_idlist_get_index (plcoll, newpos, &id);
770 
771  dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_MOVE, id, plname);
772  g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
773  g_tree_insert (dict, (gpointer) "newposition", xmmsv_new_int (newpos));
774  xmms_playlist_changed_msg_send (playlist, dict);
775 
776  XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
777 
778  g_mutex_unlock (playlist->mutex);
779 
780  return;
781 
782 }
783 
784 /**
785  * Insert an entry into the playlist at given position.
786  * Creates a #xmms_medialib_entry for you and insert it
787  * in the list.
788  *
789  * @param playlist the playlist to add it URL to.
790  * @param pos the position where the entry is inserted.
791  * @param url the URL to add.
792  * @param err an #xmms_error_t that should be defined upon error.
793  * @return TRUE on success and FALSE otherwise.
794  *
795  */
796 static void
797 xmms_playlist_client_insert_url (xmms_playlist_t *playlist, const gchar *plname,
798  gint32 pos, const gchar *url, xmms_error_t *err)
799 {
800  xmms_medialib_entry_t entry = 0;
802 
803  entry = xmms_medialib_entry_new_encoded (session, url, err);
804  xmms_medialib_end (session);
805 
806  if (!entry) {
807  return;
808  }
809 
810  xmms_playlist_client_insert_id (playlist, plname, pos, entry, err);
811 }
812 
813 /**
814  * Convenient function for inserting a directory at a given position
815  * in the playlist, It will dive down the URL you feed it and
816  * recursivly insert all files.
817  *
818  * @param playlist the playlist to add it URL to.
819  * @param plname the name of the playlist to modify.
820  * @param pos a position in the playlist.
821  * @param nurl the URL of an directory you want to add
822  * @param err an #xmms_error_t that should be defined upon error.
823  */
824 static void
825 xmms_playlist_client_rinsert (xmms_playlist_t *playlist, const gchar *plname, gint32 pos,
826  const gchar *path, xmms_error_t *err)
827 {
828  /* we actually just call the medialib function, but keep
829  * the ipc method here for not confusing users / developers
830  */
831  xmms_medialib_insert_recursive (playlist->medialib, plname, pos, path, err);
832 }
833 
834 /**
835  * Insert an xmms_medialib_entry to the playlist at given position.
836  *
837  * @param playlist the playlist to add the entry to.
838  * @param pos the position where the entry is inserted.
839  * @param file the #xmms_medialib_entry to add.
840  * @param error Upon error this will be set.
841  * @returns TRUE on success and FALSE otherwise.
842  */
843 static void
844 xmms_playlist_client_insert_id (xmms_playlist_t *playlist, const gchar *plname,
845  gint32 pos, xmms_medialib_entry_t file,
846  xmms_error_t *err)
847 {
848  if (!xmms_medialib_check_id (file)) {
849  xmms_error_set (err, XMMS_ERROR_NOENT,
850  "That is not a valid medialib id!");
851  return;
852  }
853 
854  xmms_playlist_insert_entry (playlist, plname, pos, file, err);
855 }
856 
857 static void
858 xmms_playlist_client_insert_collection (xmms_playlist_t *playlist, const gchar *plname,
859  gint32 pos, xmmsv_coll_t *coll,
860  xmmsv_t *order, xmms_error_t *err)
861 {
862  GList *res;
863 
864  res = xmms_collection_query_ids (playlist->colldag, coll, 0, 0, order, err);
865 
866  while (res) {
867  xmmsv_t *val = (xmmsv_t*) res->data;
868  gint id;
869  xmmsv_get_int (val, &id);
870  xmms_playlist_client_insert_id (playlist, plname, pos, id, err);
871  xmmsv_unref (val);
872 
873  res = g_list_delete_link (res, res);
874  pos++;
875  }
876 
877 }
878 
879 /**
880  * Insert an entry at a given position in the playlist without
881  * validating it.
882  *
883  * @internal
884  */
885 void
886 xmms_playlist_insert_entry (xmms_playlist_t *playlist, const gchar *plname,
887  guint32 pos, xmms_medialib_entry_t file,
888  xmms_error_t *err)
889 {
890  GTree *dict;
891  gint currpos;
892  gint len;
893  xmmsv_coll_t *plcoll;
894 
895  g_mutex_lock (playlist->mutex);
896 
897  plcoll = xmms_playlist_get_coll (playlist, plname, err);
898  if (plcoll == NULL) {
899  /* FIXME: happens ? */
900  g_mutex_unlock (playlist->mutex);
901  return;
902  }
903 
904  len = xmms_playlist_coll_get_size (plcoll);
905  if (pos > len) {
906  xmms_error_set (err, XMMS_ERROR_GENERIC,
907  "Could not insert entry outside of playlist!");
908  g_mutex_unlock (playlist->mutex);
909  return;
910  }
911  xmmsv_coll_idlist_insert (plcoll, pos, file);
912 
913  /** propagate the MID ! */
914  dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_INSERT, file, plname);
915  g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
916  xmms_playlist_changed_msg_send (playlist, dict);
917 
918  /** update position once client is familiar with the new item. */
919  currpos = xmms_playlist_coll_get_currpos (plcoll);
920  if (pos <= currpos) {
921  currpos++;
922  xmms_collection_set_int_attr (plcoll, "position", currpos);
923  XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
924  }
925 
926  g_mutex_unlock (playlist->mutex);
927 }
928 
929 /**
930  * Convenient function for adding a URL to the playlist,
931  * Creates a #xmms_medialib_entry_t for you and adds it
932  * to the list.
933  *
934  * @param playlist the playlist to add it URL to.
935  * @param plname the name of the playlist to modify.
936  * @param nurl the URL to add
937  * @param err an #xmms_error_t that should be defined upon error.
938  * @return TRUE on success and FALSE otherwise.
939  */
940 void
941 xmms_playlist_client_add_url (xmms_playlist_t *playlist, const gchar *plname,
942  const gchar *nurl, xmms_error_t *err)
943 {
944  xmms_medialib_entry_t entry = 0;
946 
947  entry = xmms_medialib_entry_new_encoded (session, nurl, err);
948  xmms_medialib_end (session);
949 
950  if (entry) {
951  xmms_playlist_add_entry (playlist, plname, entry, err);
952  }
953 
954 }
955 
956 /**
957  * Convenient function for adding a directory to the playlist,
958  * It will dive down the URL you feed it and recursivly add
959  * all files there.
960  *
961  * @param playlist the playlist to add it URL to.
962  * @param plname the name of the playlist to modify.
963  * @param nurl the URL of an directory you want to add
964  * @param err an #xmms_error_t that should be defined upon error.
965  */
966 static void
967 xmms_playlist_client_radd (xmms_playlist_t *playlist, const gchar *plname,
968  const gchar *path, xmms_error_t *err)
969 {
970  /* we actually just call the medialib function, but keep
971  * the ipc method here for not confusing users / developers
972  */
973  xmms_medialib_add_recursive (playlist->medialib, plname, path, err);
974 }
975 
976 /** Adds a xmms_medialib_entry to the playlist.
977  *
978  * This will append or prepend the entry according to
979  * the option.
980  * This function will wake xmms_playlist_wait.
981  *
982  * @param playlist the playlist to add the entry to.
983  * @param plname the name of the playlist to modify.
984  * @param file the #xmms_medialib_entry_t to add
985  * @param err Upon error this will be set.
986  * @returns TRUE on success
987  */
988 
989 void
990 xmms_playlist_client_add_id (xmms_playlist_t *playlist, const gchar *plname,
992 {
993  if (!xmms_medialib_check_id (file)) {
994  xmms_error_set (err, XMMS_ERROR_NOENT,
995  "That is not a valid medialib id!");
996  return;
997  }
998 
999  xmms_playlist_add_entry (playlist, plname, file, err);
1000 }
1001 
1002 void
1003 xmms_playlist_client_add_idlist (xmms_playlist_t *playlist,
1004  const gchar *plname,
1005  xmmsv_coll_t *coll, xmms_error_t *err)
1006 {
1007  xmms_medialib_entry_t entry;
1008  xmmsv_list_iter_t *it;
1009 
1011  for (xmmsv_list_iter_first (it);
1012  xmmsv_list_iter_valid (it);
1013  xmmsv_list_iter_next (it)) {
1014 
1015  xmmsv_list_iter_entry_int (it, &entry);
1016  if (!xmms_medialib_check_id (entry)) {
1017  xmms_error_set (err, XMMS_ERROR_NOENT,
1018  "Idlist contains invalid medialib id!");
1020  return;
1021  }
1022  }
1023 
1024  for (xmmsv_list_iter_first (it);
1025  xmmsv_list_iter_valid (it);
1026  xmmsv_list_iter_next (it)) {
1027 
1028  xmmsv_list_iter_entry_int (it, &entry);
1029  xmms_playlist_add_entry (playlist, plname, entry, err);
1030  }
1032 
1033 }
1034 
1035 void
1036 xmms_playlist_client_add_collection (xmms_playlist_t *playlist, const gchar *plname,
1037  xmmsv_coll_t *coll, xmmsv_t *order,
1038  xmms_error_t *err)
1039 {
1040  GList *res;
1041 
1042  res = xmms_collection_query_ids (playlist->colldag, coll, 0, 0, order, err);
1043 
1044  while (res) {
1045  xmmsv_t *val = (xmmsv_t*) res->data;
1046  gint id;
1047  xmmsv_get_int (val, &id);
1048  xmms_playlist_add_entry (playlist, plname, id, err);
1049  xmmsv_unref (val);
1050 
1051  res = g_list_delete_link (res, res);
1052  }
1053 
1054 }
1055 
1056 /**
1057  * Add an entry to the playlist without validating it.
1058  *
1059  * @internal
1060  */
1061 void
1062 xmms_playlist_add_entry (xmms_playlist_t *playlist, const gchar *plname,
1064 {
1065  xmmsv_coll_t *plcoll;
1066 
1067  g_mutex_lock (playlist->mutex);
1068 
1069  plcoll = xmms_playlist_get_coll (playlist, plname, err);
1070  if (plcoll != NULL) {
1071  xmms_playlist_add_entry_unlocked (playlist, plname, plcoll, file, err);
1072  }
1073 
1074  g_mutex_unlock (playlist->mutex);
1075 
1076 }
1077 
1078 /**
1079  * Add an entry to the playlist without locking the mutex.
1080  */
1081 void
1083  const gchar *plname,
1084  xmmsv_coll_t *plcoll,
1085  xmms_medialib_entry_t file,
1086  xmms_error_t *err)
1087 {
1088  gint prev_size;
1089  GTree *dict;
1090 
1091  prev_size = xmms_playlist_coll_get_size (plcoll);
1092  xmmsv_coll_idlist_append (plcoll, file);
1093 
1094  /** propagate the MID ! */
1095  dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_ADD, file, plname);
1096  g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (prev_size));
1097  xmms_playlist_changed_msg_send (playlist, dict);
1098 }
1099 
1100 /** Clear the playlist */
1101 static void
1102 xmms_playlist_client_clear (xmms_playlist_t *playlist, const gchar *plname,
1103  xmms_error_t *err)
1104 {
1105  xmmsv_coll_t *plcoll;
1106 
1107  g_return_if_fail (playlist);
1108 
1109  g_mutex_lock (playlist->mutex);
1110 
1111  plcoll = xmms_playlist_get_coll (playlist, plname, err);
1112  if (plcoll == NULL) {
1113  g_mutex_unlock (playlist->mutex);
1114  return;
1115  }
1116 
1117  xmmsv_coll_idlist_clear (plcoll);
1118  xmms_collection_set_int_attr (plcoll, "position", -1);
1119 
1121  g_mutex_unlock (playlist->mutex);
1122 
1123 }
1124 
1125 
1126 /** Set the nextentry pointer in the playlist.
1127  *
1128  * This will set the pointer for the next entry to be
1129  * returned by xmms_playlist_advance. This function
1130  * will also wake xmms_playlist_wait
1131  */
1132 
1133 static gint
1134 xmms_playlist_set_current_position_do (xmms_playlist_t *playlist, guint32 pos,
1135  xmms_error_t *err)
1136 {
1137  gint size;
1139  xmmsv_coll_t *plcoll;
1140  char *jumplist;
1141 
1142  g_return_val_if_fail (playlist, FALSE);
1143 
1144  plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
1145  if (plcoll == NULL) {
1146  return 0;
1147  }
1148 
1149  size = xmms_playlist_coll_get_size (plcoll);
1150 
1151  if (pos == size &&
1152  xmmsv_coll_attribute_get (plcoll, "jumplist", &jumplist)) {
1153 
1154  xmms_collection_set_int_attr (plcoll, "position", 0);
1156 
1157  xmms_playlist_client_load (playlist, jumplist, err);
1158  if (xmms_error_iserror (err)) {
1159  return 0;
1160  }
1161 
1162  plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
1163  if (plcoll == NULL) {
1164  return 0;
1165  }
1166  } else if (pos < size) {
1167  XMMS_DBG ("newpos! %d", pos);
1168  xmms_collection_set_int_attr (plcoll, "position", pos);
1170  } else {
1171  xmms_error_set (err, XMMS_ERROR_INVAL,
1172  "Can't set pos outside the current playlist!");
1173  return 0;
1174  }
1175 
1176  xmmsv_coll_idlist_get_index (plcoll, pos, &mid);
1177 
1178  return mid;
1179 }
1180 
1181 gint
1182 xmms_playlist_client_set_next (xmms_playlist_t *playlist, gint32 pos,
1183  xmms_error_t *err)
1184 {
1186  g_return_val_if_fail (playlist, FALSE);
1187 
1188  g_mutex_lock (playlist->mutex);
1189  mid = xmms_playlist_set_current_position_do (playlist, pos, err);
1190  g_mutex_unlock (playlist->mutex);
1191 
1192  return mid;
1193 }
1194 
1195 static gint
1196 xmms_playlist_client_set_next_rel (xmms_playlist_t *playlist, gint32 pos,
1197  xmms_error_t *err)
1198 {
1199  gint currpos, newpos, size;
1200  xmms_medialib_entry_t mid = 0;
1201  xmmsv_coll_t *plcoll;
1202 
1203  g_return_val_if_fail (playlist, FALSE);
1204 
1205  g_mutex_lock (playlist->mutex);
1206 
1207  plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err);
1208  if (plcoll != NULL) {
1209  currpos = xmms_playlist_coll_get_currpos (plcoll);
1210 
1211  if (playlist->repeat_all) {
1212  newpos = pos + currpos;
1213  size = (gint) xmmsv_coll_idlist_get_size (plcoll);
1214 
1215  if (size > 0) {
1216  newpos %= size;
1217  if (newpos < 0) {
1218  newpos += size;
1219  }
1220  }
1221 
1222  mid = xmms_playlist_set_current_position_do (playlist, newpos, err);
1223  } else {
1224  if (currpos + pos >= 0) {
1225  mid = xmms_playlist_set_current_position_do (playlist,
1226  currpos + pos,
1227  err);
1228  } else {
1229  xmms_error_set (err, XMMS_ERROR_INVAL,
1230  "Can't set pos outside the current playlist!");
1231  }
1232  }
1233  }
1234 
1235  g_mutex_unlock (playlist->mutex);
1236 
1237  return mid;
1238 }
1239 
1240 typedef struct {
1242  guint position;
1243  GList *val; /* List of (xmmsv_t *) prop values */
1244  gboolean current;
1245 } sortdata_t;
1246 
1247 
1248 /**
1249  * Sort helper function.
1250  * Performs a case insesitive comparation between two entries.
1251  * We compare each pair of values in the list of prop values.
1252  */
1253 static gint
1254 xmms_playlist_entry_compare (gconstpointer a, gconstpointer b, gpointer user_data)
1255 {
1256  GList *n1, *n2;
1257  xmmsv_t *val1, *val2, *properties, *propval;
1258  xmmsv_list_iter_t *propit;
1259  sortdata_t *data1 = (sortdata_t *) a;
1260  sortdata_t *data2 = (sortdata_t *) b;
1261  int s1, s2, res;
1262  const gchar *propstr, *str1, *str2;
1263 
1264  properties = (xmmsv_t *) user_data;
1265  for (n1 = data1->val, n2 = data2->val, xmmsv_get_list_iter (properties, &propit);
1266  n1 && n2 && xmmsv_list_iter_valid (propit);
1267  n1 = n1->next, n2 = n2->next, xmmsv_list_iter_next (propit)) {
1268 
1269  xmmsv_list_iter_entry (propit, &propval);
1270  xmmsv_get_string (propval, &propstr);
1271  if (propstr[0] == '-') {
1272  val2 = n1->data;
1273  val1 = n2->data;
1274  } else {
1275  val1 = n1->data;
1276  val2 = n2->data;
1277  }
1278 
1279  if (!val1) {
1280  if (!val2)
1281  continue;
1282  else
1283  return -1;
1284  }
1285 
1286  if (!val2) {
1287  return 1;
1288  }
1289 
1290  if (xmmsv_get_type (val1) == XMMSV_TYPE_STRING &&
1291  xmmsv_get_type (val2) == XMMSV_TYPE_STRING) {
1292  xmmsv_get_string (val1, &str1);
1293  xmmsv_get_string (val2, &str2);
1294  res = g_utf8_collate (str1, str2);
1295  /* keep comparing next pair if equal */
1296  if (res == 0)
1297  continue;
1298  else
1299  return res;
1300  }
1301 
1302  if (xmmsv_get_type (val1) == XMMSV_TYPE_INT32 &&
1303  xmmsv_get_type (val2) == XMMSV_TYPE_INT32)
1304  {
1305  xmmsv_get_int (val1, &s1);
1306  xmmsv_get_int (val2, &s2);
1307 
1308  if (s1 < s2)
1309  return -1;
1310  else if (s1 > s2)
1311  return 1;
1312  else
1313  continue; /* equal, compare next pair of properties */
1314  }
1315 
1316  XMMS_DBG ("Types in compare function differ to much");
1317 
1318  return 0;
1319  }
1320 
1321  /* all pairs matched, really equal! */
1322  return 0;
1323 }
1324 
1325 /**
1326  * Unwind helper function.
1327  * Frees the sortdata elements.
1328  */
1329 static void
1330 xmms_playlist_sorted_free (gpointer data, gpointer userdata)
1331 {
1332  GList *n;
1333  sortdata_t *sorted = (sortdata_t *) data;
1334 
1335  for (n = sorted->val; n; n = n->next) {
1336  if (n->data) {
1337  xmmsv_unref (n->data);
1338  }
1339  }
1340  g_list_free (sorted->val);
1341  g_free (sorted);
1342 }
1343 
1344 /**
1345  * Unwind helper function.
1346  * Fills the playlist with the new sorted data.
1347  */
1348 static void
1349 xmms_playlist_sorted_unwind (gpointer data, gpointer userdata)
1350 {
1351  gint size;
1352  sortdata_t *sorted = (sortdata_t *) data;
1353  xmmsv_coll_t *playlist = (xmmsv_coll_t *)userdata;
1354 
1355  xmmsv_coll_idlist_append (playlist, sorted->id);
1356 
1357  if (sorted->current) {
1358  size = xmmsv_coll_idlist_get_size (playlist);
1359  xmms_collection_set_int_attr (playlist, "position", size - 1);
1360  }
1361 
1362  xmms_playlist_sorted_free (sorted, NULL);
1363 }
1364 
1365 /** Sorts the playlist by properties.
1366  *
1367  * This will sort the list.
1368  * @param playlist The playlist to sort.
1369  * @param properties Tells xmms_playlist_sort which properties it
1370  * should use when sorting.
1371  * @param err An #xmms_error_t - needed since xmms_playlist_sort is an ipc
1372  * method handler.
1373  */
1374 
1375 static void
1376 xmms_playlist_client_sort (xmms_playlist_t *playlist, const gchar *plname,
1377  xmmsv_t *properties, xmms_error_t *err)
1378 {
1379  guint32 i;
1380  GList *tmp = NULL, *n;
1381  sortdata_t *data;
1382  const gchar *str;
1383  xmmsv_t *val;
1384  xmms_medialib_session_t *session;
1385  gboolean list_changed = FALSE;
1386  xmmsv_coll_t *plcoll;
1387  gint currpos, size;
1388  xmmsv_t *valstr;
1389  xmmsv_list_iter_t *propit;
1390 
1391  g_return_if_fail (playlist);
1392  g_return_if_fail (properties);
1393 
1394  g_mutex_lock (playlist->mutex);
1395 
1396  plcoll = xmms_playlist_get_coll (playlist, plname, err);
1397  if (plcoll == NULL) {
1398  xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist!");
1399  g_mutex_unlock (playlist->mutex);
1400  return;
1401  }
1402 
1403  /* check for invalid property strings */
1404  if (!check_string_list (properties)) {
1405  xmms_error_set (err, XMMS_ERROR_NOENT,
1406  "invalid list of properties to sort by!");
1407  g_mutex_unlock (playlist->mutex);
1408  return;
1409  }
1410 
1411  if (xmmsv_list_get_size (properties) < 1) {
1412  xmms_error_set (err, XMMS_ERROR_NOENT,
1413  "empty list of properties to sort by!");
1414  g_mutex_unlock (playlist->mutex);
1415  return;
1416  }
1417 
1418  /* in debug, show the first ordering property */
1419  xmmsv_list_get (properties, 0, &valstr);
1420  xmmsv_get_string (valstr, &str);
1421  XMMS_DBG ("Sorting on %s (and maybe more)", str);
1422 
1423  currpos = xmms_playlist_coll_get_currpos (plcoll);
1424  size = xmms_playlist_coll_get_size (plcoll);
1425 
1426  /* check whether we need to do any sorting at all */
1427  if (size < 2) {
1428  g_mutex_unlock (playlist->mutex);
1429  return;
1430  }
1431 
1432  session = xmms_medialib_begin ();
1433 
1434  xmmsv_get_list_iter (properties, &propit);
1435  for (i = 0; i < size; i++) {
1436  data = g_new (sortdata_t, 1);
1437 
1438  xmmsv_coll_idlist_get_index (plcoll, i, &data->id);
1439  data->position = i;
1440 
1441  /* save the list of values corresponding to the list of sort props */
1442  data->val = NULL;
1443  for (xmmsv_list_iter_first (propit);
1444  xmmsv_list_iter_valid (propit);
1445  xmmsv_list_iter_next (propit)) {
1446 
1447  xmmsv_list_iter_entry (propit, &valstr);
1448  xmmsv_get_string (valstr, &str);
1449  if (str[0] == '-')
1450  str++;
1451 
1453  data->id,
1454  str);
1455 
1456  if (val && xmmsv_get_type (val) == XMMSV_TYPE_STRING) {
1457  gchar *casefold;
1458  /* replace val by casefolded-string (beware of new/free order)*/
1459  xmmsv_get_string (val, &str);
1460  casefold = g_utf8_casefold (str, strlen (str));
1461  xmmsv_unref (val);
1462 
1463  val = xmmsv_new_string (casefold);
1464  g_free (casefold);
1465  }
1466 
1467  data->val = g_list_prepend (data->val, val);
1468  }
1469  data->val = g_list_reverse (data->val);
1470 
1471  data->current = (currpos == i);
1472 
1473  tmp = g_list_prepend (tmp, data);
1474  }
1475 
1476  xmms_medialib_end (session);
1477 
1478  tmp = g_list_reverse (tmp);
1479  tmp = g_list_sort_with_data (tmp, xmms_playlist_entry_compare, properties);
1480 
1481  /* check whether there was any change */
1482  for (i = 0, n = tmp; n; i++, n = g_list_next (n)) {
1483  if (((sortdata_t*)n->data)->position != i) {
1484  list_changed = TRUE;
1485  break;
1486  }
1487  }
1488 
1489  if (!list_changed) {
1490  g_list_foreach (tmp, xmms_playlist_sorted_free, NULL);
1491  g_list_free (tmp);
1492  g_mutex_unlock (playlist->mutex);
1493  return;
1494  }
1495 
1496  xmmsv_coll_idlist_clear (plcoll);
1497  g_list_foreach (tmp, xmms_playlist_sorted_unwind, plcoll);
1498 
1499  g_list_free (tmp);
1500 
1502  XMMS_PLAYLIST_CURRPOS_MSG (currpos, plname);
1503 
1504  g_mutex_unlock (playlist->mutex);
1505 }
1506 
1507 
1508 /** List a playlist */
1509 static GList *
1510 xmms_playlist_client_list_entries (xmms_playlist_t *playlist, const gchar *plname,
1511  xmms_error_t *err)
1512 {
1513  GList *entries = NULL;
1514  xmmsv_coll_t *plcoll;
1515  xmms_medialib_entry_t entry;
1516  xmmsv_list_iter_t *it;
1517 
1518  g_return_val_if_fail (playlist, NULL);
1519 
1520  g_mutex_lock (playlist->mutex);
1521 
1522  plcoll = xmms_playlist_get_coll (playlist, plname, err);
1523  if (plcoll == NULL) {
1524  g_mutex_unlock (playlist->mutex);
1525  return NULL;
1526  }
1527 
1529  for (xmmsv_list_iter_first (it);
1530  xmmsv_list_iter_valid (it);
1531  xmmsv_list_iter_next (it)) {
1532 
1533  xmmsv_list_iter_entry_int (it, &entry);
1534  entries = g_list_prepend (entries, xmmsv_new_int (entry));
1535  }
1537 
1538  g_mutex_unlock (playlist->mutex);
1539 
1540  entries = g_list_reverse (entries);
1541 
1542  return entries;
1543 }
1544 
1545 /** returns pointer to mediainfo reader. */
1548 {
1549  g_return_val_if_fail (playlist, NULL);
1550 
1551  return playlist->mediainfordr;
1552 }
1553 
1554 /** @} */
1555 
1556 /** Free the playlist and other memory in the xmms_playlist_t
1557  *
1558  * This will free all entries in the list!
1559  */
1560 
1561 static void
1562 xmms_playlist_destroy (xmms_object_t *object)
1563 {
1565  xmms_playlist_t *playlist = (xmms_playlist_t *)object;
1566 
1567  g_return_if_fail (playlist);
1568 
1569  g_mutex_free (playlist->mutex);
1570 
1571  val = xmms_config_lookup ("playlist.repeat_one");
1572  xmms_config_property_callback_remove (val, on_playlist_r_one_changed, playlist);
1573  val = xmms_config_lookup ("playlist.repeat_all");
1574  xmms_config_property_callback_remove (val, on_playlist_r_all_changed, playlist);
1575 
1576  xmms_object_unref (playlist->colldag);
1577  xmms_object_unref (playlist->mediainfordr);
1578 
1579  xmms_playlist_unregister_ipc_commands ();
1580 }
1581 
1582 
1583 static xmmsv_coll_t *
1584 xmms_playlist_get_coll (xmms_playlist_t *playlist, const gchar *plname,
1585  xmms_error_t *error)
1586 {
1587  xmmsv_coll_t *coll;
1588  coll = xmms_collection_get_pointer (playlist->colldag, plname,
1590 
1591  if (coll == NULL && error != NULL) {
1592  xmms_error_set (error, XMMS_ERROR_INVAL, "invalid playlist name");
1593  }
1594 
1595  return coll;
1596 }
1597 
1598 /**
1599  * Retrieve the canonical name of a playlist. Assumes the playlist
1600  * name is valid.
1601  */
1602 static const gchar *
1603 xmms_playlist_canonical_name (xmms_playlist_t *playlist, const gchar *plname)
1604 {
1605  const gchar *fullname;
1606 
1607  if (strcmp (plname, XMMS_ACTIVE_PLAYLIST) == 0) {
1608  xmmsv_coll_t *coll;
1609  coll = xmms_collection_get_pointer (playlist->colldag, plname,
1611  fullname = xmms_collection_find_alias (playlist->colldag,
1613  coll, plname);
1614  } else {
1615  fullname = plname;
1616  }
1617 
1618  return fullname;
1619 }
1620 
1621 /** Get the current position in the given playlist (set to -1 if absent). */
1622 static gint
1623 xmms_playlist_coll_get_currpos (xmmsv_coll_t *plcoll)
1624 {
1625  gint currpos;
1626 
1627  /* If absent, set to -1 and save it */
1628  if (!xmms_collection_get_int_attr (plcoll, "position", &currpos)) {
1629  currpos = -1;
1630  xmms_collection_set_int_attr (plcoll, "position", currpos);
1631  }
1632 
1633  return currpos;
1634 }
1635 
1636 /** Get the size of the given playlist (compute and update it if absent). */
1637 static gint
1638 xmms_playlist_coll_get_size (xmmsv_coll_t *plcoll)
1639 {
1640  return xmmsv_coll_idlist_get_size (plcoll);
1641 }
1642 
1643 
1644 GTree *
1647  xmms_medialib_entry_t id, const gchar *plname)
1648 {
1649  GTree *dict;
1650  const gchar *tmp;
1651 
1652  dict = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
1653  NULL, (GDestroyNotify) xmmsv_unref);
1654 
1655  g_tree_insert (dict, (gpointer) "type", xmmsv_new_int (type));
1656 
1657  if (id) {
1658  g_tree_insert (dict, (gpointer) "id", xmmsv_new_int (id));
1659  }
1660 
1661  tmp = xmms_playlist_canonical_name (playlist, plname);
1662  g_tree_insert (dict, (gpointer) "name", xmmsv_new_string (tmp));
1663 
1664  return dict;
1665 }
1666 
1667 GTree *
1668 xmms_playlist_current_pos_msg_new (xmms_playlist_t *playlist,
1669  guint32 pos, const gchar *plname)
1670 {
1671  GTree *dict;
1672  const gchar *tmp;
1673 
1674  dict = g_tree_new_full ((GCompareDataFunc) strcmp, NULL,
1675  NULL, (GDestroyNotify) xmmsv_unref);
1676 
1677  g_tree_insert (dict, (gpointer) "position", xmmsv_new_int (pos));
1678 
1679  tmp = xmms_playlist_canonical_name (playlist, plname);
1680  g_tree_insert (dict, (gpointer) "name", xmmsv_new_string (tmp));
1681 
1682  return dict;
1683 }
1684 
1685 void
1687 {
1688  xmmsv_t *type_val;
1689  xmmsv_t *pl_val;
1690  gint type;
1691  const gchar *plname;
1692 
1693  g_return_if_fail (playlist);
1694  g_return_if_fail (dict);
1695 
1696  /* If local playlist change, trigger a COLL_CHANGED signal */
1697  type_val = g_tree_lookup (dict, "type");
1698  pl_val = g_tree_lookup (dict, "name");
1699  if (type_val != NULL && xmmsv_get_int (type_val, &type) &&
1700  type != XMMS_PLAYLIST_CHANGED_UPDATE &&
1701  pl_val != NULL && xmmsv_get_string (pl_val, &plname)) {
1702  XMMS_COLLECTION_PLAYLIST_CHANGED_MSG (playlist->colldag, plname);
1703  }
1704 
1705  xmms_object_emit_f (XMMS_OBJECT (playlist),
1708  dict);
1709 
1710  g_tree_destroy (dict);
1711 }
1712 
1713 static void
1714 xmms_playlist_current_pos_msg_send (xmms_playlist_t *playlist,
1715  GTree *dict)
1716 {
1717  g_return_if_fail (playlist);
1718 
1719  g_return_if_fail (dict);
1720 
1721  xmms_object_emit_f (XMMS_OBJECT (playlist),
1724  dict);
1725 
1726  g_tree_destroy (dict);
1727 }