Mercurial > audlegacy
diff src/audacious/playlist.c @ 2313:3149d4b1a9a9 trunk
[svn] - objective-make autodepend fixes
- move all sourcecode into src/ and adjust Makefiles accordingly
author | nenolod |
---|---|
date | Fri, 12 Jan 2007 11:43:40 -0800 |
parents | |
children | d88558b0de0a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/audacious/playlist.c Fri Jan 12 11:43:40 2007 -0800 @@ -0,0 +1,3161 @@ +/* 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 "util.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 + 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; + gint mtime; + + 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; +}