mpd/src/playlist_edit.c
Avuton Olrich 0aee49bdf8 all: Update copyright header.
This updates the copyright header to all be the same, which is
pretty much an update of where to mail request for a copy of the GPL
and the years of the MPD project. This also puts all committers under
'The Music Player Project' umbrella. These entries should go
individually in the AUTHORS file, for consistancy.
2009-03-13 11:51:55 -07:00

401 lines
9.8 KiB
C

/*
* Copyright (C) 2003-2009 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*
* Functions for editing the playlist (adding, removing, reordering
* songs in the queue).
*
*/
#include "playlist_internal.h"
#include "player_control.h"
#include "database.h"
#include "uri.h"
#include "song.h"
#include "idle.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
static void incrPlaylistVersion(struct playlist *playlist)
{
queue_increment_version(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
void clearPlaylist(struct playlist *playlist)
{
stopPlaylist(playlist);
/* make sure there are no references to allocated songs
anymore */
for (unsigned i = 0; i < queue_length(&playlist->queue); i++) {
const struct song *song = queue_get(&playlist->queue, i);
if (!song_in_database(song))
pc_song_deleted(song);
}
queue_clear(&playlist->queue);
playlist->current = -1;
incrPlaylistVersion(playlist);
}
#ifndef WIN32
enum playlist_result
playlist_append_file(struct playlist *playlist, const char *path, int uid,
unsigned *added_id)
{
int ret;
struct stat st;
struct song *song;
if (uid <= 0)
/* unauthenticated client */
return PLAYLIST_RESULT_DENIED;
ret = stat(path, &st);
if (ret < 0)
return PLAYLIST_RESULT_ERRNO;
if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444)
/* client is not owner */
return PLAYLIST_RESULT_DENIED;
song = song_file_load(path, NULL);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return addSongToPlaylist(playlist, song, added_id);
}
#endif
static struct song *
song_by_url(const char *url)
{
struct song *song;
song = db_get_song(url);
if (song != NULL)
return song;
if (uri_has_scheme(url))
return song_remote_new(url);
return NULL;
}
enum playlist_result
addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id)
{
struct song *song;
g_debug("add to playlist: %s", url);
song = song_by_url(url);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return addSongToPlaylist(playlist, song, added_id);
}
enum playlist_result
addSongToPlaylist(struct playlist *playlist,
struct song *song, unsigned *added_id)
{
const struct song *queued;
unsigned id;
if (queue_is_full(&playlist->queue))
return PLAYLIST_RESULT_TOO_LARGE;
queued = playlist_get_queued_song(playlist);
id = queue_append(&playlist->queue, song);
if (playlist->queue.random) {
/* shuffle the new song into the list of remaining
songs to play */
unsigned start;
if (playlist->queued >= 0)
start = playlist->queued + 1;
else
start = playlist->current + 1;
if (start < queue_length(&playlist->queue))
queue_shuffle_order_last(&playlist->queue, start,
queue_length(&playlist->queue));
}
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
if (added_id)
*added_id = id;
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
{
const struct song *queued;
if (!queue_valid_position(&playlist->queue, song1) ||
!queue_valid_position(&playlist->queue, song2))
return PLAYLIST_RESULT_BAD_RANGE;
queued = playlist_get_queued_song(playlist);
queue_swap(&playlist->queue, song1, song2);
if (playlist->queue.random) {
/* update the queue order, so that playlist->current
still points to the current song order */
queue_swap_order(&playlist->queue,
queue_position_to_order(&playlist->queue,
song1),
queue_position_to_order(&playlist->queue,
song2));
} else {
/* correct the "current" song order */
if (playlist->current == (int)song1)
playlist->current = song2;
else if (playlist->current == (int)song2)
playlist->current = song1;
}
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
{
int song1 = queue_id_to_position(&playlist->queue, id1);
int song2 = queue_id_to_position(&playlist->queue, id2);
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return swapSongsInPlaylist(playlist, song1, song2);
}
enum playlist_result
deleteFromPlaylist(struct playlist *playlist, unsigned song)
{
const struct song *queued;
unsigned songOrder;
if (song >= queue_length(&playlist->queue))
return PLAYLIST_RESULT_BAD_RANGE;
queued = playlist_get_queued_song(playlist);
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
bool paused = getPlayerState() == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
playerWait();
playlist->playing = false;
/* see which song is going to be played instead */
playlist->current = queue_next_order(&playlist->queue,
playlist->current);
if (playlist->current == (int)songOrder)
playlist->current = -1;
if (playlist->current >= 0 && !paused)
/* play the song after the deleted one */
playPlaylistOrderNumber(playlist, playlist->current);
else
/* no songs left to play, stop playback
completely */
stopPlaylist(playlist);
queued = NULL;
} else if (playlist->current == (int)songOrder)
/* there's a "current song" but we're not playing
currently - clear "current" */
playlist->current = -1;
/* now do it: remove the song */
if (!song_in_database(queue_get(&playlist->queue, song)))
pc_song_deleted(queue_get(&playlist->queue, song));
queue_delete(&playlist->queue, song);
incrPlaylistVersion(playlist);
/* update the "current" and "queued" variables */
if (playlist->current > (int)songOrder) {
playlist->current--;
}
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
deleteFromPlaylistById(struct playlist *playlist, unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return deleteFromPlaylist(playlist, song);
}
void
deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
{
for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
if (song == queue_get(&playlist->queue, i))
deleteFromPlaylist(playlist, i);
pc_song_deleted(song);
}
enum playlist_result
moveSongInPlaylist(struct playlist *playlist, unsigned from, int to)
{
const struct song *queued;
int currentSong;
if (!queue_valid_position(&playlist->queue, from))
return PLAYLIST_RESULT_BAD_RANGE;
if ((to >= 0 && to >= (int)queue_length(&playlist->queue)) ||
(to < 0 && abs(to) > (int)queue_length(&playlist->queue)))
return PLAYLIST_RESULT_BAD_RANGE;
if ((int)from == to) /* no-op */
return PLAYLIST_RESULT_SUCCESS;
queued = playlist_get_queued_song(playlist);
/*
* (to < 0) => move to offset from current song
* (-playlist.length == to) => move to position BEFORE current song
*/
currentSong = playlist->current >= 0
? (int)queue_order_to_position(&playlist->queue,
playlist->current)
: -1;
if (to < 0 && playlist->current >= 0) {
if ((unsigned)currentSong == from)
/* no-op, can't be moved to offset of itself */
return PLAYLIST_RESULT_SUCCESS;
to = (currentSong + abs(to)) % queue_length(&playlist->queue);
}
queue_move(&playlist->queue, from, to);
if (!playlist->queue.random) {
/* update current/queued */
if (playlist->current == (int)from)
playlist->current = to;
else if (playlist->current > (int)from &&
playlist->current <= to) {
playlist->current--;
} else if (playlist->current >= to &&
playlist->current < (int)from) {
playlist->current++;
}
}
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
{
int song = queue_id_to_position(&playlist->queue, id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return moveSongInPlaylist(playlist, song, to);
}
void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
{
const struct song *queued;
if (end > queue_length(&playlist->queue))
/* correct the "end" offset */
end = queue_length(&playlist->queue);
if ((start+1) >= end)
/* needs at least two entries. */
return;
queued = playlist_get_queued_song(playlist);
if (playlist->playing && playlist->current >= 0) {
unsigned current_position;
current_position = queue_order_to_position(&playlist->queue,
playlist->current);
if (current_position >= start && current_position < end)
{
/* put current playing song first */
queue_swap(&playlist->queue, start, current_position);
if (playlist->queue.random) {
playlist->current =
queue_position_to_order(&playlist->queue, start);
} else
playlist->current = start;
/* start shuffle after the current song */
start++;
}
} else {
/* no playback currently: reset playlist->current */
playlist->current = -1;
}
queue_shuffle_range(&playlist->queue, start, end);
incrPlaylistVersion(playlist);
playlist_update_queued_song(playlist, queued);
}