Mercurial > audlegacy
view audacious/playlist.c @ 1938:1d9c1026d9f8 trunk
[svn] - DoubleSize support.
This has bugs, the most notable one being that DoubleSize only works
right if you restart the player.
The second bug is rather obvious too. No osmosis skinengine. No
TinyPlayer. Classic-esque skinengine only. This is because the
doublesize algorithm hates you and wants you to go die in a fire.
author | nenolod |
---|---|
date | Sun, 05 Nov 2006 04:43:16 -0800 |
parents | 1a801187f510 |
children | f027afc2ca2e |
line wrap: on
line source
/* 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 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> #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 "playlist_container.h" #include "ui_playlist.h" #include "urldecode.h" #include "util.h" #include "ui_fileinfo.h" #include "debug.h" typedef gint (*PlaylistCompareFunc) (PlaylistEntry * a, PlaylistEntry * b); typedef void (*PlaylistSaveFunc) (FILE * file); PlaylistEntry *playlist_position; G_LOCK_DEFINE(playlist); static GList *playlist = NULL; static GList *shuffle_list = NULL; static GList *queued_list = NULL; /* 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> */ static gboolean loading_playlist = FALSE; 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(const gchar * filename, gint pos); 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); static gboolean playlist_entry_get_info(PlaylistEntry * entry); 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; } 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()) { ip_data.stop = TRUE; bmp_playback_stop(); ip_data.stop = FALSE; } 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(); ip_data.stop = TRUE; bmp_playback_stop(); ip_data.stop = FALSE; 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, InputPlugin * dec) { g_return_if_fail(filename != NULL); PLAYLIST_LOCK(); playlist = g_list_insert(playlist, playlist_entry_new(filename, title, len, dec), pos); PLAYLIST_UNLOCK(); playlist_get_info_scan_active = TRUE; } static void __playlist_ins_with_info_tuple(const gchar * filename, gint pos, TitleInput *tuple, InputPlugin * dec) { GList *node; PlaylistEntry *entry; g_return_if_fail(filename != NULL); PLAYLIST_LOCK(); playlist = g_list_insert(playlist, playlist_entry_new(filename, tuple->track_name, tuple->length, dec), pos); if(pos < 0 ) { pos = g_list_length(playlist) - 1; /* last element. */ } node = g_list_nth(playlist, 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_get_info_scan_active = TRUE; } static void __playlist_ins(const gchar * filename, gint pos, InputPlugin *dec) { __playlist_ins_with_info(filename, pos, NULL, -1, dec); playlist_recalc_total_time(); } gboolean playlist_ins(const gchar * filename, gint pos) { gchar buf[64], *p; gint r; VFSFile *file; InputPlugin *dec; if (is_playlist_name(filename)) { loading_playlist = TRUE; playlist_load_ins(filename, pos); loading_playlist = FALSE; return TRUE; } if (loading_playlist == TRUE || cfg.playlist_detect == TRUE) dec = NULL; else dec = input_check_file(filename, TRUE); if (cfg.playlist_detect == TRUE || loading_playlist == TRUE || (loading_playlist == FALSE && dec != NULL)) { __playlist_ins(filename, pos, dec); 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 (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(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, NULL); 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) && !queued_list) { PLAYLIST_UNLOCK(); return; } if (bmp_playback_get_playing()) { /* We need to stop before changing playlist_position */ PLAYLIST_UNLOCK(); ip_data.stop = TRUE; bmp_playback_stop(); ip_data.stop = FALSE; 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(); ip_data.stop = TRUE; bmp_playback_stop(); ip_data.stop = FALSE; 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; } gint playlist_get_queue_position_number(guint pos) { PlaylistEntry *entry; gint tmp; PLAYLIST_LOCK(); entry = g_list_nth_data(playlist, pos); tmp = g_list_index(queued_list, entry); PLAYLIST_UNLOCK(); return tmp; } gint playlist_get_queue_qposition_number(guint pos) { PlaylistEntry *entry; gint tmp; PLAYLIST_LOCK(); entry = g_list_nth_data(queued_list, pos); tmp = g_list_index(playlist, entry); PLAYLIST_UNLOCK(); return tmp; } 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(); ip_data.stop = TRUE; bmp_playback_stop(); ip_data.stop = FALSE; 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; if ((cfg.no_playlist_advance && !cfg.repeat) || cfg.stopaftersong) ip_data.stop = TRUE; bmp_playback_stop(); if ((cfg.no_playlist_advance && !cfg.repeat) || cfg.stopaftersong) ip_data.stop = FALSE; 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 (cfg.stopaftersong) { PLAYLIST_UNLOCK(); mainwin_clear_song_info(); mainwin_set_stopaftersong(FALSE); 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) { 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; } gboolean playlist_save(const gchar * filename) { PlaylistContainer *plc = NULL; gchar *ext; g_return_val_if_fail(filename != NULL, FALSE); ext = strrchr(filename, '.') + 1; playlist_set_current_name(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(const gchar * filename) { gboolean ret = FALSE; loading_playlist = TRUE; ret = playlist_load_ins(filename, -1); loading_playlist = FALSE; return ret; } 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; InputPlugin *dec; /* for decoder cache */ g_return_if_fail(filename_p != NULL); g_return_if_fail(playlist_name != 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 (loading_playlist != TRUE || cfg.playlist_detect == FALSE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info(filename, pos, title, len, dec); return; } tmp = g_build_filename(path, filename, NULL); if (loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(tmp, FALSE); else dec = NULL; __playlist_ins_with_info(tmp, pos, title, len, dec); g_free(tmp); g_free(path); } else { if (loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info(filename, pos, title, len, dec); } g_free(filename); } void playlist_load_ins_file_tuple(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); 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 (loading_playlist != TRUE || cfg.playlist_detect == FALSE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info_tuple(filename, pos, tuple, dec); return; } tmp = g_build_filename(path, filename, NULL); if (loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(tmp, FALSE); else dec = NULL; __playlist_ins_with_info_tuple(tmp, pos, tuple, dec); g_free(tmp); g_free(path); } else { if (loading_playlist != TRUE && cfg.playlist_detect != TRUE) dec = input_check_file(filename, FALSE); else dec = NULL; __playlist_ins_with_info_tuple(filename, pos, tuple, dec); } g_free(filename); } static guint playlist_load_ins(const gchar * filename, gint pos) { PlaylistContainer *plc; gchar *ext; 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(); playlistwin_update_list(); return 1; } 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) { 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 == 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(); 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(guint pos) { PlaylistEntry *entry; TitleInput *tuple = NULL; GList *node; PLAYLIST_LOCK(); if (!playlist) { PLAYLIST_UNLOCK(); return NULL; } if (!(node = g_list_nth(playlist, pos))) { PLAYLIST_UNLOCK(); 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(); return tuple; } 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->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(); } else { song_time = entry->length; PLAYLIST_UNLOCK(); } 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(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; PlaylistEntry *entry = NULL; TitleInput *tuple = NULL; gint mtime; PLAYLIST_LOCK(); if ((node = g_list_nth(playlist_get(), pos))) { entry = node->data; tuple = entry->tuple; path = g_strdup(entry->filename); } PLAYLIST_UNLOCK(); /* 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->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(void) { gchar *path = NULL; TitleInput *tuple = NULL; PLAYLIST_LOCK(); if (playlist_get() && 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(); 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; 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->tuple && (entry->tuple->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->tuple == NULL && entry->title != NULL && 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->tuple && (entry->tuple->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->tuple == NULL && entry->title != NULL && 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 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( 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(); 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; } 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 (bmp_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 = g_list_delete_link(playlist, node_cmp); } } } PLAYLIST_UNLOCK(); 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; /* 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_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; } PlaylistEntry * playlist_get_entry_to_play(void) { PLAYLIST_LOCK(); if (playlist) { if (!playlist_position) { if (cfg.shuffle) playlist_position = shuffle_list->data; else playlist_position = playlist->data; } } PLAYLIST_UNLOCK(); return playlist_position; }