/* XMMS2 - X Music Multiplexer System * Copyright (C) 2003-2007 XMMS2 Team * * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!! * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ /** @file * Controls playlist */ #include #include #include #include #include #include #include #include "xmmspriv/xmms_playlist.h" #include "xmms/xmms_ipc.h" #include "xmms/xmms_config.h" #include "xmmspriv/xmms_medialib.h" #include "xmmspriv/xmms_collection.h" #include "xmms/xmms_log.h" /* #include "xmms/plsplugins.h" #include "xmms/util.h" #include "xmms/signal_xmms.h" #include "xmms/ipc.h" #include "xmms/mediainfo.h" #include "xmms/magic.h" */ static void xmms_playlist_destroy (xmms_object_t *object); static void xmms_playlist_shuffle (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *err); static void xmms_playlist_clear (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *err); static void xmms_playlist_sort (xmms_playlist_t *playlist, gchar *plname, GList *property, xmms_error_t *err); static GList * xmms_playlist_list_entries (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *err); static void xmms_playlist_destroy (xmms_object_t *object); gboolean xmms_playlist_remove (xmms_playlist_t *playlist, gchar *plname, guint pos, xmms_error_t *err); static gboolean xmms_playlist_remove_unlocked (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *plcoll, guint pos, xmms_error_t *err); static gboolean xmms_playlist_move (xmms_playlist_t *playlist, gchar *plname, guint pos, guint newpos, xmms_error_t *err); static guint xmms_playlist_set_current_position_rel (xmms_playlist_t *playlist, gint32 pos, xmms_error_t *error); static gboolean xmms_playlist_insert_url (xmms_playlist_t *playlist, gchar *plname, guint32 pos, gchar *url, xmms_error_t *error); static gboolean xmms_playlist_insert_id (xmms_playlist_t *playlist, gchar *plname, guint32 pos, xmms_medialib_entry_t file, xmms_error_t *error); static gboolean xmms_playlist_insert_collection (xmms_playlist_t *playlist, gchar *plname, guint32 pos, xmmsc_coll_t *coll, GList *order, xmms_error_t *error); static void xmms_playlist_radd (xmms_playlist_t *playlist, gchar *plname, gchar *path, xmms_error_t *error); static void xmms_playlist_load (xmms_playlist_t *, gchar *, xmms_error_t *); static xmmsc_coll_t * xmms_playlist_get_coll (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *error); static gchar *xmms_playlist_canonical_name (xmms_playlist_t *playlist, gchar *plname); static gint xmms_playlist_coll_get_currpos (xmmsc_coll_t *plcoll); static gint xmms_playlist_coll_get_size (xmmsc_coll_t *plcoll); static void xmms_playlist_update_queue (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *coll); static void xmms_playlist_update_partyshuffle (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *coll); XMMS_CMD_DEFINE (load, xmms_playlist_load, xmms_playlist_t *, NONE, STRING, NONE); XMMS_CMD_DEFINE3 (insert_url, xmms_playlist_insert_url, xmms_playlist_t *, NONE, STRING, UINT32, STRING); XMMS_CMD_DEFINE3 (insert_id, xmms_playlist_insert_id, xmms_playlist_t *, NONE, STRING, UINT32, UINT32); XMMS_CMD_DEFINE4 (insert_coll, xmms_playlist_insert_collection, xmms_playlist_t *, NONE, STRING, UINT32, COLL, STRINGLIST); XMMS_CMD_DEFINE (shuffle, xmms_playlist_shuffle, xmms_playlist_t *, NONE, STRING, NONE); XMMS_CMD_DEFINE (remove, xmms_playlist_remove, xmms_playlist_t *, NONE, STRING, UINT32); XMMS_CMD_DEFINE3 (move, xmms_playlist_move, xmms_playlist_t *, NONE, STRING, UINT32, UINT32); XMMS_CMD_DEFINE (add_url, xmms_playlist_add_url, xmms_playlist_t *, NONE, STRING, STRING); XMMS_CMD_DEFINE (add_id, xmms_playlist_add_id, xmms_playlist_t *, NONE, STRING, UINT32); XMMS_CMD_DEFINE3 (add_coll, xmms_playlist_add_collection, xmms_playlist_t *, NONE, STRING, COLL, STRINGLIST); XMMS_CMD_DEFINE (clear, xmms_playlist_clear, xmms_playlist_t *, NONE, STRING, NONE); XMMS_CMD_DEFINE (sort, xmms_playlist_sort, xmms_playlist_t *, NONE, STRING, STRINGLIST); XMMS_CMD_DEFINE (list_entries, xmms_playlist_list_entries, xmms_playlist_t *, LIST, STRING, NONE); XMMS_CMD_DEFINE (current_pos, xmms_playlist_current_pos, xmms_playlist_t *, UINT32, STRING, NONE); XMMS_CMD_DEFINE (current_active, xmms_playlist_current_active, xmms_playlist_t *, STRING, NONE, NONE); XMMS_CMD_DEFINE (set_pos, xmms_playlist_set_current_position, xmms_playlist_t *, UINT32, UINT32, NONE); XMMS_CMD_DEFINE (set_pos_rel, xmms_playlist_set_current_position_rel, xmms_playlist_t *, UINT32, INT32, NONE); XMMS_CMD_DEFINE (radd, xmms_playlist_radd, xmms_playlist_t *, NONE, STRING, STRING); #define XMMS_PLAYLIST_CHANGED_MSG(type, id, name) xmms_playlist_changed_msg_send (playlist, xmms_playlist_changed_msg_new (playlist, type, id, name)) /** @defgroup Playlist Playlist * @ingroup XMMSServer * @brief This is the playlist control. * * A playlist is a central thing in the XMMS server, it * tells us what to do after we played the following entry * @{ */ /** Playlist structure */ struct xmms_playlist_St { xmms_object_t object; /* playlists are in the collection DAG */ xmms_coll_dag_t *colldag; gboolean repeat_one; gboolean repeat_all; GMutex *mutex; xmms_mediainfo_reader_t *mediainfordr; gboolean update_flag; xmms_medialib_t *medialib; }; static void on_playlist_r_all_changed (xmms_object_t *object, gconstpointer data, gpointer udata) { xmms_playlist_t *playlist = udata; g_mutex_lock (playlist->mutex); if (data) playlist->repeat_all = atoi ((gchar *)data); g_mutex_unlock (playlist->mutex); } static void on_playlist_r_one_changed (xmms_object_t *object, gconstpointer data, gpointer udata) { xmms_playlist_t *playlist = udata; g_mutex_lock (playlist->mutex); if (data) playlist->repeat_one = atoi ((gchar *)data); g_mutex_unlock (playlist->mutex); } static void on_playlist_updated (xmms_object_t *object, gchar *plname) { xmmsc_coll_t *plcoll; xmms_playlist_t *playlist = (xmms_playlist_t*)object; /* Already in an update process, quit */ if (playlist->update_flag) { return; } plcoll = xmms_playlist_get_coll (playlist, plname, NULL); if (plcoll == NULL) { return; } else { /* Run the update function if appropriate */ switch (xmmsc_coll_get_type (plcoll)) { case XMMS_COLLECTION_TYPE_QUEUE: xmms_playlist_update_queue (playlist, plname, plcoll); break; case XMMS_COLLECTION_TYPE_PARTYSHUFFLE: xmms_playlist_update_partyshuffle (playlist, plname, plcoll); break; default: break; } } } static void on_playlist_updated_pos (xmms_object_t *object, gconstpointer data, gpointer udata) { XMMS_DBG ("PLAYLIST: updated pos!"); on_playlist_updated (object, XMMS_ACTIVE_PLAYLIST); } static void on_playlist_updated_chg (xmms_object_t *object, gconstpointer data, gpointer udata) { gchar *plname = NULL; xmms_object_cmd_value_t *pl_cmd_val; xmms_object_cmd_arg_t *val = (xmms_object_cmd_arg_t*)data; XMMS_DBG ("PLAYLIST: updated chg!"); pl_cmd_val = g_hash_table_lookup (val->retval->value.dict, "name"); if (pl_cmd_val != NULL) { plname = pl_cmd_val->value.string; } else { /* FIXME: occurs? */ XMMS_DBG ("PLAYLIST: updated_chg, NULL playlist!"); g_assert_not_reached (); } on_playlist_updated (object, plname); } static void xmms_playlist_update_queue (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *coll) { gint history, currpos; XMMS_DBG ("PLAYLIST: update-queue!"); if (!xmms_collection_get_int_attr (coll, "history", &history)) { history = 0; } playlist->update_flag = TRUE; currpos = xmms_playlist_coll_get_currpos (coll); while (currpos > history) { xmms_playlist_remove_unlocked (playlist, plname, coll, 0, NULL); currpos = xmms_playlist_coll_get_currpos (coll); } playlist->update_flag = FALSE; } static void xmms_playlist_update_partyshuffle (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *coll) { gint history, upcoming, currpos, size; xmmsc_coll_t *src; XMMS_DBG ("PLAYLIST: update-partyshuffle!"); if (!xmms_collection_get_int_attr (coll, "history", &history)) { history = 0; } if (!xmms_collection_get_int_attr (coll, "upcoming", &upcoming)) { upcoming = XMMS_DEFAULT_PARTYSHUFFLE_UPCOMING; } playlist->update_flag = TRUE; currpos = xmms_playlist_coll_get_currpos (coll); while (currpos > history) { xmms_playlist_remove_unlocked (playlist, plname, coll, 0, NULL); currpos = xmms_playlist_coll_get_currpos (coll); } xmmsc_coll_operand_list_save (coll); xmmsc_coll_operand_list_first (coll); if (!xmmsc_coll_operand_list_entry (coll, &src)) { XMMS_DBG ("Cannot find party shuffle operand!"); return; } xmmsc_coll_operand_list_restore (coll); size = xmms_playlist_coll_get_size (coll); while (size < history + 1 + upcoming) { xmms_medialib_entry_t randentry; randentry = xmms_collection_get_random_media (playlist->colldag, src); if (randentry == 0) { break; /* No media found in the collection, give up */ } /* FIXME: add_collection might yield better perf here. */ xmms_playlist_add_entry_unlocked (playlist, plname, coll, randentry, NULL); size = xmms_playlist_coll_get_size (coll); } playlist->update_flag = FALSE; } /** * Initializes a new xmms_playlist_t. */ xmms_playlist_t * xmms_playlist_init (void) { xmms_playlist_t *ret; xmms_config_property_t *val; ret = xmms_object_new (xmms_playlist_t, xmms_playlist_destroy); ret->mutex = g_mutex_new (); xmms_ipc_object_register (XMMS_IPC_OBJECT_PLAYLIST, XMMS_OBJECT (ret)); xmms_ipc_broadcast_register (XMMS_OBJECT (ret), XMMS_IPC_SIGNAL_PLAYLIST_CHANGED); xmms_ipc_broadcast_register (XMMS_OBJECT (ret), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS); xmms_ipc_broadcast_register (XMMS_OBJECT (ret), XMMS_IPC_SIGNAL_PLAYLIST_LOADED); val = xmms_config_property_register ("playlist.repeat_one", "0", on_playlist_r_one_changed, ret); ret->repeat_one = xmms_config_property_get_int (val); val = xmms_config_property_register ("playlist.repeat_all", "0", on_playlist_r_all_changed, ret); ret->repeat_all = xmms_config_property_get_int (val); ret->update_flag = FALSE; xmms_object_connect (XMMS_OBJECT (ret), XMMS_IPC_SIGNAL_PLAYLIST_CHANGED, on_playlist_updated_chg, ret); xmms_object_connect (XMMS_OBJECT (ret), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, on_playlist_updated_pos, ret); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_CURRENT_POS, XMMS_CMD_FUNC (current_pos)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_CURRENT_ACTIVE, XMMS_CMD_FUNC (current_active)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_LOAD, XMMS_CMD_FUNC (load)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_SHUFFLE, XMMS_CMD_FUNC (shuffle)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_SET_POS, XMMS_CMD_FUNC (set_pos)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_SET_POS_REL, XMMS_CMD_FUNC (set_pos_rel)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_ADD_URL, XMMS_CMD_FUNC (add_url)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_ADD_ID, XMMS_CMD_FUNC (add_id)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_ADD_COLL, XMMS_CMD_FUNC (add_coll)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_REMOVE_ENTRY, XMMS_CMD_FUNC (remove)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_MOVE_ENTRY, XMMS_CMD_FUNC (move)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_CLEAR, XMMS_CMD_FUNC (clear)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_SORT, XMMS_CMD_FUNC (sort)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_LIST, XMMS_CMD_FUNC (list_entries)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_INSERT_URL, XMMS_CMD_FUNC (insert_url)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_INSERT_ID, XMMS_CMD_FUNC (insert_id)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_INSERT_COLL, XMMS_CMD_FUNC (insert_coll)); xmms_object_cmd_add (XMMS_OBJECT (ret), XMMS_IPC_CMD_RADD, XMMS_CMD_FUNC (radd)); ret->medialib = xmms_medialib_init (ret); ret->mediainfordr = xmms_mediainfo_reader_start (ret); ret->colldag = xmms_collection_init (ret); return ret; } /** * Go to next song in playlist according to current playlist mode. * xmms_playlist_current_entry is to be used to retrieve the entry. * * @sa xmms_playlist_current_entry * * @returns FALSE if end of playlist is reached, TRUE otherwise. */ gboolean xmms_playlist_advance (xmms_playlist_t *playlist) { gint size, currpos; gboolean ret = TRUE; xmmsc_coll_t *plcoll; char *jumplist; xmms_error_t err; xmms_playlist_t *buffer = playlist; g_return_val_if_fail (playlist, FALSE); xmms_error_reset (&err); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, NULL); if (plcoll == NULL) { ret = FALSE; } else if ((size = xmms_playlist_coll_get_size (plcoll)) == 0) { if (xmmsc_coll_attribute_get (plcoll, "jumplist", &jumplist)) { xmms_playlist_load (buffer, jumplist, &err); ret = xmms_error_isok (&err); } else { ret = FALSE; } } else if (!playlist->repeat_one) { currpos = xmms_playlist_coll_get_currpos (plcoll); currpos++; if (currpos == size && !playlist->repeat_all && xmmsc_coll_attribute_get (plcoll, "jumplist", &jumplist)) { xmms_collection_set_int_attr (plcoll, "position", 0); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, 0); xmms_playlist_load (buffer, jumplist, &err); ret = xmms_error_isok (&err); } else { xmms_collection_set_int_attr (plcoll, "position", currpos%size); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, currpos%size); ret = (currpos != size) || playlist->repeat_all; } } g_mutex_unlock (playlist->mutex); return ret; } /** * Retrive the currently active xmms_medialib_entry_t. * */ xmms_medialib_entry_t xmms_playlist_current_entry (xmms_playlist_t *playlist) { gint size, currpos; xmmsc_coll_t *plcoll; xmms_medialib_entry_t ent = 0; g_return_val_if_fail (playlist, 0); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, NULL); if (plcoll == NULL) { /* FIXME: What happens? */ g_mutex_unlock (playlist->mutex); return 0; } currpos = xmms_playlist_coll_get_currpos (plcoll); size = xmms_playlist_coll_get_size (plcoll); if (currpos == -1 && (size > 0)) { currpos = 0; xmms_collection_set_int_attr (plcoll, "position", currpos); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, 0); } if (currpos < size) { guint *idlist; idlist = xmmsc_coll_get_idlist (plcoll); ent = idlist[currpos]; } else { ent = 0; } g_mutex_unlock (playlist->mutex); return ent; } /** * Retrieve the position of the currently active xmms_medialib_entry_t * */ guint32 xmms_playlist_current_pos (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *err) { guint32 pos; xmmsc_coll_t *plcoll; g_return_val_if_fail (playlist, 0); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll == NULL) { g_mutex_unlock (playlist->mutex); xmms_error_set (err, XMMS_ERROR_INVAL, "no such playlist"); return 0; } pos = xmms_playlist_coll_get_currpos (plcoll); if (pos == -1) { xmms_error_set (err, XMMS_ERROR_GENERIC, "no current entry"); } g_mutex_unlock (playlist->mutex); return pos; } /** * Retrieve the name of the currently active playlist. * */ gchar * xmms_playlist_current_active (xmms_playlist_t *playlist, xmms_error_t *err) { gchar *name = NULL; xmmsc_coll_t *active_coll; g_return_val_if_fail (playlist, 0); g_mutex_lock (playlist->mutex); active_coll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err); if (active_coll != NULL) { name = xmms_collection_find_alias (playlist->colldag, XMMS_COLLECTION_NSID_PLAYLISTS, active_coll, XMMS_ACTIVE_PLAYLIST); if (name == NULL) { xmms_error_set (err, XMMS_ERROR_GENERIC, "active playlist not referenced!"); } } else { xmms_error_set (err, XMMS_ERROR_GENERIC, "no active playlist"); } g_mutex_unlock (playlist->mutex); return name; } static void xmms_playlist_load (xmms_playlist_t *playlist, gchar *name, xmms_error_t *err) { xmmsc_coll_t *plcoll; if (strcmp (name, XMMS_ACTIVE_PLAYLIST) == 0) { xmms_error_set (err, XMMS_ERROR_INVAL, "invalid playlist to load"); return; } plcoll = xmms_playlist_get_coll (playlist, name, err); if (plcoll == NULL) { xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist"); return; } XMMS_DBG ("Loading new playlist! %s", name); xmms_collection_update_pointer (playlist->colldag, XMMS_ACTIVE_PLAYLIST, XMMS_COLLECTION_NSID_PLAYLISTS, plcoll); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_LOADED, XMMS_OBJECT_CMD_ARG_STRING, name); } static inline void swap_entries (xmmsc_coll_t *coll, gint i, gint j) { guint32 tmp, tmp2; xmmsc_coll_idlist_get_index (coll, i, &tmp); xmmsc_coll_idlist_get_index (coll, j, &tmp2); xmmsc_coll_idlist_set_index (coll, i, tmp2); xmmsc_coll_idlist_set_index (coll, j, tmp); } /** * Shuffle the playlist. * */ static void xmms_playlist_shuffle (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *err) { guint j,i; gint len, currpos; xmmsc_coll_t *plcoll; g_return_if_fail (playlist); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll == NULL) { xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist"); g_mutex_unlock (playlist->mutex); return; } currpos = xmms_playlist_coll_get_currpos (plcoll); len = xmms_playlist_coll_get_size (plcoll); if (len > 1) { /* put current at top and exclude from shuffling */ if (currpos != -1) { swap_entries (plcoll, 0, currpos); currpos = 0; xmms_collection_set_int_attr (plcoll, "position", currpos); } /* knuth <3 */ for (i = currpos + 1; i < len; i++) { j = g_random_int_range (i, len); if (i != j) { swap_entries (plcoll, i, j); } } } XMMS_PLAYLIST_CHANGED_MSG (XMMS_PLAYLIST_CHANGED_SHUFFLE, 0, plname); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, currpos); g_mutex_unlock (playlist->mutex); } static gboolean xmms_playlist_remove_unlocked (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *plcoll, guint pos, xmms_error_t *err) { gint currpos, size; GHashTable *dict; g_return_val_if_fail (playlist, FALSE); currpos = xmms_playlist_coll_get_currpos (plcoll); size = xmms_playlist_coll_get_size (plcoll); if (pos >= size) { if (err) xmms_error_set (err, XMMS_ERROR_NOENT, "Entry was not in list!"); return FALSE; } xmmsc_coll_idlist_remove (plcoll, pos); xmms_collection_set_int_attr (plcoll, "size", size - 1); /* decrease currentpos if removed entry was before or if it's * the current entry, but only if currentpos is a valid entry. */ if (currpos != -1 && pos <= currpos) { currpos--; xmms_collection_set_int_attr (plcoll, "position", currpos); } dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_REMOVE, 0, plname); g_hash_table_insert (dict, "position", xmms_object_cmd_value_int_new (pos)); xmms_playlist_changed_msg_send (playlist, dict); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, currpos); return TRUE; } typedef struct { xmms_playlist_t *pls; xmms_medialib_entry_t entry; } playlist_remove_info_t; static void remove_from_playlist (gpointer key, gpointer value, gpointer udata) { playlist_remove_info_t *rminfo = (playlist_remove_info_t *) udata; guint32 i, val; gint size; xmmsc_coll_t *plcoll = (xmmsc_coll_t *) value; size = xmms_playlist_coll_get_size (plcoll); for (i = 0; i < size; i++) { if (xmmsc_coll_idlist_get_index (plcoll, i, &val) && val == rminfo->entry) { XMMS_DBG ("removing entry on pos %d in %s", i, (gchar *)key); xmms_playlist_remove_unlocked (rminfo->pls, (gchar *)key, plcoll, i, NULL); i--; /* reset it */ } } } /** * Remove all additions of entry in the playlist * * @param playlist the playlist to remove entries from * @param entry the playlist entry to remove * * @sa xmms_playlist_remove */ gboolean xmms_playlist_remove_by_entry (xmms_playlist_t *playlist, xmms_medialib_entry_t entry) { playlist_remove_info_t rminfo; g_return_val_if_fail (playlist, FALSE); g_mutex_lock (playlist->mutex); rminfo.pls = playlist; rminfo.entry = entry; xmms_collection_foreach_in_namespace (playlist->colldag, XMMS_COLLECTION_NSID_PLAYLISTS, remove_from_playlist, &rminfo); g_mutex_unlock (playlist->mutex); return TRUE; } /** * Remove an entry from playlist. * */ gboolean xmms_playlist_remove (xmms_playlist_t *playlist, gchar *plname, guint pos, xmms_error_t *err) { gboolean ret = FALSE; xmmsc_coll_t *plcoll; g_return_val_if_fail (playlist, FALSE); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll != NULL) { ret = xmms_playlist_remove_unlocked (playlist, plname, plcoll, pos, err); } g_mutex_unlock (playlist->mutex); return ret; } /** * Move an entry in playlist * */ static gboolean xmms_playlist_move (xmms_playlist_t *playlist, gchar *plname, guint pos, guint newpos, xmms_error_t *err) { GHashTable *dict; guint32 id; gint currpos, size; gint64 ipos, inewpos; xmmsc_coll_t *plcoll; g_return_val_if_fail (playlist, FALSE); XMMS_DBG ("Moving %d, to %d", pos, newpos); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll == NULL) { /* FIXME: happens ? */ g_mutex_unlock (playlist->mutex); return FALSE; } currpos = xmms_playlist_coll_get_currpos (plcoll); size = xmms_playlist_coll_get_size (plcoll); if (size == 0 || newpos > (size - 1)) { xmms_error_set (err, XMMS_ERROR_NOENT, "Cannot move entry outside playlist"); g_mutex_unlock (playlist->mutex); return FALSE; } if (!xmmsc_coll_idlist_move (plcoll, pos, newpos)) { xmms_error_set (err, XMMS_ERROR_NOENT, "Entry was not in list!"); g_mutex_unlock (playlist->mutex); return FALSE; } /* Update the current position pointer */ ipos = pos; inewpos = newpos; if (inewpos <= currpos && ipos > currpos) currpos++; else if (inewpos >= currpos && ipos < currpos) currpos--; else if (ipos == currpos) currpos = inewpos; xmms_collection_set_int_attr (plcoll, "position", currpos); xmmsc_coll_idlist_get_index (plcoll, newpos, &id); dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_MOVE, id, plname); g_hash_table_insert (dict, "position", xmms_object_cmd_value_int_new (pos)); g_hash_table_insert (dict, "newposition", xmms_object_cmd_value_int_new (newpos)); xmms_playlist_changed_msg_send (playlist, dict); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, currpos); g_mutex_unlock (playlist->mutex); return TRUE; } /** * Insert an entry into the playlist at given position. * Creates a #xmms_medialib_entry for you and insert it * in the list. * * @param playlist the playlist to add it URL to. * @param pos the position where the entry is inserted. * @param url the URL to add. * @param err an #xmms_error_t that should be defined upon error. * @return TRUE on success and FALSE otherwise. * */ static gboolean xmms_playlist_insert_url (xmms_playlist_t *playlist, gchar *plname, guint32 pos, gchar *url, xmms_error_t *err) { xmms_medialib_entry_t entry = 0; xmms_medialib_session_t *session = xmms_medialib_begin_write (); entry = xmms_medialib_entry_new_encoded (session, url, err); xmms_medialib_end (session); if (!entry) { return FALSE; } return xmms_playlist_insert_id (playlist, plname, pos, entry, err); } /** * Insert an xmms_medialib_entry to the playlist at given position. * * @param playlist the playlist to add the entry to. * @param pos the position where the entry is inserted. * @param file the #xmms_medialib_entry to add. * @param error Upon error this will be set. * @returns TRUE on success and FALSE otherwise. */ static gboolean xmms_playlist_insert_id (xmms_playlist_t *playlist, gchar *plname, guint32 pos, xmms_medialib_entry_t file, xmms_error_t *err) { GHashTable *dict; gint len; xmmsc_coll_t *plcoll; if (!xmms_medialib_check_id (file)) { xmms_error_set (err, XMMS_ERROR_NOENT, "That is not a valid medialib id!"); return FALSE; } g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll == NULL) { /* FIXME: happens ? */ g_mutex_unlock (playlist->mutex); return FALSE; } len = xmms_playlist_coll_get_size (plcoll); if (pos > len || pos < 0) { xmms_error_set (err, XMMS_ERROR_GENERIC, "Could not insert entry outside of playlist!"); g_mutex_unlock (playlist->mutex); return FALSE; } xmmsc_coll_idlist_insert (plcoll, pos, file); xmms_collection_set_int_attr (plcoll, "size", len + 1); /** propagate the MID ! */ dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_INSERT, file, plname); g_hash_table_insert (dict, "position", xmms_object_cmd_value_int_new (pos)); xmms_playlist_changed_msg_send (playlist, dict); g_mutex_unlock (playlist->mutex); return TRUE; } gboolean xmms_playlist_insert_collection (xmms_playlist_t *playlist, gchar *plname, guint32 pos, xmmsc_coll_t *coll, GList *order, xmms_error_t *err) { GList *res; res = xmms_collection_query_ids (playlist->colldag, coll, 0, 0, order, err); while (res) { xmms_object_cmd_value_t *val = (xmms_object_cmd_value_t*)res->data; xmms_playlist_insert_id (playlist, plname, pos, val->value.int32, err); g_free (res->data); res = res->next; } g_list_free (res); /* FIXME: detect errors? */ return TRUE; } /** * Convenient function for adding a URL to the playlist, * Creates a #xmms_medialib_entry_t for you and adds it * to the list. * * @param playlist the playlist to add it URL to. * @param plname the name of the playlist to modify. * @param nurl the URL to add * @param err an #xmms_error_t that should be defined upon error. * @return TRUE on success and FALSE otherwise. */ gboolean xmms_playlist_add_url (xmms_playlist_t *playlist, gchar *plname, gchar *nurl, xmms_error_t *err) { xmms_medialib_entry_t entry = 0; xmms_medialib_session_t *session = xmms_medialib_begin_write (); entry = xmms_medialib_entry_new_encoded (session, nurl, err); if (entry) { xmms_playlist_add_entry (playlist, plname, entry, err); } xmms_medialib_end (session); return !!entry; } /** * Convenient function for adding a directory to the playlist, * It will dive down the URL you feed it and recursivly add * all files there. * * @param playlist the playlist to add it URL to. * @param plname the name of the playlist to modify. * @param nurl the URL of an directory you want to add * @param err an #xmms_error_t that should be defined upon error. */ static void xmms_playlist_radd (xmms_playlist_t *playlist, gchar *plname, gchar *path, xmms_error_t *err) { /* we actually just call the medialib function, but keep * the ipc method here for not confusing users / developers */ xmms_medialib_add_recursive (playlist->medialib, plname, path, err); } /** Adds a xmms_medialib_entry to the playlist. * * This will append or prepend the entry according to * the option. * This function will wake xmms_playlist_wait. * * @param playlist the playlist to add the entry to. * @param plname the name of the playlist to modify. * @param file the #xmms_medialib_entry_t to add * @param err Upon error this will be set. * @returns TRUE on success */ gboolean xmms_playlist_add_id (xmms_playlist_t *playlist, gchar *plname, xmms_medialib_entry_t file, xmms_error_t *err) { if (!xmms_medialib_check_id (file)) { xmms_error_set (err, XMMS_ERROR_NOENT, "That is not a valid medialib id!"); return FALSE; } xmms_playlist_add_entry (playlist, plname, file, err); return TRUE; } gboolean xmms_playlist_add_collection (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *coll, GList *order, xmms_error_t *err) { GList *res; res = xmms_collection_query_ids (playlist->colldag, coll, 0, 0, order, err); while (res) { xmms_object_cmd_value_t *val = (xmms_object_cmd_value_t*)res->data; xmms_playlist_add_entry (playlist, plname, val->value.int32, err); g_free (res->data); res = res->next; } g_list_free (res); /* FIXME: detect errors? */ return TRUE; } /** * Add an entry to the playlist without validating it. * * @internal */ void xmms_playlist_add_entry (xmms_playlist_t *playlist, gchar *plname, xmms_medialib_entry_t file, xmms_error_t *err) { xmmsc_coll_t *plcoll; g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll != NULL) { xmms_playlist_add_entry_unlocked (playlist, plname, plcoll, file, err); } g_mutex_unlock (playlist->mutex); } /** * Add an entry to the playlist without locking the mutex. */ void xmms_playlist_add_entry_unlocked (xmms_playlist_t *playlist, gchar *plname, xmmsc_coll_t *plcoll, xmms_medialib_entry_t file, xmms_error_t *err) { gint prev_size; GHashTable *dict; prev_size = xmms_playlist_coll_get_size (plcoll); xmmsc_coll_idlist_append (plcoll, file); xmms_collection_set_int_attr (plcoll, "size", prev_size + 1); /** propagate the MID ! */ dict = xmms_playlist_changed_msg_new (playlist, XMMS_PLAYLIST_CHANGED_ADD, file, plname); g_hash_table_insert (dict, "position", xmms_object_cmd_value_int_new (prev_size)); xmms_playlist_changed_msg_send (playlist, dict); } /** Clear the playlist */ static void xmms_playlist_clear (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *err) { xmmsc_coll_t *plcoll; g_return_if_fail (playlist); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll == NULL) { return; } xmmsc_coll_idlist_clear (plcoll); xmms_collection_set_int_attr (plcoll, "position", -1); xmms_collection_set_int_attr (plcoll, "size", 0); XMMS_PLAYLIST_CHANGED_MSG (XMMS_PLAYLIST_CHANGED_CLEAR, 0, plname); g_mutex_unlock (playlist->mutex); } /** Set the nextentry pointer in the playlist. * * This will set the pointer for the next entry to be * returned by xmms_playlist_advance. This function * will also wake xmms_playlist_wait */ static guint xmms_playlist_set_current_position_do (xmms_playlist_t *playlist, guint32 pos, xmms_error_t *err) { gint size; guint mid; guint *idlist; xmmsc_coll_t *plcoll; char *jumplist; g_return_val_if_fail (playlist, FALSE); plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err); if (plcoll == NULL) { return 0; } size = xmms_playlist_coll_get_size (plcoll); if (pos == size && xmmsc_coll_attribute_get (plcoll, "jumplist", &jumplist)) { xmms_collection_set_int_attr (plcoll, "position", 0); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, 0); xmms_playlist_load (playlist, jumplist, err); if (xmms_error_iserror (err)) { return 0; } plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err); if (plcoll == NULL) { return 0; } } else if (pos < size) { XMMS_DBG ("newpos! %d", pos); xmms_collection_set_int_attr (plcoll, "position", pos); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, pos); } else { xmms_error_set (err, XMMS_ERROR_INVAL, "Can't set pos outside the current playlist!"); return 0; } idlist = xmmsc_coll_get_idlist (plcoll); mid = idlist[pos]; return mid; } guint xmms_playlist_set_current_position (xmms_playlist_t *playlist, guint32 pos, xmms_error_t *err) { guint mid; g_return_val_if_fail (playlist, FALSE); g_mutex_lock (playlist->mutex); mid = xmms_playlist_set_current_position_do (playlist, pos, err); g_mutex_unlock (playlist->mutex); return mid; } static guint xmms_playlist_set_current_position_rel (xmms_playlist_t *playlist, gint32 pos, xmms_error_t *err) { gint currpos; guint mid = 0; xmmsc_coll_t *plcoll; g_return_val_if_fail (playlist, FALSE); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, XMMS_ACTIVE_PLAYLIST, err); if (plcoll != NULL) { currpos = xmms_playlist_coll_get_currpos (plcoll); if (currpos + pos >= 0) mid = xmms_playlist_set_current_position_do (playlist, currpos + pos, err); } g_mutex_unlock (playlist->mutex); return mid; } typedef struct { guint id; guint position; GList *val; /* List of (xmms_object_cmd_value_t *) prop values */ gboolean current; } sortdata_t; /** * Sort helper function. * Performs a case insesitive comparation between two entries. * We compare each pair of values in the list of prop values. */ static gint xmms_playlist_entry_compare (gconstpointer a, gconstpointer b) { GList *n1, *n2; xmms_object_cmd_value_t *val1, *val2; sortdata_t *data1 = (sortdata_t *) a; sortdata_t *data2 = (sortdata_t *) b; int s1, s2, res; for (n1 = data1->val, n2 = data2->val; n1 && n2; n1 = n1->next, n2 = n2->next) { val1 = n1->data; val2 = n2->data; if (!val1) { return -(val2 != NULL); } if (!val2) { return 1; } if (val1->type == XMMS_OBJECT_CMD_ARG_STRING && val2->type == XMMS_OBJECT_CMD_ARG_STRING) { res = g_utf8_collate (val1->value.string, val2->value.string); /* keep comparing next pair if equal */ if (res == 0) continue; else return res; } if ((val1->type == XMMS_OBJECT_CMD_ARG_INT32 || val1->type == XMMS_OBJECT_CMD_ARG_UINT32) && (val2->type == XMMS_OBJECT_CMD_ARG_INT32 || val2->type == XMMS_OBJECT_CMD_ARG_UINT32)) { s1 = (val1->type == XMMS_OBJECT_CMD_ARG_INT32) ? val1->value.int32 : val1->value.uint32; s2 = (val2->type == XMMS_OBJECT_CMD_ARG_INT32) ? val2->value.int32 : val2->value.uint32; if (s1 < s2) return -1; else if (s1 > s2) return 1; else continue; /* equal, compare next pair of properties */ } XMMS_DBG ("Types in compare function differ to much"); return 0; } /* all pairs matched, really equal! */ return 0; } /** * Unwind helper function. * Frees the sortdata elements. */ static void xmms_playlist_sorted_free (gpointer data, gpointer userdata) { GList *n; sortdata_t *sorted = (sortdata_t *) data; for (n = sorted->val; n; n = n->next) { if (n->data) { xmms_object_cmd_value_free (n->data); } } g_list_free (sorted->val); g_free (sorted); } /** * Unwind helper function. * Fills the playlist with the new sorted data. */ static void xmms_playlist_sorted_unwind (gpointer data, gpointer userdata) { gint size; sortdata_t *sorted = (sortdata_t *) data; xmmsc_coll_t *playlist = (xmmsc_coll_t *)userdata; xmmsc_coll_idlist_append (playlist, sorted->id); if (sorted->current) { size = xmmsc_coll_idlist_get_size (playlist); xmms_collection_set_int_attr (playlist, "position", size - 1); } xmms_playlist_sorted_free (sorted, NULL); } /** Sorts the playlist by properties. * * This will sort the list. * @param playlist The playlist to sort. * @param properties Tells xmms_playlist_sort which properties it * should use when sorting. * @param err An #xmms_error_t - needed since xmms_playlist_sort is an ipc * method handler. */ static void xmms_playlist_sort (xmms_playlist_t *playlist, gchar *plname, GList *properties, xmms_error_t *err) { guint32 i; GList *tmp = NULL, *n; sortdata_t *data; gchar *str; xmms_object_cmd_value_t *val; xmms_medialib_session_t *session; gboolean list_changed = FALSE; xmmsc_coll_t *plcoll; gint currpos, size; g_return_if_fail (playlist); g_return_if_fail (properties); XMMS_DBG ("Sorting on %s", (char*)properties->data); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll == NULL) { xmms_error_set (err, XMMS_ERROR_NOENT, "no such playlist!"); g_mutex_unlock (playlist->mutex); return; } currpos = xmms_playlist_coll_get_currpos (plcoll); size = xmms_playlist_coll_get_size (plcoll); /* check whether we need to do any sorting at all */ if (size < 2) { g_mutex_unlock (playlist->mutex); return; } session = xmms_medialib_begin (); for (i = 0; i < size; i++) { data = g_new (sortdata_t, 1); xmmsc_coll_idlist_get_index (plcoll, i, &data->id); data->position = i; /* save the list of values corresponding to the list of sort props */ data->val = NULL; for (n = properties; n; n = n->next) { val = xmms_medialib_entry_property_get_cmd_value (session, data->id, (char*)n->data); if (val && val->type == XMMS_OBJECT_CMD_ARG_STRING) { str = val->value.string; val->value.string = g_utf8_casefold (str, strlen (str)); g_free (str); } data->val = g_list_prepend (data->val, val); } data->val = g_list_reverse (data->val); data->current = (currpos == i); tmp = g_list_prepend (tmp, data); } xmms_medialib_end (session); tmp = g_list_reverse (tmp); tmp = g_list_sort (tmp, xmms_playlist_entry_compare); /* check whether there was any change */ for (i = 0, n = tmp; n; i++, n = g_list_next (n)) { if (((sortdata_t*)n->data)->position != i) { list_changed = TRUE; break; } } if (!list_changed) { g_list_foreach (tmp, xmms_playlist_sorted_free, NULL); g_list_free (tmp); g_mutex_unlock (playlist->mutex); return; } xmmsc_coll_idlist_clear (plcoll); g_list_foreach (tmp, xmms_playlist_sorted_unwind, plcoll); g_list_free (tmp); XMMS_PLAYLIST_CHANGED_MSG (XMMS_PLAYLIST_CHANGED_SORT, 0, plname); xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS, XMMS_OBJECT_CMD_ARG_UINT32, currpos); g_mutex_unlock (playlist->mutex); } /** List a playlist */ static GList * xmms_playlist_list_entries (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *err) { GList *entries = NULL; xmmsc_coll_t *plcoll; guint *idlist; gint i; g_return_val_if_fail (playlist, NULL); g_mutex_lock (playlist->mutex); plcoll = xmms_playlist_get_coll (playlist, plname, err); if (plcoll == NULL) { g_mutex_unlock (playlist->mutex); return NULL; } idlist = xmmsc_coll_get_idlist (plcoll); for (i = 0; idlist[i] != 0; i++) { entries = g_list_prepend (entries, xmms_object_cmd_value_uint_new (idlist[i])); } g_mutex_unlock (playlist->mutex); entries = g_list_reverse (entries); return entries; } /** returns pointer to mediainfo reader. */ xmms_mediainfo_reader_t * xmms_playlist_mediainfo_reader_get (xmms_playlist_t *playlist) { g_return_val_if_fail (playlist, NULL); return playlist->mediainfordr; } /** @} */ /** Free the playlist and other memory in the xmms_playlist_t * * This will free all entries in the list! */ static void xmms_playlist_destroy (xmms_object_t *object) { xmms_config_property_t *val; xmms_playlist_t *playlist = (xmms_playlist_t *)object; g_return_if_fail (playlist); g_mutex_free (playlist->mutex); val = xmms_config_lookup ("playlist.repeat_one"); xmms_config_property_callback_remove (val, on_playlist_r_one_changed); val = xmms_config_lookup ("playlist.repeat_all"); xmms_config_property_callback_remove (val, on_playlist_r_all_changed); xmms_object_unref (playlist->colldag); xmms_object_unref (playlist->mediainfordr); xmms_ipc_broadcast_unregister (XMMS_IPC_SIGNAL_PLAYLIST_CHANGED); xmms_ipc_broadcast_unregister (XMMS_IPC_SIGNAL_PLAYLIST_CURRENT_POS); xmms_ipc_object_unregister (XMMS_IPC_OBJECT_PLAYLIST); } static xmmsc_coll_t * xmms_playlist_get_coll (xmms_playlist_t *playlist, gchar *plname, xmms_error_t *error) { xmmsc_coll_t *coll; coll = xmms_collection_get_pointer (playlist->colldag, plname, XMMS_COLLECTION_NSID_PLAYLISTS); if (coll == NULL && error != NULL) { xmms_error_set (error, XMMS_ERROR_INVAL, "invalid playlist name"); } return coll; } /** * Retrieve the canonical name of a playlist. Assumes the playlist * name is valid. */ static gchar * xmms_playlist_canonical_name (xmms_playlist_t *playlist, gchar *plname) { gchar *fullname; if (strcmp (plname, XMMS_ACTIVE_PLAYLIST) == 0) { xmmsc_coll_t *coll; coll = xmms_collection_get_pointer (playlist->colldag, plname, XMMS_COLLECTION_NSID_PLAYLISTS); fullname = xmms_collection_find_alias (playlist->colldag, XMMS_COLLECTION_NSID_PLAYLISTS, coll, plname); } else { fullname = plname; } return fullname; } /** Get the current position in the given playlist (set to -1 if absent). */ static gint xmms_playlist_coll_get_currpos (xmmsc_coll_t *plcoll) { gint currpos; /* If absent, set to -1 and save it */ if (!xmms_collection_get_int_attr (plcoll, "position", &currpos)) { currpos = -1; xmms_collection_set_int_attr (plcoll, "position", currpos); } return currpos; } /** Get the size of the given playlist (compute and update it if absent). */ static gint xmms_playlist_coll_get_size (xmmsc_coll_t *plcoll) { gint size; /* If absent, compute the size and save it */ if (!xmms_collection_get_int_attr (plcoll, "size", &size)) { gint i; guint *idlist; size = 0; idlist = xmmsc_coll_get_idlist (plcoll); for (i = 0; idlist[i] != 0; i++) { size++; } xmms_collection_set_int_attr (plcoll, "size", size); } return size; } GHashTable * xmms_playlist_changed_msg_new (xmms_playlist_t *playlist, xmms_playlist_changed_actions_t type, guint32 id, gchar *plname) { GHashTable *dict; xmms_object_cmd_value_t *val; dict = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, xmms_object_cmd_value_free); val = xmms_object_cmd_value_int_new (type); g_hash_table_insert (dict, "type", val); if (id) { val = xmms_object_cmd_value_uint_new (id); g_hash_table_insert (dict, "id", val); } plname = xmms_playlist_canonical_name (playlist, plname); val = xmms_object_cmd_value_str_new (plname); g_hash_table_insert (dict, "name", val); return dict; } void xmms_playlist_changed_msg_send (xmms_playlist_t *playlist, GHashTable *dict) { xmms_object_cmd_value_t *type_cmd_val; xmms_object_cmd_value_t *pl_cmd_val; g_return_if_fail (playlist); g_return_if_fail (dict); /* If local playlist change, trigger a COLL_CHANGED signal */ type_cmd_val = g_hash_table_lookup (dict, "type"); pl_cmd_val = g_hash_table_lookup (dict, "name"); if (type_cmd_val != NULL && type_cmd_val->value.int32 != XMMS_PLAYLIST_CHANGED_UPDATE && pl_cmd_val != NULL) { XMMS_COLLECTION_PLAYLIST_CHANGED_MSG (playlist->colldag, pl_cmd_val->value.string); } xmms_object_emit_f (XMMS_OBJECT (playlist), XMMS_IPC_SIGNAL_PLAYLIST_CHANGED, XMMS_OBJECT_CMD_ARG_DICT, dict); g_hash_table_destroy (dict); }