Mercurial > audlegacy
diff audacious/playlist.c @ 0:cb178e5ad177 trunk
[svn] Import audacious source.
author | nenolod |
---|---|
date | Mon, 24 Oct 2005 03:06:47 -0700 |
parents | |
children | 763afa52f416 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audacious/playlist.c Mon Oct 24 03:06:47 2005 -0700 @@ -0,0 +1,2381 @@ +/* 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 02111-1307, 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> + +#include "input.h" +#include "main.h" +#include "mainwin.h" +#include "libaudacious/util.h" +#include "libaudacious/configdb.h" +#include "libaudacious/vfs.h" +#include "equalizer.h" +#include "playback.h" +#include "playlist.h" +#include "playlistwin.h" +#include "playlist_list.h" +#include "skin.h" +#include "urldecode.h" +#include "util.h" + +#include "debug.h" + +typedef gint (*PlaylistCompareFunc) (const PlaylistEntry * a, const PlaylistEntry * b); +typedef void (*PlaylistSaveFunc) (FILE * file); + +PlaylistEntry *playlist_position; +G_LOCK_DEFINE(playlist); + +/* NOTE: match the order listed in PlaylistFormat enum */ +static const gchar *playlist_format_suffixes[] = { + ".m3u", ".pls", NULL +}; + +static GList *playlist = NULL; +static GList *shuffle_list = NULL; +static GList *queued_list = NULL; + + +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(const PlaylistEntry * a, const PlaylistEntry * b); +static gint playlist_compare_filename(const PlaylistEntry * a, const PlaylistEntry * b); +static gint playlist_compare_title(const PlaylistEntry * a, const PlaylistEntry * b); +static gint playlist_compare_date(const PlaylistEntry * a, const PlaylistEntry * b); + +static PlaylistCompareFunc playlist_compare_func_table[] = { + playlist_compare_path, + playlist_compare_filename, + playlist_compare_title, + playlist_compare_date +}; + +static void playlist_save_m3u(FILE * file); +static void playlist_save_pls(FILE * file); + +static PlaylistSaveFunc playlist_save_func_table[] = { + playlist_save_m3u, + playlist_save_pls +}; + + +static guint playlist_load_ins(const gchar * filename, gint pos); + +static void playlist_load_ins_file(const gchar * filename, + const gchar * playlist_name, gint pos, + const gchar * title, gint len); + +static void playlist_generate_shuffle_list(void); +static void playlist_generate_shuffle_list_nolock(void); + +static void playlist_recalc_total_time_nolock(void); +static void playlist_recalc_total_time(void); + + +PlaylistEntry * +playlist_entry_new(const gchar * filename, + const gchar * title, + const gint length) +{ + PlaylistEntry *entry; + + entry = g_new0(PlaylistEntry, 1); + entry->filename = g_strdup(filename); + entry->title = str_to_utf8(title); + entry->length = length; + entry->selected = FALSE; + + return entry; +} + +void +playlist_entry_free(PlaylistEntry * entry) +{ + if (!entry) + return; + + g_free(entry->filename); + g_free(entry->title); + g_free(entry); +} + +static gboolean +playlist_entry_get_info(PlaylistEntry * entry) +{ + gchar *title = NULL; + gint length = -1; + + g_return_val_if_fail(entry != NULL, FALSE); + + input_get_song_info(entry->filename, &title, &length); + if (!title && length == -1) + return FALSE; + + /* entry is still around */ + entry->title = title; + entry->length = length; + + return TRUE; +} + + +const gchar * +playlist_get_current_name(void) +{ + return playlist_current_name; +} + +gboolean +playlist_set_current_name(const gchar * filename) +{ + g_free(playlist_current_name); + + if (!filename) { + playlist_current_name = NULL; + return FALSE; + } + + playlist_current_name = g_strdup(filename); + return TRUE; +} + +static GList * +find_playlist_position_list(void) +{ + REQUIRE_STATIC_LOCK(playlist); + + if (!playlist_position) { + if (cfg.shuffle) + return shuffle_list; + else + return playlist; + } + + if (cfg.shuffle) + return g_list_find(shuffle_list, playlist_position); + else + return g_list_find(playlist, playlist_position); +} + +static void +play_queued(void) +{ + GList *tmp = queued_list; + + REQUIRE_STATIC_LOCK(playlist); + + playlist_position = queued_list->data; + queued_list = g_list_remove_link(queued_list, queued_list); + g_list_free_1(tmp); +} + +void +playlist_clear(void) +{ + if (bmp_playback_get_playing()) + bmp_playback_stop(); + + PLAYLIST_LOCK(); + + if (playlist) { + g_list_foreach(playlist, (GFunc) playlist_entry_free, NULL); + g_list_free(playlist); + + playlist = NULL; + playlist_position = NULL; + } + + PLAYLIST_UNLOCK(); + + playlist_generate_shuffle_list(); + playlistwin_update_list(); + playlist_recalc_total_time(); +} + +void +playlist_delete_node(GList * node, gboolean * set_info_text, + gboolean * restart_playing) +{ + PlaylistEntry *entry; + GList *playing_song = NULL; + + REQUIRE_STATIC_LOCK(playlist); + + /* 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, playlist_position); + + entry = PLAYLIST_ENTRY(node->data); + + if (playing_song == node) { + *set_info_text = TRUE; + + if (bmp_playback_get_playing()) { + PLAYLIST_UNLOCK(); + bmp_playback_stop(); + PLAYLIST_LOCK(); + *restart_playing = TRUE; + } + + playing_song = find_playlist_position_list(); + + 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_get(), entry) == -1) + return; + + } + else if (g_list_position(playlist, playing_song) > + g_list_position(playlist, node)) { + *set_info_text = TRUE; + } + + shuffle_list = g_list_remove(shuffle_list, entry); + playlist = g_list_remove_link(playlist, node); + playlist_entry_free(entry); + g_list_free_1(node); + + playlist_recalc_total_time_nolock(); +} + +void +playlist_delete_index(guint pos) +{ + gboolean restart_playing = FALSE, set_info_text = FALSE; + GList *node; + + PLAYLIST_LOCK(); + + if (!playlist) { + PLAYLIST_UNLOCK(); + return; + } + + node = g_list_nth(playlist, pos); + + if (!node) { + PLAYLIST_UNLOCK(); + return; + } + + playlist_delete_node(node, &set_info_text, &restart_playing); + + PLAYLIST_UNLOCK(); + + playlist_recalc_total_time(); + + playlistwin_update_list(); + if (restart_playing) { + if (playlist_position) { + bmp_playback_initiate(); + } + else { + mainwin_clear_song_info(); + } + } + else if (set_info_text) { + mainwin_set_info_text(); + } +} + +void +playlist_delete_filenames(GList * filenames) +{ + GList *node, *fnode; + gboolean set_info_text = FALSE, restart_playing = FALSE; + + PLAYLIST_LOCK(); + + for (fnode = filenames; fnode; fnode = g_list_next(fnode)) { + node = playlist; + + while (node) { + GList *next = g_list_next(node); + PlaylistEntry *entry = node->data; + + if (!strcmp(entry->filename, fnode->data)) + playlist_delete_node(node, &set_info_text, &restart_playing); + + node = next; + } + } + + playlist_recalc_total_time(); + PLAYLIST_UNLOCK(); + + playlistwin_update_list(); + + if (restart_playing) { + if (playlist_position) { + bmp_playback_initiate(); + } + else { + mainwin_clear_song_info(); + } + } + else if (set_info_text) { + mainwin_set_info_text(); + } + +} + +void +playlist_delete(gboolean crop) +{ + gboolean restart_playing = FALSE, set_info_text = FALSE; + GList *node, *next_node; + PlaylistEntry *entry; + + PLAYLIST_LOCK(); + + node = playlist; + + while (node) { + entry = PLAYLIST_ENTRY(node->data); + + next_node = g_list_next(node); + + if ((entry->selected && !crop) || (!entry->selected && crop)) { + playlist_delete_node(node, &set_info_text, &restart_playing); + } + + node = next_node; + } + + PLAYLIST_UNLOCK(); + + playlist_recalc_total_time(); + + if (set_info_text) { + mainwin_set_info_text(); + } + + if (restart_playing) { + if (playlist_position) { + bmp_playback_initiate(); + } + else { + mainwin_clear_song_info(); + } + } + + playlistwin_update_list(); +} + +static void +__playlist_ins_with_info(const gchar * filename, + gint pos, + const gchar * title, + gint len) +{ + g_return_if_fail(filename != NULL); + + PLAYLIST_LOCK(); + playlist = g_list_insert(playlist, + playlist_entry_new(filename, title, len), + pos); + PLAYLIST_UNLOCK(); + + playlist_get_info_scan_active = TRUE; +} + +static void +__playlist_ins(const gchar * filename, gint pos) +{ + __playlist_ins_with_info(filename, pos, NULL, -1); + playlist_recalc_total_time(); +} + + +PlaylistFormat +playlist_format_get_from_name(const gchar * filename) +{ + int i; + + for (i = 0; i < PLAYLIST_FORMAT_COUNT; i++) + { + if (str_has_suffix_nocase(filename, playlist_format_suffixes[i])) + return i; + } + + return PLAYLIST_FORMAT_UNKNOWN; +} + +gboolean +is_playlist_name(const gchar * filename) +{ + g_return_val_if_fail(filename != NULL, FALSE); + return playlist_format_get_from_name(filename) != PLAYLIST_FORMAT_UNKNOWN; +} + + +gboolean +playlist_ins(const gchar * filename, gint pos) +{ + gchar buf[64], *p; + gint r; + VFSFile *file; + + if (is_playlist_name(filename)) { + playlist_load_ins(filename, pos); + return TRUE; + } + + if (input_check_file(filename, TRUE)) { + __playlist_ins(filename, pos); + playlist_generate_shuffle_list(); + playlistwin_update_list(); + 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(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 (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(const gchar * filename) +{ + return playlist_ins(filename, -1); +} + +guint +playlist_add_dir(const gchar * directory) +{ + return playlist_ins_dir(directory, -1, TRUE); +} + +guint +playlist_add_url(const gchar * url) +{ + return playlist_ins_url(url, -1); +} + +guint +playlist_ins_dir(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(node->data, pos); + g_free(node->data); + entries++; + if (pos >= 0) + pos++; + } + + g_list_free(list); + + playlist_recalc_total_time(); + playlist_generate_shuffle_list(); + playlistwin_update_list(); + return entries; +} + +guint +playlist_ins_url(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(string != NULL, 0); + + playlistwin_update_list(); + + while (*string) { + GList *node; + tmp = strchr(string, '\n'); + if (tmp) { + if (*(tmp - 1) == '\r') + *(tmp - 1) = '\0'; + *tmp = '\0'; + } + + if (!(decoded = xmms_urldecode_path(string))) + decoded = g_strdup(string); + + if (g_file_test(decoded, G_FILE_TEST_IS_DIR)) { + i = playlist_ins_dir(decoded, pos, FALSE); + } + else { + if (is_playlist_name(decoded)) { + i = playlist_load_ins(decoded, pos); + } + else { + success = playlist_ins(decoded, pos); + i = 1; + } + } + + g_free(decoded); + + PLAYLIST_LOCK(); + node = g_list_nth(playlist_get(), pos); + PLAYLIST_UNLOCK(); + + entries += i; + + if (first) { + first = FALSE; + firstpos = pos; + } + + if (pos >= 0) + pos += i; + if (!tmp) + break; + + string = tmp + 1; + } + + playlist_recalc_total_time(); + playlist_generate_shuffle_list(); + playlistwin_update_list(); + + return entries; +} + +void +playlist_set_info(const gchar * title, gint length, gint rate, + gint freq, gint nch) +{ + PLAYLIST_LOCK(); + + if (playlist_position) { + g_free(playlist_position->title); + playlist_position->title = g_strdup(title); + playlist_position->length = length; + } + + PLAYLIST_UNLOCK(); + + playlist_recalc_total_time(); + + mainwin_set_song_info(rate, freq, nch); +} + +void +playlist_check_pos_current(void) +{ + gint pos, row, bottom; + + PLAYLIST_LOCK(); + if (!playlist || !playlist_position || !playlistwin_list) { + PLAYLIST_UNLOCK(); + return; + } + + pos = g_list_index(playlist, playlist_position); + + if (playlistwin_item_visible(pos)) { + PLAYLIST_UNLOCK(); + return; + } + + bottom = MAX(0, playlist_get_length_nolock() - + playlistwin_list->pl_num_visible); + row = CLAMP(pos - playlistwin_list->pl_num_visible / 2, 0, bottom); + PLAYLIST_UNLOCK(); + playlistwin_set_toprow(row); +} + +void +playlist_next(void) +{ + GList *plist_pos_list; + gboolean restart_playing = FALSE; + + PLAYLIST_LOCK(); + if (!playlist) { + PLAYLIST_UNLOCK(); + return; + } + + plist_pos_list = find_playlist_position_list(); + + if (!cfg.repeat && !g_list_next(plist_pos_list)) { + PLAYLIST_UNLOCK(); + return; + } + + if (bmp_playback_get_playing()) { + /* We need to stop before changing playlist_position */ + PLAYLIST_UNLOCK(); + bmp_playback_stop(); + PLAYLIST_LOCK(); + restart_playing = TRUE; + } + + plist_pos_list = find_playlist_position_list(); + if (queued_list) + play_queued(); + 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(); + if (cfg.shuffle) + playlist_position = shuffle_list->data; + else + playlist_position = playlist->data; + } + PLAYLIST_UNLOCK(); + playlist_check_pos_current(); + + if (restart_playing) + bmp_playback_initiate(); + else { + mainwin_set_info_text(); + playlistwin_update_list(); + } +} + +void +playlist_prev(void) +{ + GList *plist_pos_list; + gboolean restart_playing = FALSE; + + PLAYLIST_LOCK(); + if (!playlist) { + PLAYLIST_UNLOCK(); + return; + } + + plist_pos_list = find_playlist_position_list(); + + if (!cfg.repeat && !g_list_previous(plist_pos_list)) { + PLAYLIST_UNLOCK(); + return; + } + + if (bmp_playback_get_playing()) { + /* We need to stop before changing playlist_position */ + PLAYLIST_UNLOCK(); + bmp_playback_stop(); + PLAYLIST_LOCK(); + restart_playing = TRUE; + } + + plist_pos_list = find_playlist_position_list(); + 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(); + if (cfg.shuffle) + node = g_list_last(shuffle_list); + else + node = g_list_last(playlist); + if (node) + playlist_position = node->data; + } + + PLAYLIST_UNLOCK(); + + playlist_check_pos_current(); + + if (restart_playing) + bmp_playback_initiate(); + else { + mainwin_set_info_text(); + playlistwin_update_list(); + } +} + +void +playlist_queue(void) +{ + GList *list = playlist_get_selected(); + GList *it = list; + + PLAYLIST_LOCK(); + + while (it) { + GList *next = g_list_next(it); + GList *tmp; + + it->data = g_list_nth_data(playlist, GPOINTER_TO_INT(it->data)); + if ((tmp = g_list_find(queued_list, it->data))) { + queued_list = g_list_remove_link(queued_list, tmp); + g_list_free_1(tmp); + list = g_list_remove_link(list, it); + g_list_free_1(it); + } + + it = next; + } + + queued_list = g_list_concat(queued_list, list); + + PLAYLIST_UNLOCK(); + + playlist_recalc_total_time(); + playlistwin_update_list(); +} + +void +playlist_queue_position(guint pos) +{ + GList *tmp; + PlaylistEntry *entry; + + PLAYLIST_LOCK(); + entry = g_list_nth_data(playlist, pos); + if ((tmp = g_list_find(queued_list, entry))) { + queued_list = g_list_remove_link(queued_list, tmp); + g_list_free_1(tmp); + } + else + queued_list = g_list_append(queued_list, entry); + PLAYLIST_UNLOCK(); + + playlist_recalc_total_time(); + playlistwin_update_list(); +} + +gboolean +playlist_is_position_queued(guint pos) +{ + PlaylistEntry *entry; + GList *tmp; + + PLAYLIST_LOCK(); + entry = g_list_nth_data(playlist, pos); + tmp = g_list_find(queued_list, entry); + PLAYLIST_UNLOCK(); + + return tmp != NULL; +} + +void +playlist_clear_queue(void) +{ + PLAYLIST_LOCK(); + g_list_free(queued_list); + queued_list = NULL; + PLAYLIST_UNLOCK(); + + playlist_recalc_total_time(); + playlistwin_update_list(); +} + +void +playlist_queue_remove(guint pos) +{ + void *entry; + + PLAYLIST_LOCK(); + entry = g_list_nth_data(playlist, pos); + queued_list = g_list_remove(queued_list, entry); + PLAYLIST_UNLOCK(); + + playlistwin_update_list(); +} + +gint +playlist_get_queue_position(PlaylistEntry * entry) +{ + return g_list_index(queued_list, entry); +} + +void +playlist_set_position(guint pos) +{ + GList *node; + gboolean restart_playing = FALSE; + + PLAYLIST_LOCK(); + if (!playlist) { + PLAYLIST_UNLOCK(); + return; + } + + node = g_list_nth(playlist, pos); + if (!node) { + PLAYLIST_UNLOCK(); + return; + } + + if (bmp_playback_get_playing()) { + /* We need to stop before changing playlist_position */ + PLAYLIST_UNLOCK(); + bmp_playback_stop(); + PLAYLIST_LOCK(); + restart_playing = TRUE; + } + + playlist_position = node->data; + PLAYLIST_UNLOCK(); + playlist_check_pos_current(); + + if (restart_playing) + bmp_playback_initiate(); + else { + mainwin_set_info_text(); + playlistwin_update_list(); + } + + /* + * Regenerate the shuffle list when the user set a position + * manually + */ + playlist_generate_shuffle_list(); + playlist_recalc_total_time(); +} + +void +playlist_eof_reached(void) +{ + GList *plist_pos_list; + + bmp_playback_stop(); + + PLAYLIST_LOCK(); + plist_pos_list = find_playlist_position_list(); + + if (cfg.no_playlist_advance) { + PLAYLIST_UNLOCK(); + mainwin_clear_song_info(); + if (cfg.repeat) + bmp_playback_initiate(); + return; + } + + if (queued_list) { + play_queued(); + } + else if (!g_list_next(plist_pos_list)) { + if (cfg.shuffle) { + playlist_position = NULL; + playlist_generate_shuffle_list_nolock(); + } + else + playlist_position = playlist->data; + + if (!cfg.repeat) { + PLAYLIST_UNLOCK(); + mainwin_clear_song_info(); + mainwin_set_info_text(); + return; + } + } + else + playlist_position = g_list_next(plist_pos_list)->data; + + PLAYLIST_UNLOCK(); + + playlist_check_pos_current(); + bmp_playback_initiate(); + mainwin_set_info_text(); + playlistwin_update_list(); +} + +gint +playlist_get_length(void) +{ + gint retval; + + PLAYLIST_LOCK(); + retval = playlist_get_length_nolock(); + PLAYLIST_UNLOCK(); + + return retval; +} + +gint +playlist_queue_get_length(void) +{ + gint length; + + PLAYLIST_LOCK(); + length = g_list_length(queued_list); + PLAYLIST_UNLOCK(); + + return length; +} + +gint +playlist_get_length_nolock(void) +{ + REQUIRE_STATIC_LOCK(playlist); + return g_list_length(playlist); +} + +gchar * +playlist_get_info_text(void) +{ + gchar *text, *title, *numbers, *length; + + PLAYLIST_LOCK(); + if (!playlist_position) { + PLAYLIST_UNLOCK(); + 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() + 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(); + + 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(void) +{ + gint len = 0; + + PLAYLIST_LOCK(); + if (playlist && playlist_position) + len = playlist_position->length; + PLAYLIST_UNLOCK(); + + return len; +} + +static void +playlist_save_m3u(FILE * file) +{ + GList *node; + + g_return_if_fail(file != NULL); + + if (cfg.use_pl_metadata) + g_fprintf(file, "#EXTM3U\n"); + + PLAYLIST_LOCK(); + + for (node = playlist; node; node = g_list_next(node)) { + PlaylistEntry *entry = PLAYLIST_ENTRY(node->data); + + if (entry->title && cfg.use_pl_metadata) { + gint seconds; + + if (entry->length > 0) + seconds = (entry->length) / 1000; + else + seconds = -1; + + g_fprintf(file, "#EXTINF:%d,%s\n", seconds, entry->title); + } + + g_fprintf(file, "%s\n", entry->filename); + } + + PLAYLIST_UNLOCK(); +} + +static void +playlist_save_pls(FILE * file) +{ + GList *node; + + g_return_if_fail(file != NULL); + + g_fprintf(file, "[playlist]\n"); + g_fprintf(file, "NumberOfEntries=%d\n", playlist_get_length()); + + PLAYLIST_LOCK(); + + for (node = playlist; node; node = g_list_next(node)) { + PlaylistEntry *entry = PLAYLIST_ENTRY(node->data); + + g_fprintf(file, "File%d=%s\n", g_list_position(playlist, node) + 1, + entry->filename); + } + + PLAYLIST_UNLOCK(); +} + +gboolean +playlist_save(const gchar * filename, + PlaylistFormat format) +{ + FILE *file; + + g_return_val_if_fail(filename != NULL, FALSE); + + playlist_set_current_name(filename); + + if ((file = fopen(filename, "w")) == NULL) + return FALSE; + + playlist_save_func_table[format](file); + + return (fclose(file) == 0); +} + +gboolean +playlist_load(const gchar * filename) +{ + return playlist_load_ins(filename, -1); +} + + +static void +playlist_load_ins_file(const gchar * filename_p, + const gchar * playlist_name, gint pos, + const gchar * title, gint len) +{ + gchar *filename; + gchar *tmp, *path; + + g_return_if_fail(filename_p != NULL); + g_return_if_fail(playlist_name != NULL); + + filename = g_strstrip(g_strdup(filename_p)); + + if (cfg.use_backslash_as_dir_delimiter) { + while ((tmp = strchr(filename, '\\')) != NULL) + *tmp = '/'; + } + + if (filename[0] != '/' && !strstr(filename, "://")) { + path = g_strdup(playlist_name); + if ((tmp = strrchr(path, '/'))) + *tmp = '\0'; + else { + __playlist_ins_with_info(filename, pos, title, len); + return; + } + tmp = g_build_filename(path, filename, NULL); + __playlist_ins_with_info(tmp, pos, title, len); + g_free(tmp); + g_free(path); + } + else + __playlist_ins_with_info(filename, pos, title, len); + + g_free(filename); +} + +static void +parse_extm3u_info(const gchar * info, gchar ** title, gint * length) +{ + gchar *str; + + g_return_if_fail(info != NULL); + g_return_if_fail(title != NULL); + g_return_if_fail(length != NULL); + + *title = NULL; + *length = -1; + + if (!str_has_prefix_nocase(info, "#EXTINF:")) { + g_message("Invalid m3u metadata (%s)", info); + return; + } + + info += 8; + + *length = atoi(info); + if (*length <= 0) + *length = -1; + else + *length *= 1000; + + if ((str = strchr(info, ','))) { + *title = g_strstrip(g_strdup(str + 1)); + if (strlen(*title) < 1) { + g_free(*title); + *title = NULL; + } + } +} + +static guint +playlist_load_pls(const gchar * filename, gint pos) +{ + guint i, count, added_count = 0; + gchar key[10]; + gchar *line; + + g_return_val_if_fail(filename != NULL, 0); + + if (!str_has_suffix_nocase(filename, ".pls")) + return 0; + + if (!(line = read_ini_string(filename, "playlist", "NumberOfEntries"))) + return 0; + + count = atoi(line); + g_free(line); + + for (i = 1; i <= count; i++) { + g_snprintf(key, sizeof(key), "File%d", i); + if ((line = read_ini_string(filename, "playlist", key))) { + playlist_load_ins_file(line, filename, pos, NULL, -1); + added_count++; + + if (pos >= 0) + pos++; + + g_free(line); + } + } + + playlist_generate_shuffle_list(); + playlistwin_update_list(); + + return added_count; +} + +static guint +playlist_load_m3u(const gchar * filename, gint pos) +{ + FILE *file; + gchar *line; + gchar *ext_info = NULL, *ext_title = NULL; + gsize line_len = 1024; + gint ext_len = -1; + gboolean is_extm3u = FALSE; + guint added_count = 0; + + if (!(file = fopen(filename, "r"))) + return 0; + + line = g_malloc(line_len); + while (fgets(line, line_len, file)) { + while (strlen(line) == line_len - 1 && line[strlen(line) - 1] != '\n') { + line_len += 1024; + line = g_realloc(line, line_len); + fgets(&line[strlen(line)], 1024, file); + } + + while (line[strlen(line) - 1] == '\r' || + line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + if (str_has_prefix_nocase(line, "#EXTM3U")) { + is_extm3u = TRUE; + continue; + } + + if (is_extm3u && str_has_prefix_nocase(line, "#EXTINF:")) { + str_replace_in(&ext_info, g_strdup(line)); + continue; + } + + if (line[0] == '#' || strlen(line) == 0) { + if (ext_info) { + g_free(ext_info); + ext_info = NULL; + } + continue; + } + + if (is_extm3u) { + if (cfg.use_pl_metadata && ext_info) + parse_extm3u_info(ext_info, &ext_title, &ext_len); + g_free(ext_info); + ext_info = NULL; + } + + playlist_load_ins_file(line, filename, pos, ext_title, ext_len); + + str_replace_in(&ext_title, NULL); + ext_len = -1; + + added_count++; + if (pos >= 0) + pos++; + } + + fclose(file); + g_free(line); + + playlist_generate_shuffle_list(); + playlistwin_update_list(); + + if (g_ascii_strcasecmp(filename, BMP_PLAYLIST_BASENAME)) + playlist_set_current_name(NULL); + else + playlist_set_current_name(filename); + + return added_count; +} + +static guint +playlist_load_ins(const gchar * filename, gint pos) +{ + guint added_count; + + g_return_val_if_fail(filename != NULL, 0); + + /* .pls ? */ + if ((added_count = playlist_load_pls(filename, pos)) > 0) + return added_count; + + /* Assume .m3u */ + return playlist_load_m3u(filename, pos); +} + +GList * +get_playlist_nth(guint nth) +{ + REQUIRE_STATIC_LOCK(playlist); + return g_list_nth(playlist, nth); +} + + +GList * +playlist_get(void) +{ + REQUIRE_STATIC_LOCK(playlist); + return playlist; +} + +gint +playlist_get_position_nolock(void) +{ + REQUIRE_STATIC_LOCK(playlist); + + if (playlist && playlist_position) + return g_list_index(playlist, playlist_position); + return 0; +} + +gint +playlist_get_position(void) +{ + gint pos; + + PLAYLIST_LOCK(); + pos = playlist_get_position_nolock(); + PLAYLIST_UNLOCK(); + + return pos; +} + +gchar * +playlist_get_filename(guint pos) +{ + gchar *filename; + PlaylistEntry *entry; + GList *node; + + PLAYLIST_LOCK(); + if (!playlist) { + PLAYLIST_UNLOCK(); + return NULL; + } + node = g_list_nth(playlist, pos); + if (!node) { + PLAYLIST_UNLOCK(); + return NULL; + } + entry = node->data; + + filename = g_strdup(entry->filename); + PLAYLIST_UNLOCK(); + + return filename; +} + +gchar * +playlist_get_songtitle(guint pos) +{ + gchar *title = NULL; + PlaylistEntry *entry; + GList *node; + + PLAYLIST_LOCK(); + + if (!playlist) { + PLAYLIST_UNLOCK(); + return NULL; + } + + if (!(node = g_list_nth(playlist, pos))) { + PLAYLIST_UNLOCK(); + return NULL; + } + + entry = node->data; + + /* FIXME: simplify this logic */ + if (!entry->title && entry->length == -1) { + if (playlist_entry_get_info(entry)) + title = entry->title; + } + else { + title = entry->title; + } + + PLAYLIST_UNLOCK(); + + if (!title) { + title = g_path_get_basename(entry->filename); + return str_replace(title, filename_to_utf8(title)); + } + + return str_to_utf8(title); +} + +gint +playlist_get_songtime(guint pos) +{ + gint song_time = -1; + PlaylistEntry *entry; + GList *node; + + PLAYLIST_LOCK(); + + if (!playlist) { + PLAYLIST_UNLOCK(); + return -1; + } + + if (!(node = g_list_nth(playlist, pos))) { + PLAYLIST_UNLOCK(); + return -1; + } + + entry = node->data; + + if (!entry->title && entry->length == -1) { + if (playlist_entry_get_info(entry)) + song_time = entry->length; + + PLAYLIST_UNLOCK(); + } + else { + song_time = entry->length; + PLAYLIST_UNLOCK(); + } + + return song_time; +} + +static gint +playlist_compare_title(const PlaylistEntry * a, + const 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 strcasecmp(a_title, b_title); +} + +static gint +playlist_compare_filename(const PlaylistEntry * a, + const 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(const PlaylistEntry * a, + const PlaylistEntry * b) +{ + return path_compare(a->filename, b->filename); +} + +static gint +playlist_compare_date(const PlaylistEntry * a, + const 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(PlaylistSortType type) +{ + playlist_remove_dead_files(); + PLAYLIST_LOCK(); + playlist = + g_list_sort(playlist, + (GCompareFunc) playlist_compare_func_table[type]); + PLAYLIST_UNLOCK(); +} + +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(PlaylistSortType type) +{ + PLAYLIST_LOCK(); + playlist = playlist_sort_selected_generic(playlist, (GCompareFunc) + playlist_compare_func_table + [type]); + PLAYLIST_UNLOCK(); +} + +void +playlist_reverse(void) +{ + PLAYLIST_LOCK(); + playlist = g_list_reverse(playlist); + PLAYLIST_UNLOCK(); +} + +static GList * +playlist_shuffle_list(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; + + REQUIRE_STATIC_LOCK(playlist); + + 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(void) +{ + PLAYLIST_LOCK(); + playlist = playlist_shuffle_list(playlist); + PLAYLIST_UNLOCK(); +} + +GList * +playlist_get_selected(void) +{ + GList *node, *list = NULL; + gint i = 0; + + PLAYLIST_LOCK(); + for (node = playlist_get(); 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(); + return g_list_reverse(list); +} + +void +playlist_clear_selected(void) +{ + GList *node = NULL; + gint i = 0; + + PLAYLIST_LOCK(); + for (node = playlist_get(); node; node = g_list_next(node), i++) { + PLAYLIST_ENTRY(node->data)->selected = FALSE; + } + PLAYLIST_UNLOCK(); + playlist_recalc_total_time(); +} + +gint +playlist_get_num_selected(void) +{ + GList *node; + gint num = 0; + + PLAYLIST_LOCK(); + for (node = playlist_get(); node; node = g_list_next(node)) { + PlaylistEntry *entry = node->data; + if (entry->selected) + num++; + } + PLAYLIST_UNLOCK(); + return num; +} + + +static void +playlist_generate_shuffle_list(void) +{ + PLAYLIST_LOCK(); + playlist_generate_shuffle_list_nolock(); + PLAYLIST_UNLOCK(); +} + +static void +playlist_generate_shuffle_list_nolock(void) +{ + GList *node; + gint numsongs; + + REQUIRE_STATIC_LOCK(playlist); + + if (shuffle_list) { + g_list_free(shuffle_list); + shuffle_list = NULL; + } + + if (!cfg.shuffle || !playlist) + return; + + shuffle_list = playlist_shuffle_list(g_list_copy(playlist)); + numsongs = g_list_length(shuffle_list); + + if (playlist_position) { + gint i = g_list_index(shuffle_list, playlist_position); + node = g_list_nth(shuffle_list, i); + shuffle_list = g_list_remove_link(shuffle_list, node); + shuffle_list = g_list_prepend(shuffle_list, node->data); + } +} + +void +playlist_fileinfo(guint pos) +{ + gchar *path = NULL; + GList *node; + + PLAYLIST_LOCK(); + if ((node = g_list_nth(playlist_get(), pos))) { + PlaylistEntry *entry = node->data; + path = g_strdup(entry->filename); + } + PLAYLIST_UNLOCK(); + if (path) { + input_file_info_box(path); + g_free(path); + } +} + +void +playlist_fileinfo_current(void) +{ + gchar *path = NULL; + + PLAYLIST_LOCK(); + if (playlist_get() && playlist_position) + path = g_strdup(playlist_position->filename); + PLAYLIST_UNLOCK(); + + if (path) { + input_file_info_box(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; + + if (cfg.use_pl_metadata && + cfg.get_info_on_load && + playlist_get_info_scan_active) { + + PLAYLIST_LOCK(); + for (node = playlist_get(); node; node = g_list_next(node)) { + entry = node->data; + + if (entry->title || entry->length != -1) + continue; + + if (!playlist_entry_get_info(entry)) { + if (g_list_index(playlist_get(), entry) == -1) + /* Entry disappeared while we looked it up. + Restart. */ + node = playlist_get(); + } + else if (entry->title || entry->length != -1) { + update_playlistwin = TRUE; + if (entry == playlist_position) + update_mainwin = TRUE; + break; + } + } + PLAYLIST_UNLOCK(); + + if (!node) + playlist_get_info_scan_active = FALSE; + } + else if (!cfg.get_info_on_load && + cfg.get_info_on_demand && + cfg.playlist_visible && + !cfg.playlist_shaded && + cfg.use_pl_metadata) { + + gboolean found = FALSE; + + PLAYLIST_LOCK(); + + if (!playlist_get()) { + PLAYLIST_UNLOCK(); + g_usleep(1000000); + continue; + } + + for (node = + g_list_nth(playlist_get(), playlistwin_get_toprow()); + node + && + playlistwin_item_visible(g_list_position + (playlist_get(), node)); + node = g_list_next(node)) { + entry = node->data; + if (entry->title || entry->length != -1) + continue; + + if (!playlist_entry_get_info(entry)) { + if (g_list_index(playlist_get(), entry) == -1) + /* Entry disapeared while we + looked it up. Restart. */ + node = + g_list_nth(playlist_get(), + playlistwin_get_toprow()); + } + else if (entry->title || entry->length != -1) { + update_playlistwin = TRUE; + if (entry == playlist_position) + update_mainwin = TRUE; + found = TRUE; + break; + } + } + + PLAYLIST_UNLOCK(); + + if (!found) { + g_usleep(500000); + continue; + } + } + else + g_usleep(500000); + + if (update_playlistwin) { + playlistwin_update_list(); + update_playlistwin = FALSE; + } + + if (update_mainwin) { + mainwin_set_info_text(); + update_mainwin = FALSE; + } + } + + g_thread_exit(NULL); + return NULL; +} + +void +playlist_start_get_info_thread(void) +{ + playlist_get_info_going = TRUE; + 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_thread_join(playlist_get_info_thread); +} + +void +playlist_start_get_info_scan(void) +{ + playlist_get_info_scan_active = TRUE; +} + +void +playlist_remove_dead_files(void) +{ + GList *node, *next_node; + + PLAYLIST_LOCK(); + + for (node = playlist; 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 (bmp_playback_get_playing()) + continue; + + if (next_node) + playlist_position = PLAYLIST_ENTRY(next_node->data); + else + playlist_position = NULL; + } + + playlist_entry_free(entry); + playlist = g_list_delete_link(playlist, node); + } + + PLAYLIST_UNLOCK(); + + playlist_generate_shuffle_list(); + playlistwin_update_list(); + playlist_recalc_total_time(); +} + +static gulong pl_total_time = 0, pl_selection_time = 0; +static gboolean pl_total_more = FALSE, pl_selection_more = FALSE; + +void +playlist_get_total_time(gulong * total_time, + gulong * selection_time, + gboolean * total_more, + gboolean * selection_more) +{ + PLAYLIST_LOCK(); + *total_time = pl_total_time; + *selection_time = pl_selection_time; + *total_more = pl_total_more; + *selection_more = pl_selection_more; + PLAYLIST_UNLOCK(); +} + + +static void +playlist_recalc_total_time_nolock(void) +{ + GList *list; + PlaylistEntry *entry; + + REQUIRE_STATIC_LOCK(playlist); + + pl_total_time = 0; + pl_selection_time = 0; + pl_total_more = FALSE; + pl_selection_more = FALSE; + + for (list = playlist_get(); list; list = g_list_next(list)) { + entry = list->data; + + if (entry->length != -1) + pl_total_time += entry->length / 1000; + else + pl_total_more = TRUE; + + if (entry->selected) { + if (entry->length != -1) + pl_selection_time += entry->length / 1000; + else + pl_selection_more = TRUE; + } + } +} + +static void +playlist_recalc_total_time(void) +{ + PLAYLIST_LOCK(); + playlist_recalc_total_time_nolock(); + PLAYLIST_UNLOCK(); +} + + +void +playlist_select_all(gboolean set) +{ + GList *list; + + PLAYLIST_LOCK(); + + for (list = playlist_get(); list; list = g_list_next(list)) { + PlaylistEntry *entry = list->data; + entry->selected = set; + } + + PLAYLIST_UNLOCK(); + playlist_recalc_total_time(); +} + +void +playlist_select_invert_all(void) +{ + GList *list; + + PLAYLIST_LOCK(); + + for (list = playlist_get(); list; list = g_list_next(list)) { + PlaylistEntry *entry = list->data; + entry->selected = !entry->selected; + } + + PLAYLIST_UNLOCK(); + playlist_recalc_total_time(); +} + +gboolean +playlist_select_invert(guint pos) +{ + GList *list; + gboolean invert_ok = FALSE; + + PLAYLIST_LOCK(); + + if ((list = g_list_nth(playlist_get(), pos))) { + PlaylistEntry *entry = list->data; + entry->selected = !entry->selected; + invert_ok = TRUE; + } + + PLAYLIST_UNLOCK(); + playlist_recalc_total_time(); + + return invert_ok; +} + + +void +playlist_select_range(gint min_pos, gint max_pos, gboolean select) +{ + GList *list; + gint i; + + if (min_pos > max_pos) + SWAP(min_pos, max_pos); + + PLAYLIST_LOCK(); + + list = g_list_nth(playlist_get(), 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_recalc_total_time(); +} + +gboolean +playlist_read_info_selection(void) +{ + GList *node; + gboolean retval = FALSE; + + PLAYLIST_LOCK(); + + for (node = playlist_get(); 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; + + if (!playlist_entry_get_info(entry)) { + if (g_list_index(playlist_get(), entry) == -1) + /* Entry disappeared while we looked it up. Restart. */ + node = playlist_get(); + } + } + + PLAYLIST_UNLOCK(); + + playlistwin_update_list(); + playlist_recalc_total_time(); + + return retval; +} + +void +playlist_read_info(guint pos) +{ + GList *node; + + PLAYLIST_LOCK(); + + if ((node = g_list_nth(playlist_get(), pos))) { + PlaylistEntry *entry = node->data; + str_replace_in(&entry->title, NULL); + entry->length = -1; + playlist_entry_get_info(entry); + } + + PLAYLIST_UNLOCK(); + + playlistwin_update_list(); + playlist_recalc_total_time(); +} + +void +playlist_set_shuffle(gboolean shuffle) +{ + PLAYLIST_LOCK(); + + cfg.shuffle = shuffle; + playlist_generate_shuffle_list_nolock(); + + PLAYLIST_UNLOCK(); +} + +void +playlist_new(void) +{ + playlist_set_current_name(NULL); + playlist_clear(); + mainwin_clear_song_info(); + mainwin_set_info_text(); +} + + +const gchar * +playlist_get_filename_to_play(void) +{ + const gchar *filename = NULL; + + PLAYLIST_LOCK(); + + if (playlist) { + if (!playlist_position) { + if (cfg.shuffle) + playlist_position = shuffle_list->data; + else + playlist_position = playlist->data; + } + + filename = playlist_position->filename; + } + + PLAYLIST_UNLOCK(); + + return filename; +}