aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/src/playlist.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/playlist.c')
-rw-r--r--trunk/src/playlist.c1499
1 files changed, 1499 insertions, 0 deletions
diff --git a/trunk/src/playlist.c b/trunk/src/playlist.c
new file mode 100644
index 000000000..d8f2c6b65
--- /dev/null
+++ b/trunk/src/playlist.c
@@ -0,0 +1,1499 @@
+/* the Music Player Daemon (MPD)
+ * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * 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 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "playlist.h"
+#include "player.h"
+#include "command.h"
+#include "ls.h"
+#include "tag.h"
+#include "conf.h"
+#include "directory.h"
+#include "log.h"
+#include "path.h"
+#include "utils.h"
+#include "sig_handlers.h"
+#include "state_file.h"
+#include "storedPlaylist.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+
+#define PLAYLIST_STATE_STOP 0
+#define PLAYLIST_STATE_PLAY 1
+
+#define PLAYLIST_PREV_UNLESS_ELAPSED 10
+
+#define PLAYLIST_STATE_FILE_STATE "state: "
+#define PLAYLIST_STATE_FILE_RANDOM "random: "
+#define PLAYLIST_STATE_FILE_REPEAT "repeat: "
+#define PLAYLIST_STATE_FILE_CURRENT "current: "
+#define PLAYLIST_STATE_FILE_TIME "time: "
+#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
+#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
+#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
+
+#define PLAYLIST_STATE_FILE_STATE_PLAY "play"
+#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
+#define PLAYLIST_STATE_FILE_STATE_STOP "stop"
+
+#define PLAYLIST_BUFFER_SIZE 2*MAXPATHLEN
+
+#define PLAYLIST_HASH_MULT 4
+
+#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
+#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS 0
+
+static Playlist playlist;
+static int playlist_state = PLAYLIST_STATE_STOP;
+static int playlist_max_length = DEFAULT_PLAYLIST_MAX_LENGTH;
+static int playlist_stopOnError;
+static int playlist_errorCount;
+static int playlist_queueError;
+static int playlist_noGoToNext;
+
+int playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
+
+static void swapOrder(int a, int b);
+static int playPlaylistOrderNumber(int fd, int orderNum);
+static void randomizeOrder(int start, int end);
+
+static void incrPlaylistVersion(void)
+{
+ static unsigned long max = ((mpd_uint32) 1 << 31) - 1;
+ playlist.version++;
+ if (playlist.version >= max) {
+ int i;
+
+ for (i = 0; i < playlist.length; i++) {
+ playlist.songMod[i] = 0;
+ }
+
+ playlist.version = 1;
+ }
+}
+
+void playlistVersionChange(void)
+{
+ int i = 0;
+
+ for (i = 0; i < playlist.length; i++) {
+ playlist.songMod[i] = playlist.version;
+ }
+
+ incrPlaylistVersion();
+}
+
+static void incrPlaylistCurrent(void)
+{
+ if (playlist.current < 0)
+ return;
+
+ if (playlist.current >= playlist.length - 1) {
+ if (playlist.repeat)
+ playlist.current = 0;
+ else
+ playlist.current = -1;
+ } else
+ playlist.current++;
+}
+
+void initPlaylist(void)
+{
+ char *test;
+ int i;
+ ConfigParam *param;
+
+ playlist.length = 0;
+ playlist.repeat = 0;
+ playlist.version = 1;
+ playlist.random = 0;
+ playlist.queued = -1;
+ playlist.current = -1;
+
+ param = getConfigParam(CONF_MAX_PLAYLIST_LENGTH);
+
+ if (param) {
+ playlist_max_length = strtol(param->value, &test, 10);
+ if (*test != '\0') {
+ FATAL("max playlist length \"%s\" is not an integer, "
+ "line %i\n", param->value, param->line);
+ }
+ }
+
+ playlist_saveAbsolutePaths = getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS);
+ if (playlist_saveAbsolutePaths == -1) playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS;
+ else if (playlist_saveAbsolutePaths < 0) exit(EXIT_FAILURE);
+
+ playlist.songs = xmalloc(sizeof(Song *) * playlist_max_length);
+ playlist.songMod = xmalloc(sizeof(mpd_uint32) * playlist_max_length);
+ playlist.order = xmalloc(sizeof(int) * playlist_max_length);
+ playlist.idToPosition = xmalloc(sizeof(int) * playlist_max_length *
+ PLAYLIST_HASH_MULT);
+ playlist.positionToId = xmalloc(sizeof(int) * playlist_max_length);
+
+ memset(playlist.songs, 0, sizeof(char *) * playlist_max_length);
+
+ srandom(time(NULL));
+
+ for (i = 0; i < playlist_max_length * PLAYLIST_HASH_MULT; i++) {
+ playlist.idToPosition[i] = -1;
+ }
+}
+
+static int getNextId(void)
+{
+ static int cur = -1;
+
+ do {
+ cur++;
+ if (cur >= playlist_max_length * PLAYLIST_HASH_MULT) {
+ cur = 0;
+ }
+ } while (playlist.idToPosition[cur] != -1);
+
+ return cur;
+}
+
+void finishPlaylist(void)
+{
+ int i;
+ for (i = 0; i < playlist.length; i++) {
+ if (playlist.songs[i]->type == SONG_TYPE_URL) {
+ freeJustSong(playlist.songs[i]);
+ }
+ }
+
+ playlist.length = 0;
+
+ free(playlist.songs);
+ playlist.songs = NULL;
+ free(playlist.songMod);
+ playlist.songMod = NULL;
+ free(playlist.order);
+ playlist.order = NULL;
+ free(playlist.idToPosition);
+ playlist.idToPosition = NULL;
+ free(playlist.positionToId);
+ playlist.positionToId = NULL;
+}
+
+int clearPlaylist(int fd)
+{
+ int i;
+
+ if (stopPlaylist(fd) < 0)
+ return -1;
+
+ for (i = 0; i < playlist.length; i++) {
+ if (playlist.songs[i]->type == SONG_TYPE_URL) {
+ freeJustSong(playlist.songs[i]);
+ }
+ playlist.idToPosition[playlist.positionToId[i]] = -1;
+ playlist.songs[i] = NULL;
+ }
+ playlist.length = 0;
+ playlist.current = -1;
+
+ incrPlaylistVersion();
+
+ return 0;
+}
+
+int clearStoredPlaylist(int fd, char *utf8file)
+{
+ removeAllFromStoredPlaylistByPath(fd, utf8file);
+ return 0;
+}
+
+int showPlaylist(int fd)
+{
+ int i;
+
+ for (i = 0; i < playlist.length; i++) {
+ fdprintf(fd, "%i:%s\n", i, getSongUrl(playlist.songs[i]));
+ }
+
+ return 0;
+}
+
+void savePlaylistState(FILE *fp)
+{
+ fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
+ switch (playlist_state) {
+ case PLAYLIST_STATE_PLAY:
+ switch (getPlayerState()) {
+ case PLAYER_STATE_PAUSE:
+ fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
+ break;
+ default:
+ fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY);
+ }
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
+ playlist.order[playlist.current]);
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
+ getPlayerElapsedTime());
+ break;
+ default:
+ fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
+ break;
+ }
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, playlist.random);
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, playlist.repeat);
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
+ (int)(getPlayerCrossFade()));
+ fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
+ fflush(fp);
+ showPlaylist(fileno(fp));
+ fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
+}
+
+static void loadPlaylistFromStateFile(FILE *fp, char *buffer,
+ int state, int current, int time)
+{
+ char *temp;
+ int song;
+
+ if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp))
+ state_file_fatal();
+ while (strcmp(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
+ song = atoi(strtok(buffer, ":"));
+ if (!(temp = strtok(NULL, "")))
+ state_file_fatal();
+ if (!addToPlaylist(STDERR_FILENO, temp, 0) && current == song) {
+ if (state != PLAYER_STATE_STOP) {
+ playPlaylist(STDERR_FILENO,
+ playlist.length - 1, 0);
+ }
+ if (state == PLAYER_STATE_PAUSE) {
+ playerPause(STDERR_FILENO);
+ }
+ if (state != PLAYER_STATE_STOP) {
+ seekSongInPlaylist(STDERR_FILENO,
+ playlist.length - 1, time);
+ }
+ }
+ if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp))
+ state_file_fatal();
+ }
+}
+
+void readPlaylistState(FILE *fp)
+{
+ int current = -1;
+ int time = 0;
+ int state = PLAYER_STATE_STOP;
+ char buffer[PLAYLIST_BUFFER_SIZE];
+
+ while (myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) {
+ if (strncmp(buffer, PLAYLIST_STATE_FILE_STATE,
+ strlen(PLAYLIST_STATE_FILE_STATE)) == 0) {
+ if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
+ PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
+ state = PLAYER_STATE_PLAY;
+ } else
+ if (strcmp
+ (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
+ PLAYLIST_STATE_FILE_STATE_PAUSE)
+ == 0) {
+ state = PLAYER_STATE_PAUSE;
+ }
+ } else if (strncmp(buffer, PLAYLIST_STATE_FILE_TIME,
+ strlen(PLAYLIST_STATE_FILE_TIME)) == 0) {
+ time =
+ atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
+ } else
+ if (strncmp
+ (buffer, PLAYLIST_STATE_FILE_REPEAT,
+ strlen(PLAYLIST_STATE_FILE_REPEAT)) == 0) {
+ if (strcmp
+ (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
+ "1") == 0) {
+ setPlaylistRepeatStatus(STDERR_FILENO, 1);
+ } else
+ setPlaylistRepeatStatus(STDERR_FILENO, 0);
+ } else
+ if (strncmp
+ (buffer, PLAYLIST_STATE_FILE_CROSSFADE,
+ strlen(PLAYLIST_STATE_FILE_CROSSFADE)) == 0) {
+ setPlayerCrossFade(atoi
+ (&
+ (buffer
+ [strlen
+ (PLAYLIST_STATE_FILE_CROSSFADE)])));
+ } else
+ if (strncmp
+ (buffer, PLAYLIST_STATE_FILE_RANDOM,
+ strlen(PLAYLIST_STATE_FILE_RANDOM)) == 0) {
+ if (strcmp
+ (&
+ (buffer
+ [strlen(PLAYLIST_STATE_FILE_RANDOM)]),
+ "1") == 0) {
+ setPlaylistRandomStatus(STDERR_FILENO, 1);
+ } else
+ setPlaylistRandomStatus(STDERR_FILENO, 0);
+ } else if (strncmp(buffer, PLAYLIST_STATE_FILE_CURRENT,
+ strlen(PLAYLIST_STATE_FILE_CURRENT))
+ == 0) {
+ if (strlen(buffer) ==
+ strlen(PLAYLIST_STATE_FILE_CURRENT))
+ state_file_fatal();
+ current = atoi(&(buffer
+ [strlen
+ (PLAYLIST_STATE_FILE_CURRENT)]));
+ } else
+ if (strncmp
+ (buffer, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN,
+ strlen(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)
+ ) == 0) {
+ if (state == PLAYER_STATE_STOP)
+ current = -1;
+ loadPlaylistFromStateFile(fp, buffer, state,
+ current, time);
+ }
+ }
+}
+
+static void printPlaylistSongInfo(int fd, int song)
+{
+ printSongInfo(fd, playlist.songs[song]);
+ fdprintf(fd, "Pos: %i\nId: %i\n", song, playlist.positionToId[song]);
+}
+
+int playlistChanges(int fd, mpd_uint32 version)
+{
+ int i;
+
+ for (i = 0; i < playlist.length; i++) {
+ if (version > playlist.version ||
+ playlist.songMod[i] >= version ||
+ playlist.songMod[i] == 0) {
+ printPlaylistSongInfo(fd, i);
+ }
+ }
+
+ return 0;
+}
+
+int playlistChangesPosId(int fd, mpd_uint32 version)
+{
+ int i;
+
+ for (i = 0; i < playlist.length; i++) {
+ if (version > playlist.version ||
+ playlist.songMod[i] >= version ||
+ playlist.songMod[i] == 0) {
+ fdprintf(fd, "cpos: %i\nId: %i\n",
+ i, playlist.positionToId[i]);
+ }
+ }
+
+ return 0;
+}
+
+int playlistInfo(int fd, int song)
+{
+ int i;
+ int begin = 0;
+ int end = playlist.length;
+
+ if (song >= 0) {
+ begin = song;
+ end = song + 1;
+ }
+ if (song >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", song);
+ return -1;
+ }
+
+ for (i = begin; i < end; i++)
+ printPlaylistSongInfo(fd, i);
+
+ return 0;
+}
+
+# define checkSongId(id) { \
+ if(id < 0 || id >= PLAYLIST_HASH_MULT*playlist_max_length || \
+ playlist.idToPosition[id] == -1 ) \
+ { \
+ commandError(fd, ACK_ERROR_NO_EXIST, \
+ "song id doesn't exist: \"%i\"", id); \
+ return -1; \
+ } \
+}
+
+int playlistId(int fd, int id)
+{
+ int i;
+ int begin = 0;
+ int end = playlist.length;
+
+ if (id >= 0) {
+ checkSongId(id);
+ begin = playlist.idToPosition[id];
+ end = begin + 1;
+ }
+
+ for (i = begin; i < end; i++)
+ printPlaylistSongInfo(fd, i);
+
+ return 0;
+}
+
+static void swapSongs(int song1, int song2)
+{
+ Song *sTemp;
+ int iTemp;
+
+ sTemp = playlist.songs[song1];
+ playlist.songs[song1] = playlist.songs[song2];
+ playlist.songs[song2] = sTemp;
+
+ playlist.songMod[song1] = playlist.version;
+ playlist.songMod[song2] = playlist.version;
+
+ playlist.idToPosition[playlist.positionToId[song1]] = song2;
+ playlist.idToPosition[playlist.positionToId[song2]] = song1;
+
+ iTemp = playlist.positionToId[song1];
+ playlist.positionToId[song1] = playlist.positionToId[song2];
+ playlist.positionToId[song2] = iTemp;
+}
+
+static void queueNextSongInPlaylist(void)
+{
+ if (playlist.current < playlist.length - 1) {
+ playlist.queued = playlist.current + 1;
+ DEBUG("playlist: queue song %i:\"%s\"\n",
+ playlist.queued,
+ getSongUrl(playlist.
+ songs[playlist.order[playlist.queued]]));
+ if (queueSong(playlist.songs[playlist.order[playlist.queued]]) <
+ 0) {
+ playlist.queued = -1;
+ playlist_queueError = 1;
+ }
+ } else if (playlist.length && playlist.repeat) {
+ if (playlist.length > 1 && playlist.random) {
+ randomizeOrder(0, playlist.length - 1);
+ }
+ playlist.queued = 0;
+ DEBUG("playlist: queue song %i:\"%s\"\n",
+ playlist.queued,
+ getSongUrl(playlist.
+ songs[playlist.order[playlist.queued]]));
+ if (queueSong(playlist.songs[playlist.order[playlist.queued]]) <
+ 0) {
+ playlist.queued = -1;
+ playlist_queueError = 1;
+ }
+ }
+}
+
+static void syncPlaylistWithQueue(int queue)
+{
+ if (queue && getPlayerQueueState() == PLAYER_QUEUE_BLANK) {
+ queueNextSongInPlaylist();
+ } else if (getPlayerQueueState() == PLAYER_QUEUE_DECODE) {
+ if (playlist.queued != -1)
+ setQueueState(PLAYER_QUEUE_PLAY);
+ else
+ setQueueState(PLAYER_QUEUE_STOP);
+ } else if (getPlayerQueueState() == PLAYER_QUEUE_EMPTY) {
+ setQueueState(PLAYER_QUEUE_BLANK);
+ if (playlist.queued >= 0) {
+ DEBUG("playlist: now playing queued song\n");
+ playlist.current = playlist.queued;
+ }
+ playlist.queued = -1;
+ if (queue)
+ queueNextSongInPlaylist();
+ }
+}
+
+static void lockPlaylistInteraction(void)
+{
+ if (getPlayerQueueState() == PLAYER_QUEUE_PLAY ||
+ getPlayerQueueState() == PLAYER_QUEUE_FULL) {
+ playerQueueLock();
+ syncPlaylistWithQueue(0);
+ }
+}
+
+static void unlockPlaylistInteraction(void)
+{
+ playerQueueUnlock();
+}
+
+static void clearPlayerQueue(void)
+{
+ playlist.queued = -1;
+ switch (getPlayerQueueState()) {
+ case PLAYER_QUEUE_FULL:
+ DEBUG("playlist: dequeue song\n");
+ setQueueState(PLAYER_QUEUE_BLANK);
+ break;
+ case PLAYER_QUEUE_PLAY:
+ DEBUG("playlist: stop decoding queued song\n");
+ setQueueState(PLAYER_QUEUE_STOP);
+ break;
+ }
+}
+
+int addToPlaylist(int fd, char *url, int printId)
+{
+ Song *song;
+
+ DEBUG("add to playlist: %s\n", url);
+
+ if ((song = getSongFromDB(url))) {
+ } else if (!(isValidRemoteUtf8Url(url) &&
+ (song = newSong(url, SONG_TYPE_URL, NULL)))) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "\"%s\" is not in the music db or is "
+ "not a valid url", url);
+ return -1;
+ }
+
+ return addSongToPlaylist(fd, song, printId);
+}
+
+int addToStoredPlaylist(int fd, char *url, char *utf8file)
+{
+ Song *song;
+
+ DEBUG("add to stored playlist: %s\n", url);
+
+ song = getSongFromDB(url);
+ if (song) {
+ appendSongToStoredPlaylistByPath(fd, utf8file, song);
+ return 0;
+ }
+
+ if (!isValidRemoteUtf8Url(url))
+ goto fail;
+
+ song = newSong(url, SONG_TYPE_URL, NULL);
+ if (song) {
+ appendSongToStoredPlaylistByPath(fd, utf8file, song);
+ freeJustSong(song);
+ return 0;
+ }
+
+fail:
+ commandError(fd, ACK_ERROR_NO_EXIST, "\"%s\" is not in the music db"
+ "or is not a valid url", url);
+ return -1;
+}
+
+int addSongToPlaylist(int fd, Song * song, int printId)
+{
+ int id;
+
+ if (playlist.length == playlist_max_length) {
+ commandError(fd, ACK_ERROR_PLAYLIST_MAX,
+ "playlist is at the max size");
+ return -1;
+ }
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.queued >= 0
+ && playlist.current == playlist.length - 1) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ }
+
+ id = getNextId();
+
+ playlist.songs[playlist.length] = song;
+ playlist.songMod[playlist.length] = playlist.version;
+ playlist.order[playlist.length] = playlist.length;
+ playlist.positionToId[playlist.length] = id;
+ playlist.idToPosition[playlist.positionToId[playlist.length]] =
+ playlist.length;
+ playlist.length++;
+
+ if (playlist.random) {
+ int swap;
+ int start;
+ /*if(playlist_state==PLAYLIST_STATE_STOP) start = 0;
+ else */ if (playlist.queued >= 0)
+ start = playlist.queued + 1;
+ else
+ start = playlist.current + 1;
+ if (start < playlist.length) {
+ swap = random() % (playlist.length - start);
+ swap += start;
+ swapOrder(playlist.length - 1, swap);
+ }
+ }
+
+ incrPlaylistVersion();
+
+ if (printId)
+ fdprintf(fd, "Id: %i\n", id);
+
+ return 0;
+}
+
+int swapSongsInPlaylist(int fd, int song1, int song2)
+{
+ int queuedSong = -1;
+ int currentSong = -1;
+
+ if (song1 < 0 || song1 >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", song1);
+ return -1;
+ }
+ if (song2 < 0 || song2 >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", song2);
+ return -1;
+ }
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.queued >= 0) {
+ queuedSong = playlist.order[playlist.queued];
+ }
+ currentSong = playlist.order[playlist.current];
+
+ if (queuedSong == song1 || queuedSong == song2
+ || currentSong == song1 || currentSong == song2) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ }
+
+ swapSongs(song1, song2);
+ if (playlist.random) {
+ int i;
+ int k;
+ int j = -1;
+ for (i = 0; playlist.order[i] != song1; i++) {
+ if (playlist.order[i] == song2)
+ j = i;
+ }
+ k = i;
+ for (; j == -1; i++)
+ if (playlist.order[i] == song2)
+ j = i;
+ swapOrder(k, j);
+ } else {
+ if (playlist.current == song1)
+ playlist.current = song2;
+ else if (playlist.current == song2)
+ playlist.current = song1;
+ }
+
+ incrPlaylistVersion();
+
+ return 0;
+}
+
+int swapSongsInPlaylistById(int fd, int id1, int id2)
+{
+ checkSongId(id1);
+ checkSongId(id2);
+
+ return swapSongsInPlaylist(fd, playlist.idToPosition[id1],
+ playlist.idToPosition[id2]);
+}
+
+#define moveSongFromTo(from, to) { \
+ playlist.idToPosition[playlist.positionToId[from]] = to; \
+ playlist.positionToId[to] = playlist.positionToId[from]; \
+ playlist.songs[to] = playlist.songs[from]; \
+ playlist.songMod[to] = playlist.version; \
+}
+
+int deleteFromPlaylist(int fd, int song)
+{
+ int i;
+ int songOrder;
+
+ if (song < 0 || song >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", song);
+ return -1;
+ }
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.queued >= 0
+ && (playlist.order[playlist.queued] == song
+ || playlist.order[playlist.current] == song)) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ }
+
+ if (playlist.songs[song]->type == SONG_TYPE_URL) {
+ freeJustSong(playlist.songs[song]);
+ }
+
+ playlist.idToPosition[playlist.positionToId[song]] = -1;
+
+ /* delete song from songs array */
+ for (i = song; i < playlist.length - 1; i++) {
+ moveSongFromTo(i + 1, i);
+ }
+ /* now find it in the order array */
+ for (i = 0; i < playlist.length - 1; i++) {
+ if (playlist.order[i] == song)
+ break;
+ }
+ songOrder = i;
+ /* delete the entry from the order array */
+ for (; i < playlist.length - 1; i++)
+ playlist.order[i] = playlist.order[i + 1];
+ /* readjust values in the order array */
+ for (i = 0; i < playlist.length - 1; i++) {
+ if (playlist.order[i] > song)
+ playlist.order[i]--;
+ }
+ /* now take care of other misc stuff */
+ playlist.songs[playlist.length - 1] = NULL;
+ playlist.length--;
+
+ incrPlaylistVersion();
+
+ if (playlist_state != PLAYLIST_STATE_STOP
+ && playlist.current == songOrder) {
+ /*if(playlist.current>=playlist.length) return playerStop(fd);
+ else return playPlaylistOrderNumber(fd,playlist.current); */
+ playerStop(STDERR_FILENO);
+ playlist_noGoToNext = 1;
+ }
+
+ if (playlist.current > songOrder) {
+ playlist.current--;
+ } else if (playlist.current >= playlist.length) {
+ incrPlaylistCurrent();
+ }
+
+ if (playlist.queued > songOrder) {
+ playlist.queued--;
+ }
+
+ return 0;
+}
+
+int deleteFromPlaylistById(int fd, int id)
+{
+ checkSongId(id);
+
+ return deleteFromPlaylist(fd, playlist.idToPosition[id]);
+}
+
+void deleteASongFromPlaylist(Song * song)
+{
+ int i;
+
+ if (NULL == playlist.songs)
+ return;
+
+ for (i = 0; i < playlist.length; i++) {
+ if (song == playlist.songs[i]) {
+ deleteFromPlaylist(STDERR_FILENO, i);
+ }
+ }
+}
+
+int stopPlaylist(int fd)
+{
+ DEBUG("playlist: stop\n");
+ if (playerStop(fd) < 0)
+ return -1;
+ playerCloseAudio();
+ playlist.queued = -1;
+ playlist_state = PLAYLIST_STATE_STOP;
+ playlist_noGoToNext = 0;
+ if (playlist.random)
+ randomizeOrder(0, playlist.length - 1);
+ return 0;
+}
+
+static int playPlaylistOrderNumber(int fd, int orderNum)
+{
+
+ if (playerStop(fd) < 0)
+ return -1;
+
+ playlist_state = PLAYLIST_STATE_PLAY;
+ playlist_noGoToNext = 0;
+ playlist.queued = -1;
+ playlist_queueError = 0;
+
+ DEBUG("playlist: play %i:\"%s\"\n", orderNum,
+ getSongUrl(playlist.songs[playlist.order[orderNum]]));
+
+ if (playerPlay(fd, (playlist.songs[playlist.order[orderNum]])) < 0) {
+ stopPlaylist(fd);
+ return -1;
+ }
+
+ playlist.current = orderNum;
+
+ return 0;
+}
+
+int playPlaylist(int fd, int song, int stopOnError)
+{
+ int i = song;
+
+ clearPlayerError();
+
+ if (song == -1) {
+ if (playlist.length == 0)
+ return 0;
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ return playerSetPause(fd, 0);
+ }
+ if (playlist.current >= 0 && playlist.current < playlist.length) {
+ i = playlist.current;
+ } else {
+ i = 0;
+ }
+ } else if (song < 0 || song >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", song);
+ return -1;
+ }
+
+ if (playlist.random) {
+ if (song == -1 && playlist_state == PLAYLIST_STATE_PLAY) {
+ randomizeOrder(0, playlist.length - 1);
+ } else {
+ if (song >= 0)
+ for (i = 0; song != playlist.order[i]; i++) ;
+ if (playlist_state == PLAYLIST_STATE_STOP) {
+ playlist.current = 0;
+ }
+ swapOrder(i, playlist.current);
+ i = playlist.current;
+ }
+ }
+
+ playlist_stopOnError = stopOnError;
+ playlist_errorCount = 0;
+
+ return playPlaylistOrderNumber(fd, i);
+}
+
+int playPlaylistById(int fd, int id, int stopOnError)
+{
+ if (id == -1) {
+ return playPlaylist(fd, id, stopOnError);
+ }
+
+ checkSongId(id);
+
+ return playPlaylist(fd, playlist.idToPosition[id], stopOnError);
+}
+
+static void syncCurrentPlayerDecodeMetadata(void)
+{
+ Song *songPlayer = playerCurrentDecodeSong();
+ Song *song;
+ int songNum;
+
+ if (!songPlayer)
+ return;
+
+ if (playlist_state != PLAYLIST_STATE_PLAY)
+ return;
+
+ songNum = playlist.order[playlist.current];
+ song = playlist.songs[songNum];
+
+ if (song->type == SONG_TYPE_URL &&
+ 0 == strcmp(getSongUrl(song), songPlayer->url) &&
+ !mpdTagsAreEqual(song->tag, songPlayer->tag)) {
+ if (song->tag)
+ freeMpdTag(song->tag);
+ song->tag = mpdTagDup(songPlayer->tag);
+ playlist.songMod[songNum] = playlist.version;
+ incrPlaylistVersion();
+ }
+}
+
+void syncPlayerAndPlaylist(void)
+{
+ if (playlist_state != PLAYLIST_STATE_PLAY)
+ return;
+
+ if (getPlayerState() == PLAYER_STATE_STOP)
+ playPlaylistIfPlayerStopped();
+ else
+ syncPlaylistWithQueue(!playlist_queueError);
+
+ syncCurrentPlayerDecodeMetadata();
+}
+
+static int currentSongInPlaylist(int fd)
+{
+ if (playlist_state != PLAYLIST_STATE_PLAY)
+ return 0;
+
+ playlist_stopOnError = 0;
+
+ syncPlaylistWithQueue(0);
+
+ if (playlist.current >= 0 && playlist.current < playlist.length) {
+ return playPlaylistOrderNumber(fd, playlist.current);
+ } else
+ return stopPlaylist(fd);
+
+ return 0;
+}
+
+int nextSongInPlaylist(int fd)
+{
+ if (playlist_state != PLAYLIST_STATE_PLAY)
+ return 0;
+
+ syncPlaylistWithQueue(0);
+
+ playlist_stopOnError = 0;
+
+ if (playlist.current < playlist.length - 1) {
+ return playPlaylistOrderNumber(fd, playlist.current + 1);
+ } else if (playlist.length && playlist.repeat) {
+ if (playlist.random)
+ randomizeOrder(0, playlist.length - 1);
+ return playPlaylistOrderNumber(fd, 0);
+ } else {
+ incrPlaylistCurrent();
+ return stopPlaylist(fd);
+ }
+
+ return 0;
+}
+
+void playPlaylistIfPlayerStopped(void)
+{
+ if (getPlayerState() == PLAYER_STATE_STOP) {
+ int error = getPlayerError();
+
+ if (error == PLAYER_ERROR_NOERROR)
+ playlist_errorCount = 0;
+ else
+ playlist_errorCount++;
+
+ if (playlist_state == PLAYLIST_STATE_PLAY
+ && ((playlist_stopOnError && error != PLAYER_ERROR_NOERROR)
+ || error == PLAYER_ERROR_AUDIO
+ || error == PLAYER_ERROR_SYSTEM
+ || playlist_errorCount >= playlist.length)) {
+ stopPlaylist(STDERR_FILENO);
+ } else if (playlist_noGoToNext)
+ currentSongInPlaylist(STDERR_FILENO);
+ else
+ nextSongInPlaylist(STDERR_FILENO);
+ }
+}
+
+int getPlaylistRepeatStatus(void)
+{
+ return playlist.repeat;
+}
+
+int getPlaylistRandomStatus(void)
+{
+ return playlist.random;
+}
+
+int setPlaylistRepeatStatus(int fd, int status)
+{
+ if (status != 0 && status != 1) {
+ commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status);
+ return -1;
+ }
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.repeat && !status && playlist.queued == 0) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ }
+
+ playlist.repeat = status;
+
+ return 0;
+}
+
+int moveSongInPlaylist(int fd, int from, int to)
+{
+ int i;
+ Song *tmpSong;
+ int tmpId;
+ int queuedSong = -1;
+ int currentSong = -1;
+
+ if (from < 0 || from >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", from);
+ return -1;
+ }
+
+ if (to < 0 || to >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", to);
+ return -1;
+ }
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.queued >= 0) {
+ queuedSong = playlist.order[playlist.queued];
+ }
+ currentSong = playlist.order[playlist.current];
+ if (queuedSong == from || queuedSong == to
+ || currentSong == from || currentSong == to) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ }
+
+ tmpSong = playlist.songs[from];
+ tmpId = playlist.positionToId[from];
+ /* move songs to one less in from->to */
+ for (i = from; i < to; i++) {
+ moveSongFromTo(i + 1, i);
+ }
+ /* move songs to one more in to->from */
+ for (i = from; i > to; i--) {
+ moveSongFromTo(i - 1, i);
+ }
+ /* put song at _to_ */
+ playlist.idToPosition[tmpId] = to;
+ playlist.positionToId[to] = tmpId;
+ playlist.songs[to] = tmpSong;
+ playlist.songMod[to] = playlist.version;
+ /* now deal with order */
+ if (playlist.random) {
+ for (i = 0; i < playlist.length; i++) {
+ if (playlist.order[i] > from && playlist.order[i] <= to) {
+ playlist.order[i]--;
+ } else if (playlist.order[i] < from &&
+ playlist.order[i] >= to) {
+ playlist.order[i]++;
+ } else if (from == playlist.order[i]) {
+ playlist.order[i] = to;
+ }
+ }
+ }
+ else
+ {
+ if (playlist.current == from)
+ playlist.current = to;
+ else if (playlist.current > from && playlist.current <= to) {
+ playlist.current--;
+ } else if (playlist.current >= to && playlist.current < from) {
+ playlist.current++;
+ }
+
+ /* this first if statement isn't necessary since the queue
+ * would have been cleared out if queued == from */
+ if (playlist.queued == from)
+ playlist.queued = to;
+ else if (playlist.queued > from && playlist.queued <= to) {
+ playlist.queued--;
+ } else if (playlist.queued>= to && playlist.queued < from) {
+ playlist.queued++;
+ }
+ }
+
+ incrPlaylistVersion();
+
+ return 0;
+}
+
+int moveSongInPlaylistById(int fd, int id1, int to)
+{
+ checkSongId(id1);
+
+ return moveSongInPlaylist(fd, playlist.idToPosition[id1], to);
+}
+
+static void orderPlaylist(void)
+{
+ int i;
+
+ if (playlist.current >= 0 && playlist.current < playlist.length) {
+ playlist.current = playlist.order[playlist.current];
+ }
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.queued >= 0) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ }
+
+ for (i = 0; i < playlist.length; i++) {
+ playlist.order[i] = i;
+ }
+
+}
+
+static void swapOrder(int a, int b)
+{
+ int bak = playlist.order[a];
+ playlist.order[a] = playlist.order[b];
+ playlist.order[b] = bak;
+}
+
+static void randomizeOrder(int start, int end)
+{
+ int i;
+ int ri;
+
+ DEBUG("playlist: randomize from %i to %i\n", start, end);
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.queued >= start && playlist.queued <= end) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ }
+
+ for (i = start; i <= end; i++) {
+ ri = random() % (end - start + 1) + start;
+ if (ri == playlist.current)
+ playlist.current = i;
+ else if (i == playlist.current)
+ playlist.current = ri;
+ swapOrder(i, ri);
+ }
+
+}
+
+int setPlaylistRandomStatus(int fd, int status)
+{
+ int statusWas = playlist.random;
+
+ if (status != 0 && status != 1) {
+ commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status);
+ return -1;
+ }
+
+ playlist.random = status;
+
+ if (status != statusWas) {
+ if (playlist.random) {
+ /*if(playlist_state==PLAYLIST_STATE_PLAY) {
+ randomizeOrder(playlist.current+1,
+ playlist.length-1);
+ }
+ else */ randomizeOrder(0, playlist.length - 1);
+ if (playlist.current >= 0 &&
+ playlist.current < playlist.length) {
+ swapOrder(playlist.current, 0);
+ playlist.current = 0;
+ }
+ } else
+ orderPlaylist();
+ }
+
+ return 0;
+}
+
+int previousSongInPlaylist(int fd)
+{
+ static time_t lastTime;
+ time_t diff = time(NULL) - lastTime;
+
+ lastTime += diff;
+
+ if (playlist_state != PLAYLIST_STATE_PLAY)
+ return 0;
+
+ syncPlaylistWithQueue(0);
+
+ if (diff && getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
+ return playPlaylistOrderNumber(fd, playlist.current);
+ } else {
+ if (playlist.current > 0) {
+ return playPlaylistOrderNumber(fd,
+ playlist.current - 1);
+ } else if (playlist.repeat) {
+ return playPlaylistOrderNumber(fd, playlist.length - 1);
+ } else {
+ return playPlaylistOrderNumber(fd, playlist.current);
+ }
+ }
+
+ return 0;
+}
+
+int shufflePlaylist(int fd)
+{
+ int i;
+ int ri;
+
+ if (playlist.length > 1) {
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ /* put current playing song first */
+ swapSongs(0, playlist.order[playlist.current]);
+ if (playlist.random) {
+ int j;
+ for (j = 0; 0 != playlist.order[j]; j++) ;
+ playlist.current = j;
+ } else
+ playlist.current = 0;
+ i = 1;
+ } else {
+ i = 0;
+ playlist.current = -1;
+ }
+ /* shuffle the rest of the list */
+ for (; i < playlist.length; i++) {
+ ri = random() % (playlist.length - 1) + 1;
+ swapSongs(i, ri);
+ }
+
+ incrPlaylistVersion();
+ }
+
+ return 0;
+}
+
+int deletePlaylist(int fd, char *utf8file)
+{
+ char *file = utf8ToFsCharset(utf8file);
+ char *rfile = xmalloc(strlen(file) + strlen(".") +
+ strlen(PLAYLIST_FILE_SUFFIX) + 1);
+ char *actualFile;
+
+ strcpy(rfile, file);
+ strcat(rfile, ".");
+ strcat(rfile, PLAYLIST_FILE_SUFFIX);
+
+ if ((actualFile = rpp2app(rfile)) && isPlaylist(actualFile))
+ free(rfile);
+ else {
+ free(rfile);
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "playlist \"%s\" not found", utf8file);
+ return -1;
+ }
+
+ if (unlink(actualFile) < 0) {
+ commandError(fd, ACK_ERROR_SYSTEM,
+ "problems deleting file");
+ return -1;
+ }
+
+ return 0;
+}
+
+int savePlaylist(int fd, char *utf8file)
+{
+ StoredPlaylist *sp = newStoredPlaylist(utf8file, fd, 0);
+ if (!sp)
+ return -1;
+
+ appendPlaylistToStoredPlaylist(sp, &playlist);
+ if (writeStoredPlaylist(sp) != 0) {
+ freeStoredPlaylist(sp);
+ return -1;
+ }
+
+ freeStoredPlaylist(sp);
+ return 0;
+}
+
+int getPlaylistCurrentSong(void)
+{
+ if (playlist.current >= 0 && playlist.current < playlist.length) {
+ return playlist.order[playlist.current];
+ }
+
+ return -1;
+}
+
+unsigned long getPlaylistVersion(void)
+{
+ return playlist.version;
+}
+
+int getPlaylistLength(void)
+{
+ return playlist.length;
+}
+
+int seekSongInPlaylist(int fd, int song, float time)
+{
+ int i = song;
+
+ if (song < 0 || song >= playlist.length) {
+ commandError(fd, ACK_ERROR_NO_EXIST,
+ "song doesn't exist: \"%i\"", song);
+ return -1;
+ }
+
+ if (playlist.random)
+ for (i = 0; song != playlist.order[i]; i++) ;
+
+ clearPlayerError();
+ playlist_stopOnError = 1;
+ playlist_errorCount = 0;
+
+ if (playlist_state == PLAYLIST_STATE_PLAY) {
+ if (playlist.queued >= 0) {
+ lockPlaylistInteraction();
+ clearPlayerQueue();
+ unlockPlaylistInteraction();
+ }
+ } else if (playPlaylistOrderNumber(fd, i) < 0)
+ return -1;
+
+ if (playlist.current != i) {
+ if (playPlaylistOrderNumber(fd, i) < 0)
+ return -1;
+ }
+
+ return playerSeek(fd, playlist.songs[playlist.order[i]], time);
+}
+
+int seekSongInPlaylistById(int fd, int id, float time)
+{
+ checkSongId(id);
+
+ return seekSongInPlaylist(fd, playlist.idToPosition[id], time);
+}
+
+int getPlaylistSongId(int song)
+{
+ return playlist.positionToId[song];
+}
+
+int PlaylistInfo(int fd, char *utf8file, int detail)
+{
+ ListNode *node;
+ StoredPlaylist *sp = loadStoredPlaylist(utf8file, fd);
+ if (sp == NULL)
+ return -1;
+
+ node = sp->list->firstNode;
+ while (node != NULL) {
+ char *temp = node->data;
+ int wrote = 0;
+
+ if (detail) {
+ Song *song = getSongFromDB(temp);
+ if (song) {
+ printSongInfo(fd, song);
+ wrote = 1;
+ }
+ }
+
+ if (!wrote) {
+ fdprintf(fd, SONG_FILE "%s\n", temp);
+ }
+
+ node = node->nextNode;
+ }
+
+ freeStoredPlaylist(sp);
+ return 0;
+}
+
+int loadPlaylist(int fd, char *utf8file)
+{
+ ListNode *node;
+ StoredPlaylist *sp = loadStoredPlaylist(utf8file, fd);
+ if (sp == NULL)
+ return -1;
+
+ node = sp->list->firstNode;
+ while (node != NULL) {
+ char *temp = node->data;
+ if ((addToPlaylist(STDERR_FILENO, temp, 0)) < 0) {
+ /* for windows compatibility, convert slashes */
+ char *temp2 = xstrdup(temp);
+ char *p = temp2;
+ while (*p) {
+ if (*p == '\\')
+ *p = '/';
+ p++;
+ }
+ if ((addToPlaylist(STDERR_FILENO, temp2, 0)) < 0) {
+ commandError(fd, ACK_ERROR_PLAYLIST_LOAD,
+ "can't add file \"%s\"", temp2);
+ }
+ free(temp2);
+ }
+
+ node = node->nextNode;
+ }
+
+ freeStoredPlaylist(sp);
+ return 0;
+}
+
+void searchForSongsInPlaylist(int fd, int numItems, LocateTagItem * items)
+{
+ int i;
+ char **originalNeedles = xmalloc(numItems * sizeof(char *));
+
+ for (i = 0; i < numItems; i++) {
+ originalNeedles[i] = items[i].needle;
+ items[i].needle = strDupToUpper(originalNeedles[i]);
+ }
+
+ for (i = 0; i < playlist.length; i++) {
+ if (strstrSearchTags(playlist.songs[i], numItems, items))
+ printPlaylistSongInfo(fd, i);
+ }
+
+ for (i = 0; i < numItems; i++) {
+ free(items[i].needle);
+ items[i].needle = originalNeedles[i];
+ }
+
+ free(originalNeedles);
+}
+
+void findSongsInPlaylist(int fd, int numItems, LocateTagItem * items)
+{
+ int i;
+
+ for (i = 0; i < playlist.length; i++) {
+ if (tagItemsFoundAndMatches(playlist.songs[i], numItems, items))
+ printPlaylistSongInfo(fd, i);
+ }
+}