diff options
author | Max Kellermann <max@duempel.org> | 2014-02-27 17:12:42 +0100 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2014-02-27 17:12:42 +0100 |
commit | 809b89b5af5eaf7abc3240d786cda15f354b6624 (patch) | |
tree | c1affa37a38f45470cf68dbcae18310abaac7b38 /src/queue | |
parent | 681e012fb542ee1bb2ea5312dc673987a7a8ee29 (diff) |
Playlist*: move to queue/
Diffstat (limited to 'src/queue')
-rw-r--r-- | src/queue/Playlist.cxx | 333 | ||||
-rw-r--r-- | src/queue/Playlist.hxx | 264 | ||||
-rw-r--r-- | src/queue/PlaylistControl.cxx | 260 | ||||
-rw-r--r-- | src/queue/PlaylistEdit.cxx | 410 | ||||
-rw-r--r-- | src/queue/PlaylistState.cxx | 245 | ||||
-rw-r--r-- | src/queue/PlaylistState.hxx | 54 | ||||
-rw-r--r-- | src/queue/PlaylistTag.cxx | 93 | ||||
-rw-r--r-- | src/queue/PlaylistUpdate.cxx | 73 |
8 files changed, 1732 insertions, 0 deletions
diff --git a/src/queue/Playlist.cxx b/src/queue/Playlist.cxx new file mode 100644 index 000000000..abcb2ceaa --- /dev/null +++ b/src/queue/Playlist.cxx @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "DetachedSong.hxx" +#include "Idle.hxx" +#include "Log.hxx" + +#include <assert.h> + +void +playlist::TagModified(DetachedSong &&song) +{ + if (!playing) + return; + + assert(current >= 0); + + DetachedSong ¤t_song = queue.GetOrder(current); + if (song.IsSame(current_song)) + current_song.MoveTagFrom(std::move(song)); + + queue.ModifyAtOrder(current); + queue.IncrementVersion(); + idle_add(IDLE_PLAYLIST); +} + +/** + * Queue a song, addressed by its order number. + */ +static void +playlist_queue_song_order(playlist &playlist, PlayerControl &pc, + unsigned order) +{ + assert(playlist.queue.IsValidOrder(order)); + + playlist.queued = order; + + const DetachedSong &song = playlist.queue.GetOrder(order); + + FormatDebug(playlist_domain, "queue song %i:\"%s\"", + playlist.queued, song.GetURI()); + + pc.EnqueueSong(new DetachedSong(song)); +} + +/** + * Called if the player thread has started playing the "queued" song. + */ +static void +playlist_song_started(playlist &playlist, PlayerControl &pc) +{ + assert(pc.next_song == nullptr); + assert(playlist.queued >= -1); + + /* queued song has started: copy queued to current, + and notify the clients */ + + int current = playlist.current; + playlist.current = playlist.queued; + playlist.queued = -1; + + if(playlist.queue.consume) + playlist.DeleteOrder(pc, current); + + idle_add(IDLE_PLAYER); +} + +const DetachedSong * +playlist::GetQueuedSong() const +{ + return playing && queued >= 0 + ? &queue.GetOrder(queued) + : nullptr; +} + +void +playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert((queued < 0) == (prev == nullptr)); + + const int next_order = current >= 0 + ? queue.GetNextOrder(current) + : 0; + + if (next_order == 0 && queue.random && !queue.single) { + /* shuffle the song order again, so we get a different + order each time the playlist is played + completely */ + const unsigned current_position = + queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that the current still points to + the current song, after the song order has been + shuffled */ + current = queue.PositionToOrder(current_position); + } + + const DetachedSong *const next_song = next_order >= 0 + ? &queue.GetOrder(next_order) + : nullptr; + + if (prev != nullptr && next_song != prev) { + /* clear the currently queued song */ + pc.Cancel(); + queued = -1; + } + + if (next_order >= 0) { + if (next_song != prev) + playlist_queue_song_order(*this, pc, next_order); + else + queued = next_order; + } +} + +void +playlist::PlayOrder(PlayerControl &pc, int order) +{ + playing = true; + queued = -1; + + const DetachedSong &song = queue.GetOrder(order); + + FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI()); + + pc.Play(new DetachedSong(song)); + current = order; +} + +static void +playlist_resume_playback(playlist &playlist, PlayerControl &pc); + +void +playlist::SyncWithPlayer(PlayerControl &pc) +{ + if (!playing) + /* this event has reached us out of sync: we aren't + playing anymore; ignore the event */ + return; + + pc.Lock(); + const PlayerState pc_state = pc.GetState(); + const DetachedSong *pc_next_song = pc.next_song; + pc.Unlock(); + + if (pc_state == PlayerState::STOP) + /* the player thread has stopped: check if playback + should be restarted with the next song. That can + happen if the playlist isn't filling the queue fast + enough */ + playlist_resume_playback(*this, pc); + else { + /* check if the player thread has already started + playing the queued song */ + if (pc_next_song == nullptr && queued != -1) + playlist_song_started(*this, pc); + + pc.Lock(); + pc_next_song = pc.next_song; + pc.Unlock(); + + /* make sure the queued song is always set (if + possible) */ + if (pc_next_song == nullptr && queued < 0) + UpdateQueuedSong(pc, nullptr); + } +} + +/** + * The player has stopped for some reason. Check the error, and + * decide whether to re-start playback + */ +static void +playlist_resume_playback(playlist &playlist, PlayerControl &pc) +{ + assert(playlist.playing); + assert(pc.GetState() == PlayerState::STOP); + + const auto error = pc.GetErrorType(); + if (error == PlayerError::NONE) + playlist.error_count = 0; + else + ++playlist.error_count; + + if ((playlist.stop_on_error && error != PlayerError::NONE) || + error == PlayerError::OUTPUT || + playlist.error_count >= playlist.queue.GetLength()) + /* too many errors, or critical error: stop + playback */ + playlist.Stop(pc); + else + /* continue playback at the next song */ + playlist.PlayNext(pc); +} + +void +playlist::SetRepeat(PlayerControl &pc, bool status) +{ + if (status == queue.repeat) + return; + + queue.repeat = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when repeat mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +static void +playlist_order(playlist &playlist) +{ + if (playlist.current >= 0) + /* update playlist.current, order==position now */ + playlist.current = playlist.queue.OrderToPosition(playlist.current); + + playlist.queue.RestoreOrder(); +} + +void +playlist::SetSingle(PlayerControl &pc, bool status) +{ + if (status == queue.single) + return; + + queue.single = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when single mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetConsume(bool status) +{ + if (status == queue.consume) + return; + + queue.consume = status; + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetRandom(PlayerControl &pc, bool status) +{ + if (status == queue.random) + return; + + const DetachedSong *const queued_song = GetQueuedSong(); + + queue.random = status; + + if (queue.random) { + /* shuffle the queue order, but preserve current */ + + const int current_position = GetCurrentPosition(); + + queue.ShuffleOrder(); + + if (current_position >= 0) { + /* make sure the current song is the first in + the order list, so the whole rest of the + playlist is played after that */ + unsigned current_order = + queue.PositionToOrder(current_position); + queue.SwapOrders(0, current_order); + current = 0; + } else + current = -1; + } else + playlist_order(*this); + + UpdateQueuedSong(pc, queued_song); + + idle_add(IDLE_OPTIONS); +} + +int +playlist::GetCurrentPosition() const +{ + return current >= 0 + ? queue.OrderToPosition(current) + : -1; +} + +int +playlist::GetNextPosition() const +{ + if (current < 0) + return -1; + + if (queue.single && queue.repeat) + return queue.OrderToPosition(current); + else if (queue.IsValidOrder(current + 1)) + return queue.OrderToPosition(current + 1); + else if (queue.repeat) + return queue.OrderToPosition(0); + + return -1; +} diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx new file mode 100644 index 000000000..09980155e --- /dev/null +++ b/src/queue/Playlist.hxx @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +#ifndef MPD_PLAYLIST_HXX +#define MPD_PLAYLIST_HXX + +#include "queue/Queue.hxx" +#include "PlaylistError.hxx" + +enum TagType : uint8_t; +struct PlayerControl; +class DetachedSong; +class Database; +class Error; +class SongLoader; + +struct playlist { + /** + * The song queue - it contains the "real" playlist. + */ + struct Queue queue; + + /** + * This value is true if the player is currently playing (or + * should be playing). + */ + bool playing; + + /** + * If true, then any error is fatal; if false, MPD will + * attempt to play the next song on non-fatal errors. During + * seeking, this flag is set. + */ + bool stop_on_error; + + /** + * Number of errors since playback was started. If this + * number exceeds the length of the playlist, MPD gives up, + * because all songs have been tried. + */ + unsigned error_count; + + /** + * The "current song pointer". This is the song which is + * played when we get the "play" command. It is also the song + * which is currently being played. + */ + int current; + + /** + * The "next" song to be played, when the current one + * finishes. The decoder thread may start decoding and + * buffering it, while the "current" song is still playing. + * + * This variable is only valid if #playing is true. + */ + int queued; + + playlist(unsigned max_length) + :queue(max_length), playing(false), current(-1), queued(-1) { + } + + ~playlist() { + } + + uint32_t GetVersion() const { + return queue.version; + } + + unsigned GetLength() const { + return queue.GetLength(); + } + + unsigned PositionToId(unsigned position) const { + return queue.PositionToId(position); + } + + gcc_pure + int GetCurrentPosition() const; + + gcc_pure + int GetNextPosition() const; + + /** + * Returns the song object which is currently queued. Returns + * none if there is none (yet?) or if MPD isn't playing. + */ + gcc_pure + const DetachedSong *GetQueuedSong() const; + + /** + * This is the "PLAYLIST" event handler. It is invoked by the + * player thread whenever it requests a new queued song, or + * when it exits. + */ + void SyncWithPlayer(PlayerControl &pc); + +protected: + /** + * Called by all editing methods after a modification. + * Updates the queue version and emits #IDLE_PLAYLIST. + */ + void OnModified(); + + /** + * Updates the "queued song". Calculates the next song + * according to the current one (if MPD isn't playing, it + * takes the first song), and queues this song. Clears the + * old queued song if there was one. + * + * @param prev the song which was previously queued, as + * determined by playlist_get_queued_song() + */ + void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev); + +public: + void Clear(PlayerControl &pc); + + /** + * A tag in the play queue has been modified by the player + * thread. Apply the given song's tag to the current song if + * the song matches. + */ + void TagModified(DetachedSong &&song); + +#ifdef ENABLE_DATABASE + /** + * The database has been modified. Pull all updates. + */ + void DatabaseModified(const Database &db); +#endif + + PlaylistResult AppendSong(PlayerControl &pc, + DetachedSong &&song, + unsigned *added_id=nullptr); + + PlaylistResult AppendURI(PlayerControl &pc, + const SongLoader &loader, + const char *uri_utf8, + unsigned *added_id=nullptr); + +protected: + void DeleteInternal(PlayerControl &pc, + unsigned song, const DetachedSong **queued_p); + +public: + PlaylistResult DeletePosition(PlayerControl &pc, + unsigned position); + + PlaylistResult DeleteOrder(PlayerControl &pc, + unsigned order) { + return DeletePosition(pc, queue.OrderToPosition(order)); + } + + PlaylistResult DeleteId(PlayerControl &pc, unsigned id); + + /** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ + PlaylistResult DeleteRange(PlayerControl &pc, + unsigned start, unsigned end); + + void DeleteSong(PlayerControl &pc, const char *uri); + + void Shuffle(PlayerControl &pc, unsigned start, unsigned end); + + PlaylistResult MoveRange(PlayerControl &pc, + unsigned start, unsigned end, int to); + + PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to); + + PlaylistResult SwapPositions(PlayerControl &pc, + unsigned song1, unsigned song2); + + PlaylistResult SwapIds(PlayerControl &pc, + unsigned id1, unsigned id2); + + PlaylistResult SetPriorityRange(PlayerControl &pc, + unsigned start_position, + unsigned end_position, + uint8_t priority); + + PlaylistResult SetPriorityId(PlayerControl &pc, + unsigned song_id, uint8_t priority); + + bool AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error); + bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error); + + void Stop(PlayerControl &pc); + + PlaylistResult PlayPosition(PlayerControl &pc, int position); + + void PlayOrder(PlayerControl &pc, int order); + + PlaylistResult PlayId(PlayerControl &pc, int id); + + void PlayNext(PlayerControl &pc); + + void PlayPrevious(PlayerControl &pc); + + PlaylistResult SeekSongPosition(PlayerControl &pc, + unsigned song_position, + float seek_time); + + PlaylistResult SeekSongId(PlayerControl &pc, + unsigned song_id, float seek_time); + + /** + * Seek within the current song. Fails if MPD is not currently + * playing. + * + * @param time the time in seconds + * @param relative if true, then the specified time is relative to the + * current position + */ + PlaylistResult SeekCurrent(PlayerControl &pc, + float seek_time, bool relative); + + bool GetRepeat() const { + return queue.repeat; + } + + void SetRepeat(PlayerControl &pc, bool new_value); + + bool GetRandom() const { + return queue.random; + } + + void SetRandom(PlayerControl &pc, bool new_value); + + bool GetSingle() const { + return queue.single; + } + + void SetSingle(PlayerControl &pc, bool new_value); + + bool GetConsume() const { + return queue.consume; + } + + void SetConsume(bool new_value); +}; + +#endif diff --git a/src/queue/PlaylistControl.cxx b/src/queue/PlaylistControl.cxx new file mode 100644 index 000000000..9d75cc26d --- /dev/null +++ b/src/queue/PlaylistControl.cxx @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2003-2014 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 controlling playback on the playlist level. + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "DetachedSong.hxx" +#include "Log.hxx" + +void +playlist::Stop(PlayerControl &pc) +{ + if (!playing) + return; + + assert(current >= 0); + + FormatDebug(playlist_domain, "stop"); + pc.Stop(); + queued = -1; + playing = false; + + if (queue.random) { + /* shuffle the playlist, so the next playback will + result in a new random order */ + + unsigned current_position = queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that "current" stays valid, and the next + "play" command plays the same song again */ + current = queue.PositionToOrder(current_position); + } +} + +PlaylistResult +playlist::PlayPosition(PlayerControl &pc, int song) +{ + pc.ClearError(); + + unsigned i = song; + if (song == -1) { + /* play any song ("current" song, or the first song */ + + if (queue.IsEmpty()) + return PlaylistResult::SUCCESS; + + if (playing) { + /* already playing: unpause playback, just in + case it was paused, and return */ + pc.SetPause(false); + return PlaylistResult::SUCCESS; + } + + /* select a song: "current" song, or the first one */ + i = current >= 0 + ? current + : 0; + } else if (!queue.IsValidPosition(song)) + return PlaylistResult::BAD_RANGE; + + if (queue.random) { + if (song >= 0) + /* "i" is currently the song position (which + would be equal to the order number in + no-random mode); convert it to a order + number, because random mode is enabled */ + i = queue.PositionToOrder(song); + + if (!playing) + current = 0; + + /* swap the new song with the previous "current" one, + so playback continues as planned */ + queue.SwapOrders(i, current); + i = current; + } + + stop_on_error = false; + error_count = 0; + + PlayOrder(pc, i); + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::PlayId(PlayerControl &pc, int id) +{ + if (id == -1) + return PlayPosition(pc, id); + + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return PlayPosition(pc, song); +} + +void +playlist::PlayNext(PlayerControl &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert(queue.IsValidOrder(current)); + + const int old_current = current; + stop_on_error = false; + + /* determine the next song from the queue's order list */ + + const int next_order = queue.GetNextOrder(current); + if (next_order < 0) { + /* no song after this one: stop playback */ + Stop(pc); + + /* reset "current song" */ + current = -1; + } + else + { + if (next_order == 0 && queue.random) { + /* The queue told us that the next song is the first + song. This means we are in repeat mode. Shuffle + the queue order, so this time, the user hears the + songs in a different than before */ + assert(queue.repeat); + + queue.ShuffleOrder(); + + /* note that current and queued are + now invalid, but playlist_play_order() will + discard them anyway */ + } + + PlayOrder(pc, next_order); + } + + /* Consume mode removes each played songs. */ + if (queue.consume) + DeleteOrder(pc, old_current); +} + +void +playlist::PlayPrevious(PlayerControl &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + + int order; + if (current > 0) { + /* play the preceding song */ + order = current - 1; + } else if (queue.repeat) { + /* play the last song in "repeat" mode */ + order = queue.GetLength() - 1; + } else { + /* re-start playing the current song if it's + the first one */ + order = current; + } + + PlayOrder(pc, order); +} + +PlaylistResult +playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time) +{ + if (!queue.IsValidPosition(song)) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *queued_song = GetQueuedSong(); + + unsigned i = queue.random + ? queue.PositionToOrder(song) + : song; + + pc.ClearError(); + stop_on_error = true; + error_count = 0; + + if (!playing || (unsigned)current != i) { + /* seeking is not within the current song - prepare + song change */ + + playing = true; + current = i; + + queued_song = nullptr; + } + + if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) { + UpdateQueuedSong(pc, queued_song); + + return PlaylistResult::NOT_PLAYING; + } + + queued = -1; + UpdateQueuedSong(pc, nullptr); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SeekSongPosition(pc, song, seek_time); +} + +PlaylistResult +playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative) +{ + if (!playing) + return PlaylistResult::NOT_PLAYING; + + if (relative) { + const auto status = pc.GetStatus(); + + if (status.state != PlayerState::PLAY && + status.state != PlayerState::PAUSE) + return PlaylistResult::NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return SeekSongPosition(pc, current, seek_time); +} diff --git a/src/queue/PlaylistEdit.cxx b/src/queue/PlaylistEdit.cxx new file mode 100644 index 000000000..8d2c76e6e --- /dev/null +++ b/src/queue/PlaylistEdit.cxx @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2003-2014 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.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DetachedSong.hxx" +#include "SongLoader.hxx" +#include "Idle.hxx" +#include "Log.hxx" + +#include <stdlib.h> + +void +playlist::OnModified() +{ + queue.IncrementVersion(); + + idle_add(IDLE_PLAYLIST); +} + +void +playlist::Clear(PlayerControl &pc) +{ + Stop(pc); + + queue.Clear(); + current = -1; + + OnModified(); +} + +PlaylistResult +playlist::AppendSong(PlayerControl &pc, + DetachedSong &&song, unsigned *added_id) +{ + unsigned id; + + if (queue.IsFull()) + return PlaylistResult::TOO_LARGE; + + const DetachedSong *const queued_song = GetQueuedSong(); + + id = queue.Append(std::move(song), 0); + + if (queue.random) { + /* shuffle the new song into the list of remaning + songs to play */ + + unsigned start; + if (queued >= 0) + start = queued + 1; + else + start = current + 1; + if (start < queue.GetLength()) + queue.ShuffleOrderLast(start, queue.GetLength()); + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + if (added_id) + *added_id = id; + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::AppendURI(PlayerControl &pc, + const SongLoader &loader, + const char *uri, unsigned *added_id) +{ + FormatDebug(playlist_domain, "add to playlist: %s", uri); + + Error error; + DetachedSong *song = loader.LoadSong(uri, error); + if (song == nullptr) { + // TODO: return the Error + LogError(error); + return error.IsDomain(playlist_domain) + ? PlaylistResult(error.GetCode()) + : PlaylistResult::NO_SUCH_SONG; + } + + PlaylistResult result = AppendSong(pc, std::move(*song), added_id); + delete song; + + return result; +} + +PlaylistResult +playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2) +{ + if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *const queued_song = GetQueuedSong(); + + queue.SwapPositions(song1, song2); + + if (queue.random) { + /* update the queue order, so that current + still points to the current song order */ + + queue.SwapOrders(queue.PositionToOrder(song1), + queue.PositionToOrder(song2)); + } else { + /* correct the "current" song order */ + + if (current == (int)song1) + current = song2; + else if (current == (int)song2) + current = song1; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2) +{ + int song1 = queue.IdToPosition(id1); + int song2 = queue.IdToPosition(id2); + + if (song1 < 0 || song2 < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SwapPositions(pc, song1, song2); +} + +PlaylistResult +playlist::SetPriorityRange(PlayerControl &pc, + unsigned start, unsigned end, + uint8_t priority) +{ + if (start >= GetLength()) + return PlaylistResult::BAD_RANGE; + + if (end > GetLength()) + end = GetLength(); + + if (start >= end) + return PlaylistResult::SUCCESS; + + /* remember "current" and "queued" */ + + const int current_position = GetCurrentPosition(); + const DetachedSong *const queued_song = GetQueuedSong(); + + /* apply the priority changes */ + + queue.SetPriorityRange(start, end, priority, current); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + current = queue.PositionToOrder(current_position); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SetPriorityId(PlayerControl &pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue.IdToPosition(song_id); + if (song_position < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SetPriorityRange(pc, song_position, song_position + 1, + priority); + +} + +void +playlist::DeleteInternal(PlayerControl &pc, + unsigned song, const DetachedSong **queued_p) +{ + assert(song < GetLength()); + + unsigned songOrder = queue.PositionToOrder(song); + + if (playing && current == (int)songOrder) { + const bool paused = pc.GetState() == PlayerState::PAUSE; + + /* the current song is going to be deleted: stop the player */ + + pc.Stop(); + playing = false; + + /* see which song is going to be played instead */ + + current = queue.GetNextOrder(current); + if (current == (int)songOrder) + current = -1; + + if (current >= 0 && !paused) + /* play the song after the deleted one */ + PlayOrder(pc, current); + else + /* no songs left to play, stop playback + completely */ + Stop(pc); + + *queued_p = nullptr; + } else if (current == (int)songOrder) + /* there's a "current song" but we're not playing + currently - clear "current" */ + current = -1; + + /* now do it: remove the song */ + + queue.DeletePosition(song); + + /* update the "current" and "queued" variables */ + + if (current > (int)songOrder) + current--; +} + +PlaylistResult +playlist::DeletePosition(PlayerControl &pc, unsigned song) +{ + if (song >= queue.GetLength()) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *queued_song = GetQueuedSong(); + + DeleteInternal(pc, song, &queued_song); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end) +{ + if (start >= queue.GetLength()) + return PlaylistResult::BAD_RANGE; + + if (end > queue.GetLength()) + end = queue.GetLength(); + + if (start >= end) + return PlaylistResult::SUCCESS; + + const DetachedSong *queued_song = GetQueuedSong(); + + do { + DeleteInternal(pc, --end, &queued_song); + } while (end != start); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::DeleteId(PlayerControl &pc, unsigned id) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return DeletePosition(pc, song); +} + +void +playlist::DeleteSong(PlayerControl &pc, const char *uri) +{ + for (int i = queue.GetLength() - 1; i >= 0; --i) + if (queue.Get(i).IsURI(uri)) + DeletePosition(pc, i); +} + +PlaylistResult +playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to) +{ + if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) + return PlaylistResult::BAD_RANGE; + + if ((to >= 0 && to + end - start - 1 >= GetLength()) || + (to < 0 && unsigned(abs(to)) > GetLength())) + return PlaylistResult::BAD_RANGE; + + if ((int)start == to) + /* nothing happens */ + return PlaylistResult::SUCCESS; + + const DetachedSong *const queued_song = GetQueuedSong(); + + /* + * (to < 0) => move to offset from current song + * (-playlist.length == to) => move to position BEFORE current song + */ + const int currentSong = GetCurrentPosition(); + if (to < 0) { + if (currentSong < 0) + /* can't move relative to current song, + because there is no current song */ + return PlaylistResult::BAD_RANGE; + + if (start <= (unsigned)currentSong && (unsigned)currentSong < end) + /* no-op, can't be moved to offset of itself */ + return PlaylistResult::SUCCESS; + to = (currentSong + abs(to)) % GetLength(); + if (start < (unsigned)to) + to--; + } + + queue.MoveRange(start, end, to); + + if (!queue.random) { + /* update current/queued */ + if ((int)start <= current && (unsigned)current < end) + current += to - start; + else if (current >= (int)end && current <= to) + current -= end - start; + else if (current >= to && current < (int)start) + current += end - start; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::MoveId(PlayerControl &pc, unsigned id1, int to) +{ + int song = queue.IdToPosition(id1); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return MoveRange(pc, song, song + 1, to); +} + +void +playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end) +{ + if (end > GetLength()) + /* correct the "end" offset */ + end = GetLength(); + + if (start + 1 >= end) + /* needs at least two entries. */ + return; + + const DetachedSong *const queued_song = GetQueuedSong(); + if (playing && current >= 0) { + unsigned current_position = queue.OrderToPosition(current); + + if (current_position >= start && current_position < end) { + /* put current playing song first */ + queue.SwapPositions(start, current_position); + + if (queue.random) { + current = queue.PositionToOrder(start); + } else + current = start; + + /* start shuffle after the current song */ + start++; + } + } else { + /* no playback currently: reset current */ + + current = -1; + } + + queue.ShuffleRange(start, end); + + UpdateQueuedSong(pc, queued_song); + OnModified(); +} diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx new file mode 100644 index 000000000..f5c798e3e --- /dev/null +++ b/src/queue/PlaylistState.cxx @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#include "config.h" +#include "PlaylistState.hxx" +#include "PlaylistError.hxx" +#include "Playlist.hxx" +#include "queue/QueueSave.hxx" +#include "fs/TextFile.hxx" +#include "PlayerControl.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "fs/Limits.hxx" +#include "util/CharUtil.hxx" +#include "util/StringUtil.hxx" +#include "Log.hxx" + +#include <string.h> +#include <stdlib.h> + +#define PLAYLIST_STATE_FILE_STATE "state: " +#define PLAYLIST_STATE_FILE_RANDOM "random: " +#define PLAYLIST_STATE_FILE_REPEAT "repeat: " +#define PLAYLIST_STATE_FILE_SINGLE "single: " +#define PLAYLIST_STATE_FILE_CONSUME "consume: " +#define PLAYLIST_STATE_FILE_CURRENT "current: " +#define PLAYLIST_STATE_FILE_TIME "time: " +#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " +#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " +#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*MPD_PATH_MAX + +void +playlist_state_save(FILE *fp, const struct playlist &playlist, + PlayerControl &pc) +{ + const auto player_status = pc.GetStatus(); + + fputs(PLAYLIST_STATE_FILE_STATE, fp); + + if (playlist.playing) { + switch (player_status.state) { + case PlayerState::PAUSE: + fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); + break; + default: + fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); + } + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist.queue.OrderToPosition(playlist.current)); + fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", + (int)player_status.elapsed_time); + } else { + fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); + + if (playlist.current >= 0) + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist.queue.OrderToPosition(playlist.current)); + } + + fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random); + fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat); + fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single); + fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", + playlist.queue.consume); + fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)pc.GetCrossFade()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", + pc.GetMixRampDb()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc.GetMixRampDelay()); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); + queue_save(fp, playlist.queue); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); +} + +static void +playlist_state_load(TextFile &file, const SongLoader &song_loader, + struct playlist &playlist) +{ + const char *line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, "No playlist in state file"); + return; + } + + while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(file, song_loader, line, playlist.queue); + + line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, + "'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); + break; + } + } + + playlist.queue.IncrementVersion(); +} + +bool +playlist_state_restore(const char *line, TextFile &file, + const SongLoader &song_loader, + struct playlist &playlist, PlayerControl &pc) +{ + int current = -1; + int seek_time = 0; + bool random_mode = false; + + if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE)) + return false; + + line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; + + PlayerState state; + if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) + state = PlayerState::PLAY; + else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) + state = PlayerState::PAUSE; + else + state = PlayerState::STOP; + + while ((line = file.ReadLine()) != nullptr) { + if (StringStartsWith(line, PLAYLIST_STATE_FILE_TIME)) { + seek_time = + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_REPEAT)) { + playlist.SetRepeat(pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_SINGLE)) { + playlist.SetSingle(pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CONSUME)) { + playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY); + + /* this check discards "nan" which was used + prior to MPD 0.18 */ + if (IsDigitASCII(*p)) + pc.SetMixRampDelay(atof(p)); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_RANDOM)) { + random_mode = + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), + "1") == 0; + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) { + current = atoi(&(line + [strlen + (PLAYLIST_STATE_FILE_CURRENT)])); + } else if (StringStartsWith(line, + PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { + playlist_state_load(file, song_loader, playlist); + } + } + + playlist.SetRandom(pc, random_mode); + + if (!playlist.queue.IsEmpty()) { + if (!playlist.queue.IsValidPosition(current)) + current = 0; + + if (state == PlayerState::PLAY && + config_get_bool(CONF_RESTORE_PAUSED, false)) + /* the user doesn't want MPD to auto-start + playback after startup; fall back to + "pause" */ + state = PlayerState::PAUSE; + + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PlayerState::STOP) + pc.UpdateAudio(); + + if (state == PlayerState::STOP /* && config_option */) + playlist.current = current; + else if (seek_time == 0) + playlist.PlayPosition(pc, current); + else + playlist.SeekSongPosition(pc, current, seek_time); + + if (state == PlayerState::PAUSE) + pc.Pause(); + } + + return true; +} + +unsigned +playlist_state_get_hash(const playlist &playlist, + PlayerControl &pc) +{ + const auto player_status = pc.GetStatus(); + + return playlist.queue.version ^ + (player_status.state != PlayerState::STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist.current >= 0 + ? (playlist.queue.OrderToPosition(playlist.current) << 16) + : 0) ^ + ((int)pc.GetCrossFade() << 20) ^ + (unsigned(player_status.state) << 24) ^ + (playlist.queue.random << 27) ^ + (playlist.queue.repeat << 28) ^ + (playlist.queue.single << 29) ^ + (playlist.queue.consume << 30) ^ + (playlist.queue.random << 31); +} diff --git a/src/queue/PlaylistState.hxx b/src/queue/PlaylistState.hxx new file mode 100644 index 000000000..8d3f88ae2 --- /dev/null +++ b/src/queue/PlaylistState.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#ifndef MPD_PLAYLIST_STATE_HXX +#define MPD_PLAYLIST_STATE_HXX + +#include <stdio.h> + +struct playlist; +struct PlayerControl; +class TextFile; +class SongLoader; + +void +playlist_state_save(FILE *fp, const playlist &playlist, + PlayerControl &pc); + +bool +playlist_state_restore(const char *line, TextFile &file, + const SongLoader &song_loader, + playlist &playlist, PlayerControl &pc); + +/** + * Generates a hash number for the current state of the playlist and + * the playback options. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +playlist_state_get_hash(const playlist &playlist, + PlayerControl &c); + +#endif diff --git a/src/queue/PlaylistTag.cxx b/src/queue/PlaylistTag.cxx new file mode 100644 index 000000000..556e7f4e9 --- /dev/null +++ b/src/queue/PlaylistTag.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2014 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.hxx" +#include "PlaylistError.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" + +bool +playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + tag.AddItem(tag_type, value); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} + +bool +playlist::ClearSongIdTag(unsigned id, TagType tag_type, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) + tag.RemoveAll(); + else + tag.RemoveType(tag_type); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} diff --git a/src/queue/PlaylistUpdate.cxx b/src/queue/PlaylistUpdate.cxx new file mode 100644 index 000000000..8876711ef --- /dev/null +++ b/src/queue/PlaylistUpdate.cxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +#include "config.h" +#include "Playlist.hxx" +#include "db/Interface.hxx" +#include "db/LightSong.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "Idle.hxx" +#include "util/Error.hxx" + +static bool +UpdatePlaylistSong(const Database &db, DetachedSong &song) +{ + if (!song.IsInDatabase() || !song.IsFile()) + /* only update Songs instances that are "detached" + from the Database */ + return false; + + const LightSong *original = db.GetSong(song.GetURI(), IgnoreError()); + if (original == nullptr) + /* not found - shouldn't happen, because the update + thread should ensure that all stale Song instances + have been purged */ + return false; + + if (original->mtime == song.GetLastModified()) { + /* not modified */ + db.ReturnSong(original); + return false; + } + + song.SetLastModified(original->mtime); + song.SetTag(*original->tag); + + db.ReturnSong(original); + return true; +} + +void +playlist::DatabaseModified(const Database &db) +{ + bool modified = false; + + for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) { + if (UpdatePlaylistSong(db, queue.Get(i))) { + queue.ModifyAtPosition(i); + modified = true; + } + } + + if (modified) { + queue.IncrementVersion(); + idle_add(IDLE_PLAYLIST); + } +} |