Mercurial > audlegacy
view src/audacious/playlist.c @ 2380:825c3715b90f trunk
[svn] - handle empty playlists gracefully
author | nenolod |
---|---|
date | Sun, 21 Jan 2007 07:11:08 -0800 |
parents | ad1d7687814c |
children | 1dc1d36d0347 |
line wrap: on
line source
/* Audacious * Copyright (C) 2005-2007 Audacious team. * * BMP (C) GPL 2003 $top_src_dir/AUTHORS * * based on: * * XMMS - Cross-platform multimedia player * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, * Thomas Nilsson and 4Front Technologies * Copyright (C) 1999-2003 Haavard Kvaalen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; under version 2 of the License. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Tmple Place - Suite 330, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "playlist.h" #include <glib.h> #include <glib/gprintf.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/errno.h> #if defined(USE_REGEX_ONIGURUMA) #include <onigposix.h> #elif defined(USE_REGEX_PCRE) #include <pcreposix.h> #else #include <regex.h> #endif #include "input.h" #include "main.h" #include "ui_main.h" #include "libaudacious/util.h" #include "libaudacious/configdb.h" #include "vfs.h" #include "libaudacious/urldecode.h" #include "ui_equalizer.h" #include "playback.h" #include "playlist.h" #include "playlist_container.h" #include "playlist_manager.h" #include "ui_playlist.h" #include "strings.h" #include "ui_fileinfo.h" #include "debug.h" typedef gint (*PlaylistCompareFunc) (PlaylistEntry * a, PlaylistEntry * b); typedef void (*PlaylistSaveFunc) (FILE * file); /* If we manually change the song, p_p_b_j will show us where to go back to */ PlaylistEntry *playlist_position_before_jump = NULL; static GList *playlists = NULL; static GList *playlists_iter; /* If this is set to TRUE, we do not probe upon playlist add. * * Under Audacious 0.1.x, this was not a big deal because we used * file extension introspection instead of looking for file format magic * strings. * * Because we use file magic strings, we have to fstat a file being added * to a playlist up to 1 * <number of input plugins installed> times. * * This can get really slow now that we're looking for files to add to a * playlist. (Up to 5 minutes for 5000 songs, etcetera.) * * So, we obviously don't want to probe while opening a large playlist * up. Hince the boolean below. * * January 7, 2006, William Pitcock <nenolod@nenolod.net> */ G_LOCK_DEFINE(playlist_get_info_going); //static gchar *playlist_current_name = NULL; static gboolean playlist_get_info_scan_active = FALSE; static gboolean playlist_get_info_going = FALSE; static GThread *playlist_get_info_thread; static gint path_compare(const gchar * a, const gchar * b); static gint playlist_compare_path(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_compare_filename(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_compare_title(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_compare_artist(PlaylistEntry * a, PlaylistEntry * b); static time_t playlist_get_mtime(const gchar *filename); static gint playlist_compare_date(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_compare_track(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_compare_playlist(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_dupscmp_path(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_dupscmp_filename(PlaylistEntry * a, PlaylistEntry * b); static gint playlist_dupscmp_title(PlaylistEntry * a, PlaylistEntry * b); static PlaylistCompareFunc playlist_compare_func_table[] = { playlist_compare_path, playlist_compare_filename, playlist_compare_title, playlist_compare_artist, playlist_compare_date, playlist_compare_track, playlist_compare_playlist }; static guint playlist_load_ins(Playlist * playlist, const gchar * filename, gint pos); static void playlist_generate_shuffle_list(Playlist *); static void playlist_generate_shuffle_list_nolock(Playlist *); static void playlist_recalc_total_time_nolock(Playlist *); static void playlist_recalc_total_time(Playlist *); static gboolean playlist_entry_get_info(PlaylistEntry * entry); /* *********************** playlist entry code ********************** */ PlaylistEntry * playlist_entry_new(const gchar * filename, const gchar * title, const gint length, InputPlugin * dec) { PlaylistEntry *entry; entry = g_new0(PlaylistEntry, 1); entry->filename = g_strdup(filename); entry->title = str_to_utf8(title); entry->length = length; entry->selected = FALSE; entry->decoder = dec; /* only do this if we have a decoder, otherwise it just takes too long */ if (entry->decoder) playlist_entry_get_info(entry); return entry; } void playlist_entry_free(PlaylistEntry * entry) { if (!entry) return; if (entry->tuple != NULL) { bmp_title_input_free(entry->tuple); entry->tuple = NULL; } if (entry->filename != NULL) g_free(entry->filename); if (entry->title != NULL) g_free(entry->title); g_free(entry); } static gboolean playlist_entry_get_info(PlaylistEntry * entry) { TitleInput *tuple; time_t modtime; g_return_val_if_fail(entry != NULL, FALSE); if (entry->tuple == NULL || entry->tuple->mtime > 0 || entry->tuple->mtime == -1) modtime = playlist_get_mtime(entry->filename); else modtime = 0; /* URI -nenolod */ if (entry->decoder == NULL) entry->decoder = input_check_file(entry->filename, FALSE); /* renew tuple if file mtime is newer than tuple mtime. */ if(entry->tuple){ if(entry->tuple->mtime == modtime) return TRUE; else { bmp_title_input_free(entry->tuple); entry->tuple = NULL; } } if (entry->decoder == NULL || entry->decoder->get_song_tuple == NULL) tuple = input_get_song_tuple(entry->filename); else tuple = entry->decoder->get_song_tuple(entry->filename); if (tuple == NULL) return FALSE; /* attach mtime */ tuple->mtime = modtime; /* entry is still around */ entry->title = xmms_get_titlestring(tuple->formatter != NULL ? tuple->formatter : xmms_get_gentitle_format(), tuple); entry->length = tuple->length; entry->tuple = tuple; return TRUE; } /* *********************** playlist selector code ************************* */ void playlist_init(void) { Playlist *initial_pl; /* FIXME: is this really necessary? REQUIRE_STATIC_LOCK(playlists); */ initial_pl = playlist_new(); playlist_add_playlist(initial_pl); } void playlist_add_playlist(Playlist *playlist) { playlists = g_list_append(playlists, playlist); if (playlists_iter == NULL) playlists_iter = playlists; playlist_manager_update(); } void playlist_remove_playlist(Playlist *playlist) { /* trying to free the last playlist simply clears and resets it */ if (g_list_length(playlists) < 2) { playlist_clear(playlist); playlist_set_current_name(playlist, NULL); return; } if (playlist == playlist_get_active()) playlist_select_next(); /* upon removal, a playlist should be cleared and freed */ playlists = g_list_remove(playlists, playlist); playlist_clear(playlist); playlist_free(playlist); if (playlists_iter == NULL) playlists_iter = playlists; playlist_manager_update(); } GList * playlist_get_playlists(void) { return playlists; } void playlist_select_next(void) { if (playlists_iter == NULL) playlists_iter = playlists; playlists_iter = g_list_next(playlists_iter); if (playlists_iter == NULL) playlists_iter = playlists; playlistwin_update_list(playlist_get_active()); } void playlist_select_prev(void) { if (playlists_iter == NULL) playlists_iter = playlists; playlists_iter = g_list_previous(playlists_iter); if (playlists_iter == NULL) playlists_iter = playlists; playlistwin_update_list(playlist_get_active()); } void playlist_select_playlist(Playlist *playlist) { if (playlists_iter == NULL) playlists_iter = playlists; playlists_iter = g_list_find(playlists, playlist); if (playlists_iter == NULL) playlists_iter = playlists; playlistwin_update_list(playlist); } /* *********************** playlist code ********************** */ const gchar * playlist_get_current_name(Playlist *playlist) { return playlist->title; } gboolean playlist_set_current_name(Playlist *playlist, const gchar * filename) { if (playlist->title) g_free(playlist->title); if (!filename) { playlist->title = NULL; return FALSE; } playlist->title = g_strdup(filename); return TRUE; } static GList * find_playlist_position_list(Playlist *playlist) { REQUIRE_LOCK(playlist->mutex); if (!playlist->position) { if (cfg.shuffle) return playlist->shuffle; else return playlist->entries; } if (cfg.shuffle) return g_list_find(playlist->shuffle, playlist->position); else return g_list_find(playlist->entries, playlist->position); } static void play_queued(Playlist *playlist) { GList *tmp = playlist->queue; REQUIRE_LOCK( playlist->mutex ); playlist->position = playlist->queue->data; playlist->queue = g_list_remove_link(playlist->queue, playlist->queue); g_list_free_1(tmp); } void playlist_clear(Playlist *playlist) { if (!playlist) return; PLAYLIST_LOCK( playlist->mutex ); g_list_foreach(playlist->entries, (GFunc) playlist_entry_free, NULL); g_list_free(playlist->entries); playlist->position = NULL; playlist->entries = NULL; PLAYLIST_UNLOCK( playlist->mutex ); playlist_generate_shuffle_list(playlist); playlistwin_update_list(playlist); playlist_recalc_total_time(playlist); playlist_manager_update(); } static void playlist_delete_node(Playlist * playlist, GList * node, gboolean * set_info_text, gboolean * restart_playing) { PlaylistEntry *entry; GList *playing_song = NULL; REQUIRE_LOCK(playlist->mutex); /* We call g_list_find manually here because we don't want an item * in the shuffle_list */ if (playlist->position) playing_song = g_list_find(playlist->entries, playlist->position); entry = PLAYLIST_ENTRY(node->data); if (playing_song == node) { *set_info_text = TRUE; if (playback_get_playing()) { PLAYLIST_UNLOCK(playlist->mutex); ip_data.stop = TRUE; playback_stop(); ip_data.stop = FALSE; PLAYLIST_LOCK(playlist->mutex); *restart_playing = TRUE; } playing_song = find_playlist_position_list(playlist); if (g_list_next(playing_song)) playlist->position = g_list_next(playing_song)->data; else if (g_list_previous(playing_song)) playlist->position = g_list_previous(playing_song)->data; else playlist->position = NULL; /* Make sure the entry did not disappear under us */ if (g_list_index(playlist->entries, entry) == -1) return; } else if (g_list_position(playlist->entries, playing_song) > g_list_position(playlist->entries, node)) { *set_info_text = TRUE; } playlist->shuffle = g_list_remove(playlist->shuffle, entry); playlist->queue = g_list_remove(playlist->queue, entry); playlist->entries = g_list_remove_link(playlist->entries, node); playlist_entry_free(entry); g_list_free_1(node); playlist_recalc_total_time_nolock(playlist); } void playlist_delete_index(Playlist *playlist, guint pos) { gboolean restart_playing = FALSE, set_info_text = FALSE; GList *node; if (!playlist) return; PLAYLIST_LOCK(playlist->mutex); node = g_list_nth(playlist->entries, pos); if (!node) { PLAYLIST_UNLOCK(playlist->mutex); return; } playlist_delete_node(playlist, node, &set_info_text, &restart_playing); PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); playlistwin_update_list(playlist); if (restart_playing) { if (playlist->position) { playback_initiate(); } else { mainwin_clear_song_info(); } } else if (set_info_text) { mainwin_set_info_text(); } playlist_manager_update(); } void playlist_delete_filenames(Playlist * playlist, GList * filenames) { GList *node, *fnode; gboolean set_info_text = FALSE, restart_playing = FALSE; PLAYLIST_LOCK(playlist->mutex); for (fnode = filenames; fnode; fnode = g_list_next(fnode)) { node = playlist->entries; while (node) { GList *next = g_list_next(node); PlaylistEntry *entry = node->data; if (!strcmp(entry->filename, fnode->data)) playlist_delete_node(playlist, node, &set_info_text, &restart_playing); node = next; } } playlist_recalc_total_time(playlist); PLAYLIST_UNLOCK(playlist->mutex); playlistwin_update_list(playlist); if (restart_playing) { if (playlist->position) { playback_initiate(); } else { mainwin_clear_song_info(); } } else if (set_info_text) { mainwin_set_info_text(); } playlist_manager_update(); } void playlist_delete(Playlist * playlist, gboolean crop) { gboolean restart_playing = FALSE, set_info_text = FALSE; GList *node, *next_node; PlaylistEntry *entry; g_return_if_fail(playlist != NULL); PLAYLIST_LOCK(playlist->mutex); node = playlist->entries; while (node) { entry = PLAYLIST_ENTRY(node->data); next_node = g_list_next(node); if ((entry->selected && !crop) || (!entry->selected && crop)) { playlist_delete_node(playlist, node, &set_info_text, &restart_playing); } node = next_node; } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); if (set_info_text) { mainwin_set_info_text(); } if (restart_playing) { if (playlist->position) { playback_initiate(); } else { mainwin_clear_song_info(); } } playlistwin_update_list(playlist); playlist_manager_update(); } static void __playlist_ins_with_info(Playlist * playlist, const gchar * filename, gint pos, const gchar * title, gint len, InputPlugin * dec) { g_return_if_fail(filename != NULL); PLAYLIST_LOCK(playlist->mutex); playlist->entries = g_list_insert(playlist->entries, playlist_entry_new(filename, title, len, dec), pos); PLAYLIST_UNLOCK(playlist->mutex); g_mutex_lock(mutex_scan); playlist_get_info_scan_active = TRUE; g_mutex_unlock(mutex_scan); g_cond_signal(cond_scan); } static void __playlist_ins_with_info_tuple(Playlist * playlist, const gchar * filename, gint pos, TitleInput *tuple, InputPlugin * dec) { GList *node; PlaylistEntry *entry; g_return_if_fail(playlist != NULL); g_return_if_fail(filename != NULL); PLAYLIST_LOCK(playlist->mutex); playlist->entries = g_list_insert(playlist->entries, playlist_entry_new(filename, tuple->track_name, tuple->length, dec), pos); if (pos < 0) pos = g_list_length(playlist->entries) - 1; /* last element. */ node = g_list_nth(playlist->entries, pos); entry = PLAYLIST_ENTRY(node->data); if (tuple != NULL) { entry->title = xmms_get_titlestring(tuple->formatter != NULL ? tuple->formatter : xmms_get_gentitle_format(), tuple); entry->length = tuple->length; entry->tuple = tuple; } PLAYLIST_UNLOCK(playlist->mutex); g_mutex_lock(mutex_scan); playlist_get_info_scan_active = TRUE; g_mutex_unlock(mutex_scan); g_cond_signal(cond_scan); } static void __playlist_ins(Playlist * playlist, const gchar * filename, gint pos, InputPlugin *dec) { __playlist_ins_with_info(playlist, filename, pos, NULL, -1, dec); playlist_recalc_total_time(playlist); playlist_manager_update(); } gboolean playlist_ins(Playlist * playlist, const gchar * filename, gint pos) { gchar buf[64], *p; gint r; VFSFile *file; InputPlugin *dec; g_return_val_if_fail(playlist != NULL, FALSE); g_return_val_if_fail(filename != NULL, FALSE); if (is_playlist_name(filename)) { playlist->loading_playlist = TRUE; playlist_load_ins(playlist, filename, pos); playlist->loading_playlist = FALSE; return TRUE; } if (playlist->loading_playlist == TRUE || cfg.playlist_detect == TRUE) dec = NULL; else dec = input_check_file(filename, TRUE); if (cfg.playlist_detect == TRUE || playlist->loading_playlist == TRUE || (playlist->loading_playlist == FALSE && dec != NULL)) { __playlist_ins(playlist, filename, pos, dec); playlist_generate_shuffle_list(playlist); playlistwin_update_list(playlist); return TRUE; } /* Some files (typically produced by some cgi-scripts) don't have * the correct extension. Try to recognize these files by looking * at their content. We only check for http entries since it does * not make sense to have file entries in a playlist fetched from * the net. */ /* Some strange people put fifo's with the .mp3 extension, so we * need to make sure it's a real file (otherwise fread() may block * and stall the entire program) */ /* FIXME: bah, FIFOs actually pass this regular file test */ if (!vfs_file_test(filename, G_FILE_TEST_IS_REGULAR)) return FALSE; if (!(file = vfs_fopen(filename, "rb"))) return FALSE; r = vfs_fread(buf, 1, sizeof(buf), file); vfs_fclose(file); for (p = buf; r-- > 0 && (*p == '\r' || *p == '\n'); p++); if (r > 5 && str_has_prefix_nocase(p, "http:")) { playlist_load_ins(playlist, filename, pos); return TRUE; } return FALSE; } /* FIXME: The next few functions are specific to Unix * filesystems. Either abstract it away, or don't even bother checking * at such low level */ typedef struct { dev_t dev; ino_t ino; } DeviceInode; static DeviceInode * devino_new(dev_t device, ino_t inode) { DeviceInode *devino = g_new0(DeviceInode, 1); if (devino) { devino->dev = device; devino->ino = inode; } return devino; } static guint devino_hash(gconstpointer key) { const DeviceInode *d = key; return d->ino; } static gint devino_compare(gconstpointer a, gconstpointer b) { const DeviceInode *da = a, *db = b; return (da->dev == db->dev && da->ino == db->ino); } static gboolean devino_destroy(gpointer key, gpointer value, gpointer data) { g_free(key); return TRUE; } static gboolean file_is_hidden(const gchar * filename) { // FIXME: remove the const cast g_return_val_if_fail(filename != NULL, FALSE); return (g_basename((gchar *) filename)[0] == '.'); } static GList * playlist_dir_find_files(const gchar * path, gboolean background, GHashTable * htab) { GDir *dir; GList *list = NULL, *ilist; const gchar *dir_entry; struct stat statbuf; DeviceInode *devino; if (!g_file_test(path, G_FILE_TEST_IS_DIR)) return NULL; stat(path, &statbuf); devino = devino_new(statbuf.st_dev, statbuf.st_ino); if (g_hash_table_lookup(htab, devino)) { g_free(devino); return NULL; } g_hash_table_insert(htab, devino, GINT_TO_POINTER(1)); if ((ilist = input_scan_dir(path))) { GList *node; for (node = ilist; node; node = g_list_next(node)) { gchar *name = g_build_filename(path, node->data, NULL); list = g_list_prepend(list, name); g_free(node->data); } g_list_free(ilist); return list; } if (!(dir = g_dir_open(path, 0, NULL))) return NULL; while ((dir_entry = g_dir_read_name(dir))) { gchar *filename; if (file_is_hidden(dir_entry)) continue; filename = g_build_filename(path, dir_entry, NULL); if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { GList *sub; sub = playlist_dir_find_files(filename, background, htab); g_free(filename); list = g_list_concat(list, sub); } else if (cfg.playlist_detect == TRUE) list = g_list_prepend(list, filename); else if (input_check_file(filename, TRUE)) list = g_list_prepend(list, filename); else g_free(filename); while (background && gtk_events_pending()) gtk_main_iteration(); } g_dir_close(dir); return list; } gboolean playlist_add(Playlist * playlist, const gchar * filename) { return playlist_ins(playlist, filename, -1); } guint playlist_add_dir(Playlist * playlist, const gchar * directory) { return playlist_ins_dir(playlist, directory, -1, TRUE); } guint playlist_add_url(Playlist * playlist, const gchar * url) { return playlist_ins_url(playlist, url, -1); } guint playlist_ins_dir(Playlist * playlist, const gchar * path, gint pos, gboolean background) { guint entries = 0; GList *list, *node; GHashTable *htab; htab = g_hash_table_new(devino_hash, devino_compare); list = playlist_dir_find_files(path, background, htab); list = g_list_sort(list, (GCompareFunc) path_compare); g_hash_table_foreach_remove(htab, devino_destroy, NULL); for (node = list; node; node = g_list_next(node)) { __playlist_ins(playlist, node->data, pos, NULL); g_free(node->data); entries++; if (pos >= 0) pos++; } g_list_free(list); playlist_recalc_total_time(playlist); playlist_generate_shuffle_list(playlist); playlistwin_update_list(playlist); playlist_manager_update(); return entries; } guint playlist_ins_url(Playlist * playlist, const gchar * string, gint pos) { gchar *tmp; gint i = 1, entries = 0; gboolean first = TRUE; guint firstpos = 0; gboolean success = FALSE; gchar *decoded = NULL; g_return_val_if_fail(playlist != NULL, 0); g_return_val_if_fail(string != NULL, 0); playlistwin_update_list(playlist); while (*string) { GList *node; tmp = strchr(string, '\n'); if (tmp) { if (*(tmp - 1) == '\r') *(tmp - 1) = '\0'; *tmp = '\0'; } decoded = g_strdup(string); if (g_file_test(decoded, G_FILE_TEST_IS_DIR)) { i = playlist_ins_dir(playlist, decoded, pos, FALSE); } else { if (is_playlist_name(decoded)) { i = playlist_load_ins(playlist, decoded, pos); } else { success = playlist_ins(playlist, decoded, pos); i = 1; } } g_free(decoded); PLAYLIST_LOCK(playlist->mutex); node = g_list_nth(playlist->entries, pos); PLAYLIST_UNLOCK(playlist->mutex); entries += i; if (first) { first = FALSE; firstpos = pos; } if (pos >= 0) pos += i; if (!tmp) break; string = tmp + 1; } playlist_recalc_total_time(playlist); playlist_generate_shuffle_list(playlist); playlistwin_update_list(playlist); playlist_manager_update(); return entries; } void playlist_set_info_old_abi(const gchar * title, gint length, gint rate, gint freq, gint nch) { Playlist *playlist = playlist_get_active(); PLAYLIST_LOCK(playlist->mutex); g_return_if_fail(playlist != NULL); if (playlist->position) { g_free(playlist->position->title); playlist->position->title = g_strdup(title); playlist->position->length = length; } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); mainwin_set_song_info(rate, freq, nch); } void playlist_set_info(Playlist * playlist, const gchar * title, gint length, gint rate, gint freq, gint nch) { PLAYLIST_LOCK(playlist->mutex); g_return_if_fail(playlist != NULL); if (playlist->position) { g_free(playlist->position->title); playlist->position->title = g_strdup(title); playlist->position->length = length; } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); mainwin_set_song_info(rate, freq, nch); } void playlist_check_pos_current(Playlist *playlist) { gint pos, row, bottom; if (!playlist) return; PLAYLIST_LOCK(playlist->mutex); if (!playlist->position || !playlistwin_list) { PLAYLIST_UNLOCK(playlist->mutex); return; } pos = g_list_index(playlist->entries, playlist->position); if (playlistwin_item_visible(pos)) { PLAYLIST_UNLOCK(playlist->mutex); return; } bottom = MAX(0, playlist_get_length_nolock(playlist) - playlistwin_list->pl_num_visible); row = CLAMP(pos - playlistwin_list->pl_num_visible / 2, 0, bottom); PLAYLIST_UNLOCK(playlist->mutex); playlistwin_set_toprow(row); g_cond_signal(cond_scan); } void playlist_next(Playlist *playlist) { GList *plist_pos_list; gboolean restart_playing = FALSE; if (!playlist) return; PLAYLIST_LOCK(playlist->mutex); if ((playlist_position_before_jump != NULL) && playlist->queue == NULL) { playlist->position = playlist_position_before_jump; playlist_position_before_jump = NULL; } plist_pos_list = find_playlist_position_list(playlist); if (!cfg.repeat && !g_list_next(plist_pos_list) && playlist->queue == NULL) { PLAYLIST_UNLOCK(playlist->mutex); return; } if (playback_get_playing()) { /* We need to stop before changing playlist_position */ PLAYLIST_UNLOCK(playlist->mutex); ip_data.stop = TRUE; playback_stop(); ip_data.stop = FALSE; PLAYLIST_LOCK(playlist->mutex); restart_playing = TRUE; } plist_pos_list = find_playlist_position_list(playlist); if (playlist->queue != NULL) play_queued(playlist); else if (g_list_next(plist_pos_list)) playlist->position = g_list_next(plist_pos_list)->data; else if (cfg.repeat) { playlist->position = NULL; playlist_generate_shuffle_list_nolock(playlist); if (cfg.shuffle) playlist->position = playlist->shuffle->data; else playlist->position = playlist->entries->data; } PLAYLIST_UNLOCK(playlist->mutex); playlist_check_pos_current(playlist); if (restart_playing) playback_initiate(); else { mainwin_set_info_text(); playlistwin_update_list(playlist); } } void playlist_prev(Playlist *playlist) { GList *plist_pos_list; gboolean restart_playing = FALSE; if (!playlist) return; PLAYLIST_LOCK(playlist->mutex); if ((playlist_position_before_jump != NULL) && playlist->queue == NULL) { playlist->position = playlist_position_before_jump; playlist_position_before_jump = NULL; } plist_pos_list = find_playlist_position_list(playlist); if (!cfg.repeat && !g_list_previous(plist_pos_list)) { PLAYLIST_UNLOCK(playlist->mutex); return; } if (playback_get_playing()) { /* We need to stop before changing playlist_position */ PLAYLIST_UNLOCK(playlist->mutex); ip_data.stop = TRUE; playback_stop(); ip_data.stop = FALSE; PLAYLIST_LOCK(playlist->mutex); restart_playing = TRUE; } plist_pos_list = find_playlist_position_list(playlist); if (g_list_previous(plist_pos_list)) { playlist->position = g_list_previous(plist_pos_list)->data; } else if (cfg.repeat) { GList *node; playlist->position = NULL; playlist_generate_shuffle_list_nolock(playlist); if (cfg.shuffle) node = g_list_last(playlist->shuffle); else node = g_list_last(playlist->entries); if (node) playlist->position = node->data; } PLAYLIST_UNLOCK(playlist->mutex); playlist_check_pos_current(playlist); if (restart_playing) playback_initiate(); else { mainwin_set_info_text(); playlistwin_update_list(playlist); } } void playlist_queue(Playlist *playlist) { GList *list = playlist_get_selected(playlist); GList *it = list; PLAYLIST_LOCK(playlist->mutex); if ((cfg.shuffle) && (playlist_position_before_jump == NULL)) { /* Shuffling and this is our first manual jump. */ playlist_position_before_jump = playlist->position; } while (it) { GList *next = g_list_next(it); GList *tmp; /* XXX: WTF? --nenolod */ it->data = g_list_nth_data(playlist->entries, GPOINTER_TO_INT(it->data)); if ((tmp = g_list_find(playlist->queue, it->data))) { playlist->queue = g_list_remove_link(playlist->queue, tmp); g_list_free_1(tmp); list = g_list_remove_link(list, it); g_list_free_1(it); } it = next; } playlist->queue = g_list_concat(playlist->queue, list); PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); playlistwin_update_list(playlist); } void playlist_queue_position(Playlist *playlist, guint pos) { GList *tmp; PlaylistEntry *entry; PLAYLIST_LOCK(playlist->mutex); if ((cfg.shuffle) && (playlist_position_before_jump == NULL)) { /* Shuffling and this is our first manual jump. */ playlist_position_before_jump = playlist->position; } entry = g_list_nth_data(playlist->entries, pos); if ((tmp = g_list_find(playlist->queue, entry))) { playlist->queue = g_list_remove_link(playlist->queue, tmp); g_list_free_1(tmp); } else playlist->queue = g_list_append(playlist->queue, entry); PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); playlistwin_update_list(playlist); } gboolean playlist_is_position_queued(Playlist *playlist, guint pos) { PlaylistEntry *entry; GList *tmp; PLAYLIST_LOCK(playlist->mutex); entry = g_list_nth_data(playlist->entries, pos); tmp = g_list_find(playlist->queue, entry); PLAYLIST_UNLOCK(playlist->mutex); return tmp != NULL; } gint playlist_get_queue_position_number(Playlist *playlist, guint pos) { PlaylistEntry *entry; gint tmp; PLAYLIST_LOCK(playlist->mutex); entry = g_list_nth_data(playlist->entries, pos); tmp = g_list_index(playlist->queue, entry); PLAYLIST_UNLOCK(playlist->mutex); return tmp; } gint playlist_get_queue_qposition_number(Playlist *playlist, guint pos) { PlaylistEntry *entry; gint tmp; PLAYLIST_LOCK(playlist->mutex); entry = g_list_nth_data(playlist->queue, pos); tmp = g_list_index(playlist->entries, entry); PLAYLIST_UNLOCK(playlist->mutex); return tmp; } void playlist_clear_queue(Playlist *playlist) { PLAYLIST_LOCK(playlist->mutex); g_list_free(playlist->queue); playlist->queue = NULL; PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); playlistwin_update_list(playlist); } void playlist_queue_remove(Playlist *playlist, guint pos) { void *entry; PLAYLIST_LOCK(playlist->mutex); entry = g_list_nth_data(playlist->entries, pos); playlist->queue = g_list_remove(playlist->queue, entry); PLAYLIST_UNLOCK(playlist->mutex); playlistwin_update_list(playlist); } gint playlist_get_queue_position(Playlist *playlist, PlaylistEntry * entry) { return g_list_index(playlist->queue, entry); } void playlist_set_position(Playlist *playlist, guint pos) { GList *node; gboolean restart_playing = FALSE; if (!playlist) return; PLAYLIST_LOCK(playlist->mutex); node = g_list_nth(playlist->entries, pos); if (!node) { PLAYLIST_UNLOCK(playlist->mutex); return; } if (playback_get_playing()) { /* We need to stop before changing playlist_position */ PLAYLIST_UNLOCK(playlist->mutex); ip_data.stop = TRUE; playback_stop(); ip_data.stop = FALSE; PLAYLIST_LOCK(playlist->mutex); restart_playing = TRUE; } if ((cfg.shuffle) && (playlist_position_before_jump == NULL)) { /* Shuffling and this is our first manual jump. */ playlist_position_before_jump = playlist->position; } playlist->position = node->data; PLAYLIST_UNLOCK(playlist->mutex); playlist_check_pos_current(playlist); if (restart_playing) playback_initiate(); else { mainwin_set_info_text(); playlistwin_update_list(playlist); } } void playlist_eof_reached(Playlist *playlist) { GList *plist_pos_list; if ((cfg.no_playlist_advance && !cfg.repeat) || cfg.stopaftersong) ip_data.stop = TRUE; playback_stop(); if ((cfg.no_playlist_advance && !cfg.repeat) || cfg.stopaftersong) ip_data.stop = FALSE; PLAYLIST_LOCK(playlist->mutex); if ((playlist_position_before_jump != NULL) && playlist->queue == NULL) { playlist->position = playlist_position_before_jump; playlist_position_before_jump = NULL; } plist_pos_list = find_playlist_position_list(playlist); if (cfg.no_playlist_advance) { PLAYLIST_UNLOCK(playlist->mutex); mainwin_clear_song_info(); if (cfg.repeat) playback_initiate(); return; } if (cfg.stopaftersong) { PLAYLIST_UNLOCK(playlist->mutex); mainwin_clear_song_info(); mainwin_set_stopaftersong(FALSE); return; } if (playlist->queue != NULL) { play_queued(playlist); } else if (!g_list_next(plist_pos_list)) { if (cfg.shuffle) { playlist->position = NULL; playlist_generate_shuffle_list_nolock(playlist); } else if (playlist->entries != NULL) playlist->position = playlist->entries->data; if (!cfg.repeat) { PLAYLIST_UNLOCK(playlist->mutex); mainwin_clear_song_info(); mainwin_set_info_text(); return; } } else playlist->position = g_list_next(plist_pos_list)->data; PLAYLIST_UNLOCK(playlist->mutex); playlist_check_pos_current(playlist); playback_initiate(); mainwin_set_info_text(); playlistwin_update_list(playlist); } gint playlist_get_length(Playlist *playlist) { gint retval; PLAYLIST_LOCK(playlist->mutex); retval = playlist_get_length_nolock(playlist); PLAYLIST_UNLOCK(playlist->mutex); return retval; } gint playlist_queue_get_length(Playlist *playlist) { gint length; PLAYLIST_LOCK(playlist->mutex); length = g_list_length(playlist->queue); PLAYLIST_UNLOCK(playlist->mutex); return length; } gint playlist_get_length_nolock(Playlist *playlist) { return g_list_length(playlist->entries); } gchar * playlist_get_info_text(Playlist *playlist) { gchar *text, *title, *numbers, *length; g_return_val_if_fail(playlist != NULL, NULL); PLAYLIST_LOCK(playlist->mutex); if (!playlist->position) { PLAYLIST_UNLOCK(playlist->mutex); return NULL; } /* FIXME: there should not be a need to do additional conversion, * if playlist is properly maintained */ if (playlist->position->title) { title = str_to_utf8(playlist->position->title); } else { gchar *basename = g_path_get_basename(playlist->position->filename); title = filename_to_utf8(basename); g_free(basename); } /* * If the user don't want numbers in the playlist, don't * display them in other parts of XMMS */ if (cfg.show_numbers_in_pl) numbers = g_strdup_printf("%d. ", playlist_get_position_nolock(playlist) + 1); else numbers = g_strdup(""); if (playlist->position->length != -1) length = g_strdup_printf(" (%d:%-2.2d)", playlist->position->length / 60000, (playlist->position->length / 1000) % 60); else length = g_strdup(""); PLAYLIST_UNLOCK(playlist->mutex); text = convert_title_text(g_strconcat(numbers, title, length, NULL)); g_free(numbers); g_free(title); g_free(length); return text; } gint playlist_get_current_length(Playlist * playlist) { gint len = 0; if (!playlist) return 0; PLAYLIST_LOCK(playlist->mutex); if (playlist->position) len = playlist->position->length; PLAYLIST_UNLOCK(playlist->mutex); return len; } gboolean playlist_save(Playlist * playlist, const gchar * filename) { PlaylistContainer *plc = NULL; gchar *ext; g_return_val_if_fail(playlist != NULL, FALSE); g_return_val_if_fail(filename != NULL, FALSE); ext = strrchr(filename, '.') + 1; playlist_set_current_name(playlist, filename); if ((plc = playlist_container_find(ext)) == NULL) return FALSE; if (plc->plc_write == NULL) return FALSE; plc->plc_write(filename, 0); return TRUE; } gboolean playlist_load(Playlist * playlist, const gchar * filename) { gboolean ret = FALSE; g_return_val_if_fail(playlist != NULL, FALSE); playlist->loading_playlist = TRUE; ret = playlist_load_ins(playlist, filename, -1); playlist->loading_playlist = FALSE; return ret; } void playlist_load_ins_file(Playlist *playlist, const gchar * filename_p, const gchar * playlist_name, gint pos, const gchar * title, gint len) { gchar *filename; gchar *tmp, *path; InputPlugin *dec; /* for decoder cache */ g_return_if_fail(filename_p != NULL); g_return_if_fail(playlist != NULL); g_return_if_fail(playlist_name != NULL); filename = g_strchug(g_strdup(filename_p)); if(cfg.convert_slash) while ((tmp = strchr(filename, '\\')) != NULL) *tmp = '/'; if (filename[0] != '/' && !strstr(filename, "://")) { path = g_strdup(playlist_name); if ((tmp = strrchr(path, '/'))) *tmp = '\0'; else { if (playlist->loading_playlist != TRUE || cfg.playlist_detect == FALSE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info(playlist, filename, pos, title, len, dec); return; } tmp = g_build_filename(path, filename, NULL); if (playlist->loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(tmp, FALSE); else dec = NULL; __playlist_ins_with_info(playlist, tmp, pos, title, len, dec); g_free(tmp); g_free(path); } else { if (playlist->loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info(playlist, filename, pos, title, len, dec); } g_free(filename); } void playlist_load_ins_file_tuple(Playlist * playlist, const gchar * filename_p, const gchar * playlist_name, gint pos, TitleInput *tuple) { gchar *filename; gchar *tmp, *path; InputPlugin *dec; /* for decoder cache */ g_return_if_fail(filename_p != NULL); g_return_if_fail(playlist_name != NULL); g_return_if_fail(playlist != NULL); filename = g_strchug(g_strdup(filename_p)); while ((tmp = strchr(filename, '\\')) != NULL) *tmp = '/'; if (filename[0] != '/' && !strstr(filename, "://")) { path = g_strdup(playlist_name); if ((tmp = strrchr(path, '/'))) *tmp = '\0'; else { if (playlist->loading_playlist != TRUE || cfg.playlist_detect == FALSE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info_tuple(playlist, filename, pos, tuple, dec); return; } tmp = g_build_filename(path, filename, NULL); if (playlist->loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(tmp, FALSE); else dec = NULL; __playlist_ins_with_info_tuple(playlist, tmp, pos, tuple, dec); g_free(tmp); g_free(path); } else { if (playlist->loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info_tuple(playlist, filename, pos, tuple, dec); } g_free(filename); } static guint playlist_load_ins(Playlist * playlist, const gchar * filename, gint pos) { PlaylistContainer *plc; gchar *ext; g_return_val_if_fail(playlist != NULL, 0); g_return_val_if_fail(filename != NULL, 0); ext = strrchr(filename, '.') + 1; plc = playlist_container_find(ext); g_return_val_if_fail(plc != NULL, 0); g_return_val_if_fail(plc->plc_read != NULL, 0); plc->plc_read(filename, pos); playlist_generate_shuffle_list(playlist); playlistwin_update_list(playlist); return 1; } GList * get_playlist_nth(Playlist *playlist, guint nth) { g_warning("deprecated function get_playlist_nth() was called"); REQUIRE_LOCK(playlist->mutex); return g_list_nth(playlist->entries, nth); } gint playlist_get_position_nolock(Playlist *playlist) { if (playlist && playlist->position) return g_list_index(playlist->entries, playlist->position); return 0; } gint playlist_get_position(Playlist *playlist) { gint pos; PLAYLIST_LOCK(playlist->mutex); pos = playlist_get_position_nolock(playlist); PLAYLIST_UNLOCK(playlist->mutex); return pos; } gchar * playlist_get_filename(Playlist *playlist, guint pos) { gchar *filename; PlaylistEntry *entry; GList *node; if (!playlist) return NULL; PLAYLIST_LOCK(playlist->mutex); node = g_list_nth(playlist->entries, pos); if (!node) { PLAYLIST_UNLOCK(playlist->mutex); return NULL; } entry = node->data; filename = g_strdup(entry->filename); PLAYLIST_UNLOCK(playlist->mutex); return filename; } gchar * playlist_get_songtitle(Playlist *playlist, guint pos) { gchar *title = NULL; PlaylistEntry *entry; GList *node; if (!playlist) return NULL; PLAYLIST_LOCK(playlist->mutex); if (!(node = g_list_nth(playlist->entries, pos))) { PLAYLIST_UNLOCK(playlist->mutex); return NULL; } entry = node->data; /* FIXME: simplify this logic */ if ((entry->title == NULL && entry->length == -1) || (entry->tuple && entry->tuple->mtime != 0 && (entry->tuple->mtime == -1 || entry->tuple->mtime != playlist_get_mtime(entry->filename)))) { if (playlist_entry_get_info(entry)) title = entry->title; } else { title = entry->title; } PLAYLIST_UNLOCK(playlist->mutex); if (!title) { title = g_path_get_basename(entry->filename); return str_replace(title, filename_to_utf8(title)); } return str_to_utf8(title); } TitleInput * playlist_get_tuple(Playlist *playlist, guint pos) { PlaylistEntry *entry; TitleInput *tuple = NULL; GList *node; if (!playlist) return NULL; PLAYLIST_LOCK(playlist->mutex); if (!(node = g_list_nth(playlist->entries, pos))) { PLAYLIST_UNLOCK(playlist->mutex); return NULL; } entry = (PlaylistEntry *) node->data; tuple = entry->tuple; // if no tuple or tuple with old mtime, get new one. if (tuple == NULL || (entry->tuple && entry->tuple->mtime != 0 && (entry->tuple->mtime == -1 || entry->tuple->mtime != playlist_get_mtime(entry->filename)))) { playlist_entry_get_info(entry); tuple = entry->tuple; } PLAYLIST_UNLOCK(playlist->mutex); return tuple; } gint playlist_get_songtime(Playlist *playlist, guint pos) { gint song_time = -1; PlaylistEntry *entry; GList *node; if (!playlist) return -1; PLAYLIST_LOCK(playlist->mutex); if (!(node = g_list_nth(playlist->entries, pos))) { PLAYLIST_UNLOCK(playlist->mutex); return -1; } entry = node->data; if (entry->tuple == NULL || (entry->tuple->mtime != 0 && (entry->tuple->mtime == -1 || entry->tuple->mtime != playlist_get_mtime(entry->filename)))) { if (playlist_entry_get_info(entry)) song_time = entry->length; PLAYLIST_UNLOCK(playlist->mutex); } else { song_time = entry->length; PLAYLIST_UNLOCK(playlist->mutex); } return song_time; } static gint playlist_compare_track(PlaylistEntry * a, PlaylistEntry * b) { g_return_val_if_fail(a != NULL, 0); g_return_val_if_fail(b != NULL, 0); g_return_val_if_fail(a->tuple != NULL, 0); g_return_val_if_fail(b->tuple != NULL, 0); return (a->tuple->track_number - b->tuple->track_number); } static gint playlist_compare_playlist(PlaylistEntry * a, PlaylistEntry * b) { const gchar *a_title = NULL, *b_title = NULL; g_return_val_if_fail(a != NULL, 0); g_return_val_if_fail(b != NULL, 0); if (a->title != NULL) a_title = a->title; else { if (strrchr(a->filename, '/')) a_title = strrchr(a->filename, '/') + 1; else a_title = a->filename; } if (b->title != NULL) b_title = b->title; else { if (strrchr(a->filename, '/')) b_title = strrchr(b->filename, '/') + 1; else b_title = b->filename; } return strcasecmp(a_title, b_title); } static gint playlist_compare_title(PlaylistEntry * a, PlaylistEntry * b) { const gchar *a_title = NULL, *b_title = NULL; g_return_val_if_fail(a != NULL, 0); g_return_val_if_fail(b != NULL, 0); if (a->tuple != NULL && a->tuple->track_name != NULL) a_title = a->tuple->track_name; if (b->tuple != NULL && b->tuple->track_name != NULL) b_title = b->tuple->track_name; if (a_title != NULL && b_title != NULL) return strcasecmp(a_title, b_title); if (a->title != NULL) a_title = a->title; else { if (strrchr(a->filename, '/')) a_title = strrchr(a->filename, '/') + 1; else a_title = a->filename; } if (b->title != NULL) b_title = b->title; else { if (strrchr(a->filename, '/')) b_title = strrchr(b->filename, '/') + 1; else b_title = b->filename; } return strcasecmp(a_title, b_title); } static gint playlist_compare_artist(PlaylistEntry * a, PlaylistEntry * b) { const gchar *a_artist = NULL, *b_artist = NULL; g_return_val_if_fail(a != NULL, 0); g_return_val_if_fail(b != NULL, 0); if (a->tuple != NULL) playlist_entry_get_info(a); if (b->tuple != NULL) playlist_entry_get_info(b); if (a->tuple != NULL && a->tuple->performer != NULL) a_artist = a->tuple->performer; if (b->tuple != NULL && b->tuple->performer != NULL) b_artist = b->tuple->performer; if (a_artist != NULL && b_artist != NULL) return strcasecmp(a_artist, b_artist); return 0; } static gint playlist_compare_filename(PlaylistEntry * a, PlaylistEntry * b) { gchar *a_filename, *b_filename; g_return_val_if_fail(a != NULL, 0); g_return_val_if_fail(b != NULL, 0); if (strrchr(a->filename, '/')) a_filename = strrchr(a->filename, '/') + 1; else a_filename = a->filename; if (strrchr(b->filename, '/')) b_filename = strrchr(b->filename, '/') + 1; else b_filename = b->filename; return strcasecmp(a_filename, b_filename); } static gint path_compare(const gchar * a, const gchar * b) { gchar *posa, *posb; gint len, ret; posa = strrchr(a, '/'); posb = strrchr(b, '/'); /* * Sort directories before files */ if (posa && posb && (posa - a != posb - b)) { if (posa - a > posb - b) { len = posb - b; ret = -1; } else { len = posa - a; ret = 1; } if (!strncasecmp(a, b, len)) return ret; } return strcasecmp(a, b); } static gint playlist_compare_path(PlaylistEntry * a, PlaylistEntry * b) { return path_compare(a->filename, b->filename); } static time_t playlist_get_mtime(const gchar *filename) { struct stat buf; gint rv; rv = stat(filename, &buf); if (rv == 0) { return buf.st_mtime; } else { return 0; //error } } static gint playlist_compare_date(PlaylistEntry * a, PlaylistEntry * b) { struct stat buf; time_t modtime; gint rv; rv = stat(a->filename, &buf); if (rv == 0) { modtime = buf.st_mtime; rv = stat(b->filename, &buf); if (stat(b->filename, &buf) == 0) { if (buf.st_mtime == modtime) return 0; else return (buf.st_mtime - modtime) > 0 ? -1 : 1; } else return -1; } else if (!lstat(b->filename, &buf)) return 1; else return playlist_compare_filename(a, b); } void playlist_sort(Playlist *playlist, PlaylistSortType type) { playlist_remove_dead_files(playlist); PLAYLIST_LOCK(playlist->mutex); playlist->entries = g_list_sort(playlist->entries, (GCompareFunc) playlist_compare_func_table[type]); PLAYLIST_UNLOCK(playlist->mutex); } static GList * playlist_sort_selected_generic(GList * list, GCompareFunc cmpfunc) { GList *list1, *list2; GList *tmp_list = NULL; GList *index_list = NULL; /* * We take all the selected entries out of the playlist, * sorts them, and then put them back in again. */ list1 = g_list_last(list); while (list1) { list2 = g_list_previous(list1); if (PLAYLIST_ENTRY(list1->data)->selected) { gpointer idx; idx = GINT_TO_POINTER(g_list_position(list, list1)); index_list = g_list_prepend(index_list, idx); list = g_list_remove_link(list, list1); tmp_list = g_list_concat(list1, tmp_list); } list1 = list2; } tmp_list = g_list_sort(tmp_list, cmpfunc); list1 = tmp_list; list2 = index_list; while (list2) { if (!list1) { g_critical(G_STRLOC ": Error during list sorting. " "Possibly dropped some playlist-entries."); break; } list = g_list_insert(list, list1->data, GPOINTER_TO_INT(list2->data)); list2 = g_list_next(list2); list1 = g_list_next(list1); } g_list_free(index_list); g_list_free(tmp_list); return list; } void playlist_sort_selected(Playlist *playlist, PlaylistSortType type) { PLAYLIST_LOCK(playlist->mutex); playlist->entries = playlist_sort_selected_generic(playlist->entries, (GCompareFunc) playlist_compare_func_table [type]); PLAYLIST_UNLOCK(playlist->mutex); } void playlist_reverse(Playlist *playlist) { PLAYLIST_LOCK(playlist->mutex); playlist->entries = g_list_reverse(playlist->entries); PLAYLIST_UNLOCK(playlist->mutex); } static GList * playlist_shuffle_list(Playlist *playlist, GList * list) { /* * Note that this doesn't make a copy of the original list. * The pointer to the original list is not valid after this * fuction is run. */ gint len = g_list_length(list); gint i, j; GList *node, **ptrs; if (!playlist) return NULL; REQUIRE_LOCK(playlist->mutex); if (!len) return NULL; ptrs = g_new(GList *, len); for (node = list, i = 0; i < len; node = g_list_next(node), i++) ptrs[i] = node; j = g_random_int_range(0, len); list = ptrs[j]; ptrs[j]->next = NULL; ptrs[j] = ptrs[0]; for (i = 1; i < len; i++) { j = g_random_int_range(0, len - i); list->prev = ptrs[i + j]; ptrs[i + j]->next = list; list = ptrs[i + j]; ptrs[i + j] = ptrs[i]; } list->prev = NULL; g_free(ptrs); return list; } void playlist_random(Playlist *playlist) { PLAYLIST_LOCK(playlist->mutex); playlist->entries = playlist_shuffle_list(playlist, playlist->entries); PLAYLIST_UNLOCK(playlist->mutex); } GList * playlist_get_selected(Playlist *playlist) { GList *node, *list = NULL; gint i = 0; PLAYLIST_LOCK(playlist->mutex); for (node = playlist->entries; node; node = g_list_next(node), i++) { PlaylistEntry *entry = node->data; if (entry->selected) list = g_list_prepend(list, GINT_TO_POINTER(i)); } PLAYLIST_UNLOCK(playlist->mutex); return g_list_reverse(list); } void playlist_clear_selected(Playlist *playlist) { GList *node = NULL; gint i = 0; PLAYLIST_LOCK(playlist->mutex); for (node = playlist->entries; node; node = g_list_next(node), i++) { PLAYLIST_ENTRY(node->data)->selected = FALSE; } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); playlist_manager_update(); } gint playlist_get_num_selected(Playlist *playlist) { GList *node; gint num = 0; PLAYLIST_LOCK(playlist->mutex); for (node = playlist->entries; node; node = g_list_next(node)) { PlaylistEntry *entry = node->data; if (entry->selected) num++; } PLAYLIST_UNLOCK(playlist->mutex); return num; } static void playlist_generate_shuffle_list(Playlist *playlist) { PLAYLIST_LOCK(playlist->mutex); playlist_generate_shuffle_list_nolock(playlist); PLAYLIST_UNLOCK(playlist->mutex); } static void playlist_generate_shuffle_list_nolock(Playlist *playlist) { GList *node; gint numsongs; if (!cfg.shuffle || !playlist) return; REQUIRE_LOCK(playlist->mutex); if (playlist->shuffle) { g_list_free(playlist->shuffle); playlist->shuffle = NULL; } playlist->shuffle = playlist_shuffle_list(playlist, g_list_copy(playlist->entries)); numsongs = g_list_length(playlist->shuffle); if (playlist->position) { gint i = g_list_index(playlist->shuffle, playlist->position); node = g_list_nth(playlist->shuffle, i); playlist->shuffle = g_list_remove_link(playlist->shuffle, node); playlist->shuffle = g_list_prepend(playlist->shuffle, node->data); } } void playlist_fileinfo(Playlist *playlist, guint pos) { gchar *path = NULL; GList *node; PlaylistEntry *entry = NULL; TitleInput *tuple = NULL; PLAYLIST_LOCK(playlist->mutex); if ((node = g_list_nth(playlist->entries, pos))) { entry = node->data; tuple = entry->tuple; path = g_strdup(entry->filename); } PLAYLIST_UNLOCK(playlist->mutex); /* No tuple? Try to set this entry up properly. --nenolod */ if (entry->tuple == NULL || entry->tuple->mtime == -1 || entry->tuple->mtime == 0 || entry->tuple->mtime != playlist_get_mtime(entry->filename)) { playlist_entry_get_info(entry); tuple = entry->tuple; } if (tuple != NULL) { if (entry->decoder == NULL) entry->decoder = input_check_file(entry->filename, FALSE); /* try to find a decoder */ if (entry->decoder != NULL && entry->decoder->file_info_box == NULL) fileinfo_show_for_tuple(tuple); else if (entry->decoder != NULL && entry->decoder->file_info_box != NULL) entry->decoder->file_info_box(path); else fileinfo_show_for_path(path); g_free(path); } else if (path != NULL) { if (entry != NULL && entry->decoder != NULL && entry->decoder->file_info_box != NULL) entry->decoder->file_info_box(path); else fileinfo_show_for_path(path); g_free(path); } } void playlist_fileinfo_current(Playlist *playlist) { gchar *path = NULL; TitleInput *tuple = NULL; PLAYLIST_LOCK(playlist->mutex); if (playlist->entries && playlist->position) { path = g_strdup(playlist->position->filename); if (( playlist->position->tuple == NULL ) || ( playlist->position->decoder == NULL )) playlist_entry_get_info(playlist->position); tuple = playlist->position->tuple; } PLAYLIST_UNLOCK(playlist->mutex); if (tuple != NULL) { if (playlist->position->decoder != NULL && playlist->position->decoder->file_info_box == NULL) fileinfo_show_for_tuple(tuple); else if (playlist->position->decoder != NULL && playlist->position->decoder->file_info_box != NULL) playlist->position->decoder->file_info_box(path); else fileinfo_show_for_path(path); g_free(path); } else if (path != NULL) { if (playlist->position != NULL && playlist->position->decoder != NULL && playlist->position->decoder->file_info_box != NULL) playlist->position->decoder->file_info_box(path); else fileinfo_show_for_path(path); g_free(path); } } static gboolean playlist_get_info_is_going(void) { gboolean result; G_LOCK(playlist_get_info_going); result = playlist_get_info_going; G_UNLOCK(playlist_get_info_going); return result; } static gpointer playlist_get_info_func(gpointer arg) { GList *node; gboolean update_playlistwin = FALSE; gboolean update_mainwin = FALSE; while (playlist_get_info_is_going()) { PlaylistEntry *entry; Playlist *playlist = playlist_get_active(); // on_load if (cfg.use_pl_metadata && cfg.get_info_on_load && playlist_get_info_scan_active) { PLAYLIST_LOCK(playlist->mutex); for (node = playlist->entries; node; node = g_list_next(node)) { entry = node->data; if(entry->tuple && (entry->tuple->length > -1)) { update_playlistwin = TRUE; continue; } if (!playlist_entry_get_info(entry)) { if (g_list_index(playlist->entries, entry) == -1) /* Entry disappeared while we looked it up. Restart. */ node = playlist->entries; } else if ((entry->tuple != NULL || entry->title != NULL) && entry->length != -1) { update_playlistwin = TRUE; if (entry == playlist->position) update_mainwin = TRUE; break; } } PLAYLIST_UNLOCK(playlist->mutex); if (!node) { g_mutex_lock(mutex_scan); playlist_get_info_scan_active = FALSE; g_mutex_unlock(mutex_scan); } } // on_load // on_demand else if (!cfg.get_info_on_load && cfg.get_info_on_demand && cfg.playlist_visible && !cfg.playlist_shaded && cfg.use_pl_metadata) { g_mutex_lock(mutex_scan); playlist_get_info_scan_active = FALSE; g_mutex_unlock(mutex_scan); PLAYLIST_LOCK(playlist->mutex); if (!playlist->entries) { PLAYLIST_UNLOCK(playlist->mutex); } else { for (node = g_list_nth(playlist->entries, playlistwin_get_toprow()); node && playlistwin_item_visible(g_list_position(playlist->entries, node)); node = g_list_next(node)) { entry = node->data; if(entry->tuple && (entry->tuple->length > -1)) { update_playlistwin = TRUE; continue; } if (!playlist_entry_get_info(entry)) { if (g_list_index(playlist->entries, entry) == -1) /* Entry disapeared while we looked it up. Restart. */ node = g_list_nth(playlist->entries, playlistwin_get_toprow()); } else if ((entry->tuple != NULL || entry->title != NULL) && entry->length != -1) { update_playlistwin = TRUE; if (entry == playlist->position) update_mainwin = TRUE; // no need for break here since this iteration is very short. } } PLAYLIST_UNLOCK(playlist->mutex); } } // on_demand else if (cfg.get_info_on_demand && (!cfg.playlist_visible || cfg.playlist_shaded || !cfg.use_pl_metadata)) { g_mutex_lock(mutex_scan); playlist_get_info_scan_active = FALSE; g_mutex_unlock(mutex_scan); } else /* not on_demand and not on_load... NOTE: this shouldn't happen anymore, sanity check in bmp_config_load now */ { g_mutex_lock(mutex_scan); playlist_get_info_scan_active = FALSE; g_mutex_unlock(mutex_scan); } if (update_playlistwin) { playlistwin_update_list(playlist); update_playlistwin = FALSE; } if (update_mainwin) { mainwin_set_info_text(); update_mainwin = FALSE; } if (playlist_get_info_scan_active) { continue; } g_mutex_lock(mutex_scan); g_cond_wait(cond_scan, mutex_scan); g_mutex_unlock(mutex_scan); } // while g_thread_exit(NULL); return NULL; } void playlist_start_get_info_thread(void) { G_LOCK(playlist_get_info_going); playlist_get_info_going = TRUE; G_UNLOCK(playlist_get_info_going); playlist_get_info_thread = g_thread_create(playlist_get_info_func, NULL, TRUE, NULL); } void playlist_stop_get_info_thread(void) { G_LOCK(playlist_get_info_going); playlist_get_info_going = FALSE; G_UNLOCK(playlist_get_info_going); g_cond_broadcast(cond_scan); g_thread_join(playlist_get_info_thread); } void playlist_start_get_info_scan(void) { g_mutex_lock(mutex_scan); playlist_get_info_scan_active = TRUE; g_mutex_unlock(mutex_scan); g_cond_signal(cond_scan); } void playlist_remove_dead_files(Playlist *playlist) { GList *node, *next_node; PLAYLIST_LOCK(playlist->mutex); for (node = playlist->entries; node; node = next_node) { PlaylistEntry *entry = PLAYLIST_ENTRY(node->data); next_node = g_list_next(node); if (!entry || !entry->filename) { g_message(G_STRLOC ": Playlist entry is invalid!"); continue; } /* FIXME: What about 'file:///'? */ /* Don't kill URLs */ if (strstr(entry->filename, "://")) continue; /* FIXME: Should test for readability */ if (vfs_file_test(entry->filename, G_FILE_TEST_EXISTS)) continue; if (entry == playlist->position) { /* Don't remove the currently playing song */ if (playback_get_playing()) continue; if (next_node) playlist->position = PLAYLIST_ENTRY(next_node->data); else playlist->position = NULL; } playlist_entry_free(entry); playlist->entries = g_list_delete_link(playlist->entries, node); } PLAYLIST_UNLOCK(playlist->mutex); playlist_generate_shuffle_list(playlist); playlistwin_update_list(playlist); playlist_recalc_total_time(playlist); playlist_manager_update(); } static gint playlist_dupscmp_title(PlaylistEntry * a, PlaylistEntry * b) { const gchar *a_title, *b_title; g_return_val_if_fail(a != NULL, 0); g_return_val_if_fail(b != NULL, 0); if (a->title) a_title = a->title; else { if (strrchr(a->filename, '/')) a_title = strrchr(a->filename, '/') + 1; else a_title = a->filename; } if (b->title) b_title = b->title; else { if (strrchr(a->filename, '/')) b_title = strrchr(b->filename, '/') + 1; else b_title = b->filename; } return strcmp(a_title, b_title); } static gint playlist_dupscmp_filename(PlaylistEntry * a, PlaylistEntry * b ) { gchar *a_filename, *b_filename; g_return_val_if_fail(a != NULL, 0); g_return_val_if_fail(b != NULL, 0); if (strrchr(a->filename, '/')) a_filename = strrchr(a->filename, '/') + 1; else a_filename = a->filename; if (strrchr(b->filename, '/')) b_filename = strrchr(b->filename, '/') + 1; else b_filename = b->filename; return strcmp(a_filename, b_filename); } static gint playlist_dupscmp_path(PlaylistEntry * a, PlaylistEntry * b) { /* simply compare the entire filename string */ return strcmp(a->filename, b->filename); } void playlist_remove_duplicates(Playlist *playlist, PlaylistDupsType type) { GList *node, *next_node; GList *node_cmp, *next_node_cmp; gint (*dups_compare_func)(PlaylistEntry * , PlaylistEntry *); switch ( type ) { case PLAYLIST_DUPS_TITLE: dups_compare_func = playlist_dupscmp_title; break; case PLAYLIST_DUPS_PATH: dups_compare_func = playlist_dupscmp_path; break; case PLAYLIST_DUPS_FILENAME: default: dups_compare_func = playlist_dupscmp_filename; break; } PLAYLIST_LOCK(playlist->mutex); for (node = playlist->entries; node; node = next_node) { PlaylistEntry *entry = PLAYLIST_ENTRY(node->data); next_node = g_list_next(node); if (!entry || !entry->filename) { g_message(G_STRLOC ": Playlist entry is invalid!"); continue; } for (node_cmp = next_node; node_cmp; node_cmp = next_node_cmp) { PlaylistEntry *entry_cmp = PLAYLIST_ENTRY(node_cmp->data); next_node_cmp = g_list_next(node_cmp); if (!entry_cmp || !entry_cmp->filename) { g_message(G_STRLOC ": Playlist entry is invalid!"); continue; } /* compare using the chosen dups_compare_func */ if ( !dups_compare_func( entry , entry_cmp ) ) { if (entry_cmp == playlist->position) { /* Don't remove the currently playing song */ if (playback_get_playing()) continue; if (next_node_cmp) playlist->position = PLAYLIST_ENTRY(next_node_cmp->data); else playlist->position = NULL; } /* check if this was the next item of the external loop; if true, replace it with the next of the next*/ if ( node_cmp == next_node ) next_node = g_list_next(next_node); playlist_entry_free(entry_cmp); playlist->entries = g_list_delete_link(playlist->entries, node_cmp); } } } PLAYLIST_UNLOCK(playlist->mutex); playlistwin_update_list(playlist); playlist_recalc_total_time(playlist); playlist_manager_update(); } void playlist_get_total_time(Playlist * playlist, gulong * total_time, gulong * selection_time, gboolean * total_more, gboolean * selection_more) { PLAYLIST_LOCK(playlist->mutex); *total_time = playlist->pl_total_time; *selection_time = playlist->pl_selection_time; *total_more = playlist->pl_total_more; *selection_more = playlist->pl_selection_more; PLAYLIST_UNLOCK(playlist->mutex); } static void playlist_recalc_total_time_nolock(Playlist *playlist) { GList *list; PlaylistEntry *entry; REQUIRE_LOCK(playlist->mutex); playlist->pl_total_time = 0; playlist->pl_selection_time = 0; playlist->pl_total_more = FALSE; playlist->pl_selection_more = FALSE; for (list = playlist->entries; list; list = g_list_next(list)) { entry = list->data; if (entry->length != -1) playlist->pl_total_time += entry->length / 1000; else playlist->pl_total_more = TRUE; if (entry->selected) { if (entry->length != -1) playlist->pl_selection_time += entry->length / 1000; else playlist->pl_selection_more = TRUE; } } } static void playlist_recalc_total_time(Playlist *playlist) { PLAYLIST_LOCK(playlist->mutex); playlist_recalc_total_time_nolock(playlist); PLAYLIST_UNLOCK(playlist->mutex); } gint playlist_select_search( Playlist *playlist , TitleInput *tuple , gint action ) { GList *entry_list = NULL, *found_list = NULL, *sel_list = NULL; gboolean is_first_search = TRUE; gint num_of_entries_found = 0; #if defined(USE_REGEX_ONIGURUMA) /* set encoding for Oniguruma regex to UTF-8 */ reg_set_encoding( REG_POSIX_ENCODING_UTF8 ); onig_set_default_syntax( ONIG_SYNTAX_POSIX_BASIC ); #endif PLAYLIST_LOCK(playlist->mutex); if ( tuple->track_name != NULL ) { /* match by track_name */ const gchar *regex_pattern = tuple->track_name; regex_t regex; #if defined(USE_REGEX_PCRE) if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 ) #else if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE ) == 0 ) #endif { GList *tfound_list = NULL; if ( is_first_search == TRUE ) entry_list = playlist->entries; else entry_list = found_list; /* use found_list */ for ( ; entry_list ; entry_list = g_list_next(entry_list) ) { PlaylistEntry *entry = entry_list->data; if ( ( entry->tuple != NULL ) && ( entry->tuple->track_name != NULL ) && ( regexec( ®ex , entry->tuple->track_name , 0 , NULL , 0 ) == 0 ) ) { tfound_list = g_list_append( tfound_list , entry ); } } g_list_free( found_list ); /* wipe old found_list */ found_list = tfound_list; /* move tfound_list in found_list */ regfree( ®ex ); } is_first_search = FALSE; } if ( tuple->album_name != NULL ) { /* match by album_name */ const gchar *regex_pattern = tuple->album_name; regex_t regex; #if defined(USE_REGEX_PCRE) if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 ) #else if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE ) == 0 ) #endif { GList *tfound_list = NULL; if ( is_first_search == TRUE ) entry_list = playlist->entries; else entry_list = found_list; /* use found_list */ for ( ; entry_list ; entry_list = g_list_next(entry_list) ) { PlaylistEntry *entry = entry_list->data; if ( ( entry->tuple != NULL ) && ( entry->tuple->album_name != NULL ) && ( regexec( ®ex , entry->tuple->album_name , 0 , NULL , 0 ) == 0 ) ) { tfound_list = g_list_append( tfound_list , entry ); } } g_list_free( found_list ); /* wipe old found_list */ found_list = tfound_list; /* move tfound_list in found_list */ regfree( ®ex ); } is_first_search = FALSE; } if ( tuple->performer != NULL ) { /* match by performer */ const gchar *regex_pattern = tuple->performer; regex_t regex; #if defined(USE_REGEX_PCRE) if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 ) #else if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE ) == 0 ) #endif { GList *tfound_list = NULL; if ( is_first_search == TRUE ) entry_list = playlist->entries; else entry_list = found_list; /* use found_list */ for ( ; entry_list ; entry_list = g_list_next(entry_list) ) { PlaylistEntry *entry = entry_list->data; if ( ( entry->tuple != NULL ) && ( entry->tuple->performer != NULL ) && ( regexec( ®ex , entry->tuple->performer , 0 , NULL , 0 ) == 0 ) ) { tfound_list = g_list_append( tfound_list , entry ); } } g_list_free( found_list ); /* wipe old found_list */ found_list = tfound_list; /* move tfound_list in found_list */ regfree( ®ex ); } is_first_search = FALSE; } if ( tuple->file_name != NULL ) { /* match by file_name */ const gchar *regex_pattern = tuple->file_name; regex_t regex; #if defined(USE_REGEX_PCRE) if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 ) #else if ( regcomp( ®ex , regex_pattern , REG_NOSUB | REG_ICASE ) == 0 ) #endif { GList *tfound_list = NULL; if ( is_first_search == TRUE ) entry_list = playlist->entries; else entry_list = found_list; /* use found_list */ for ( ; entry_list ; entry_list = g_list_next(entry_list) ) { PlaylistEntry *entry = entry_list->data; if ( ( entry->tuple != NULL ) && ( entry->tuple->file_name != NULL ) && ( regexec( ®ex , entry->tuple->file_name , 0 , NULL , 0 ) == 0 ) ) { tfound_list = g_list_append( tfound_list , entry ); } } g_list_free( found_list ); /* wipe old found_list */ found_list = tfound_list; /* move tfound_list in found_list */ regfree( ®ex ); } is_first_search = FALSE; } /* NOTE: action = 0 -> default behaviour, select all matching entries */ /* if some entries are still in found_list, those are what the user is searching for; select them */ for ( sel_list = found_list ; sel_list ; sel_list = g_list_next(sel_list) ) { PlaylistEntry *entry = sel_list->data; entry->selected = TRUE; num_of_entries_found++; } g_list_free( found_list ); PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); return num_of_entries_found; } void playlist_select_all(Playlist *playlist, gboolean set) { GList *list; PLAYLIST_LOCK(playlist->mutex); for (list = playlist->entries; list; list = g_list_next(list)) { PlaylistEntry *entry = list->data; entry->selected = set; } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); } void playlist_select_invert_all(Playlist *playlist) { GList *list; PLAYLIST_LOCK(playlist->mutex); for (list = playlist->entries; list; list = g_list_next(list)) { PlaylistEntry *entry = list->data; entry->selected = !entry->selected; } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); } gboolean playlist_select_invert(Playlist *playlist, guint pos) { GList *list; gboolean invert_ok = FALSE; PLAYLIST_LOCK(playlist->mutex); if ((list = g_list_nth(playlist->entries, pos))) { PlaylistEntry *entry = list->data; entry->selected = !entry->selected; invert_ok = TRUE; } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); return invert_ok; } void playlist_select_range(Playlist *playlist, gint min_pos, gint max_pos, gboolean select) { GList *list; gint i; if (min_pos > max_pos) SWAP(min_pos, max_pos); PLAYLIST_LOCK(playlist->mutex); list = g_list_nth(playlist->entries, min_pos); for (i = min_pos; i <= max_pos && list; i++) { PlaylistEntry *entry = list->data; entry->selected = select; list = g_list_next(list); } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(playlist); } gboolean playlist_read_info_selection(Playlist *playlist) { GList *node; gboolean retval = FALSE; PLAYLIST_LOCK(playlist->mutex); for (node = playlist->entries; node; node = g_list_next(node)) { PlaylistEntry *entry = node->data; if (!entry->selected) continue; retval = TRUE; str_replace_in(&entry->title, NULL); entry->length = -1; /* invalidate mtime to reread */ if (entry->tuple != NULL) entry->tuple->mtime = -1; /* -1 denotes "non-initialized". now 0 is for stream etc. yaz */ if (!playlist_entry_get_info(entry)) { if (g_list_index(playlist->entries, entry) == -1) /* Entry disappeared while we looked it up. Restart. */ node = playlist->entries; } } PLAYLIST_UNLOCK(playlist->mutex); playlistwin_update_list(playlist); playlist_recalc_total_time(playlist); return retval; } void playlist_read_info(Playlist *playlist, guint pos) { GList *node; PLAYLIST_LOCK(playlist->mutex); if ((node = g_list_nth(playlist->entries, pos))) { PlaylistEntry *entry = node->data; str_replace_in(&entry->title, NULL); entry->length = -1; playlist_entry_get_info(entry); } PLAYLIST_UNLOCK(playlist->mutex); playlistwin_update_list(playlist); playlist_recalc_total_time(playlist); } Playlist * playlist_get_active(void) { if (playlists_iter != NULL) return (Playlist *) playlists_iter->data; return (Playlist *) playlists->data; } void playlist_set_shuffle(gboolean shuffle) { Playlist *playlist = playlist_get_active(); if (!playlist) return; PLAYLIST_LOCK(playlist->mutex); playlist_position_before_jump = NULL; cfg.shuffle = shuffle; playlist_generate_shuffle_list_nolock(playlist); PLAYLIST_UNLOCK(playlist->mutex); } Playlist * playlist_new(void) { Playlist *playlist = g_new0(Playlist, 1); playlist->mutex = g_mutex_new(); playlist->loading_playlist = FALSE; playlist_set_current_name(playlist, NULL); playlist_clear(playlist); return playlist; } void playlist_free(Playlist *playlist) { g_mutex_free( playlist->mutex ); g_free( playlist ); return; } Playlist * playlist_new_from_selected(void) { Playlist *newpl = playlist_new(); Playlist *playlist = playlist_get_active(); GList *list = playlist_get_selected(playlist); playlist_add_playlist( newpl ); PLAYLIST_LOCK(playlist->mutex); while ( list != NULL ) { PlaylistEntry *entry = g_list_nth_data(playlist->entries, GPOINTER_TO_INT(list->data)); if ( entry->filename != NULL ) /* paranoid? oh well... */ playlist_add( newpl , entry->filename ); list = g_list_next(list); } PLAYLIST_UNLOCK(playlist->mutex); playlist_recalc_total_time(newpl); playlistwin_update_list(playlist); return newpl; } const gchar * playlist_get_filename_to_play(Playlist *playlist) { const gchar *filename = NULL; if (!playlist) return NULL; PLAYLIST_LOCK(playlist->mutex); if (!playlist->position) { if (cfg.shuffle) playlist->position = playlist->shuffle->data; else playlist->position = playlist->entries->data; } filename = playlist->position->filename; PLAYLIST_UNLOCK(playlist->mutex); return filename; } PlaylistEntry * playlist_get_entry_to_play(Playlist *playlist) { if (!playlist) return NULL; PLAYLIST_LOCK(playlist->mutex); if (!playlist->position) { if (cfg.shuffle) playlist->position = playlist->shuffle->data; else playlist->position = playlist->entries->data; } PLAYLIST_UNLOCK(playlist->mutex); return playlist->position; }