Files
mpd/src/playlist_edit.c
Maarten Sebregts 3a9697adf2 Playlist: fix bug in moving after current song
Moving songs using either 'move' or 'moveid' to position -1 (after the
current song) would fail for a song which is just before the current
song.
This patch corrects the check to see if the current song is in the range
to be moved. Since the range is from `start` up to `end` (exclusive) the
check was incorrect, but is now fixed.
2011-12-21 10:29:07 +01:00

445 lines
11 KiB
C

/*
* Copyright (C) 2003-2010 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 "config.h"
#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 playlist_increment_version(struct playlist *playlist)
{
queue_increment_version(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
void playlist_clear(struct playlist *playlist)
{
playlist_stop(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;
playlist_increment_version(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 playlist_append_song(playlist, song, added_id);
}
#endif
enum playlist_result
playlist_append_song(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));
}
playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
if (added_id)
*added_id = id;
return PLAYLIST_RESULT_SUCCESS;
}
static struct song *
song_by_uri(const char *uri)
{
struct song *song;
song = db_get_song(uri);
if (song != NULL)
return song;
if (uri_has_scheme(uri))
return song_remote_new(uri);
return NULL;
}
enum playlist_result
playlist_append_uri(struct playlist *playlist, const char *uri,
unsigned *added_id)
{
struct song *song;
g_debug("add to playlist: %s", uri);
song = song_by_uri(uri);
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return playlist_append_song(playlist, song, added_id);
}
enum playlist_result
playlist_swap_songs(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;
}
playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
playlist_swap_songs_id(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 playlist_swap_songs(playlist, song1, song2);
}
static void
playlist_delete_internal(struct playlist *playlist, unsigned song,
const struct song **queued_p)
{
unsigned songOrder;
assert(song < queue_length(&playlist->queue));
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
bool paused = pc_get_state() == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
pc_stop();
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 */
playlist_play_order(playlist, playlist->current);
else
/* no songs left to play, stop playback
completely */
playlist_stop(playlist);
*queued_p = 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);
/* update the "current" and "queued" variables */
if (playlist->current > (int)songOrder) {
playlist->current--;
}
}
enum playlist_result
playlist_delete(struct playlist *playlist, unsigned song)
{
const struct song *queued;
if (song >= queue_length(&playlist->queue))
return PLAYLIST_RESULT_BAD_RANGE;
queued = playlist_get_queued_song(playlist);
playlist_delete_internal(playlist, song, &queued);
playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end)
{
const struct song *queued;
if (start >= queue_length(&playlist->queue))
return PLAYLIST_RESULT_BAD_RANGE;
if (end > queue_length(&playlist->queue))
end = queue_length(&playlist->queue);
if (start >= end)
return PLAYLIST_RESULT_SUCCESS;
queued = playlist_get_queued_song(playlist);
do {
playlist_delete_internal(playlist, --end, &queued);
} while (end != start);
playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
playlist_delete_id(struct playlist *playlist, unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
return playlist_delete(playlist, song);
}
void
playlist_delete_song(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))
playlist_delete(playlist, i);
pc_song_deleted(song);
}
enum playlist_result
playlist_move_range(struct playlist *playlist,
unsigned start, unsigned end, int to)
{
const struct song *queued;
int currentSong;
if (!queue_valid_position(&playlist->queue, start) ||
!queue_valid_position(&playlist->queue, end - 1))
return PLAYLIST_RESULT_BAD_RANGE;
if ((to >= 0 && to + end - start - 1 >= queue_length(&playlist->queue)) ||
(to < 0 && abs(to) > (int)queue_length(&playlist->queue)))
return PLAYLIST_RESULT_BAD_RANGE;
if ((int)start == to)
/* nothing happens */
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 (start <= (unsigned)currentSong && (unsigned)currentSong < end)
/* no-op, can't be moved to offset of itself */
return PLAYLIST_RESULT_SUCCESS;
to = (currentSong + abs(to)) % queue_length(&playlist->queue);
if (start < (unsigned)to)
to--;
}
queue_move_range(&playlist->queue, start, end, to);
if (!playlist->queue.random) {
/* update current/queued */
if ((int)start <= playlist->current &&
(unsigned)playlist->current < end)
playlist->current += to - start;
else if (playlist->current >= (int)end &&
playlist->current <= to) {
playlist->current -= end - start;
} else if (playlist->current >= to &&
playlist->current < (int)start) {
playlist->current += end - start;
}
}
playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
playlist_move_id(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 playlist_move_range(playlist, song, song+1, to);
}
void
playlist_shuffle(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);
playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
}