diff --git a/NEWS b/NEWS index db8cc04f8..015cffde2 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ ver 0.24 (not yet released) * protocol - new command "searchcount" (case-insensitive "count") - "playlistfind"/"playlistsearch" have "sort" and "window" parameters + - allow range in "playlistmove" - "save" can append to or replace an existing playlist - filter "prio" (for "playlistfind"/"playlistsearch") - limit "player" idle events to the current partition diff --git a/doc/protocol.rst b/doc/protocol.rst index 50207b735..f41d9488f 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -994,8 +994,9 @@ remote playlists (absolute URI with a supported scheme). .. _command_playlistmove: -:command:`playlistmove {NAME} {FROM} {TO}` - Moves the song at position ``FROM`` in +:command:`playlistmove {NAME} [{FROM} | {START:END}] {TO}` + Moves the song at position ``FROM`` or range of songs + at ``START:END`` [#since_0_24]_ in the playlist `NAME.m3u` to the position ``TO``. diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx index e5dbeee3b..aa43569cc 100644 --- a/src/PlaylistFile.cxx +++ b/src/PlaylistFile.cxx @@ -296,18 +296,41 @@ PlaylistFileEditor::Insert(std::size_t i, const DetachedSong &song) Insert(i, uri); } -void -PlaylistFileEditor::MoveIndex(unsigned src, unsigned dest) +static PlaylistFileContents +CutRange(PlaylistFileContents &src, RangeArg range) noexcept { - if (src >= contents.size() || dest >= contents.size()) + PlaylistFileContents dest; + dest.reserve(range.Count()); + + const auto begin = std::next(src.begin(), range.start); + const auto end = std::next(src.begin(), range.end); + + for (auto i = begin; i != end; ++i) + dest.emplace_back(std::move(*i)); + + src.erase(begin, end); + + return dest; +} + +static void +InsertRange(PlaylistFileContents &dest, PlaylistFileContents::iterator pos, + PlaylistFileContents &&src) noexcept +{ + dest.reserve(dest.size() + src.size()); + + for (auto &i : src) + pos = std::next(dest.emplace(pos, std::move(i))); +} + +void +PlaylistFileEditor::MoveIndex(RangeArg src, unsigned dest) +{ + if (src.end > contents.size() || dest > contents.size() - src.Count()) throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range"); - const auto src_i = std::next(contents.begin(), src); - auto value = std::move(*src_i); - contents.erase(src_i); - - const auto dest_i = std::next(contents.begin(), dest); - contents.insert(dest_i, std::move(value)); + auto tmp = CutRange(contents, src); + InsertRange(contents, std::next(contents.begin(), dest), std::move(tmp)); } void diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx index 2bbf97bfa..c905d8935 100644 --- a/src/PlaylistFile.hxx +++ b/src/PlaylistFile.hxx @@ -59,7 +59,7 @@ public: void Insert(std::size_t i, const char *uri); void Insert(std::size_t i, const DetachedSong &song); - void MoveIndex(unsigned src, unsigned dest); + void MoveIndex(RangeArg src, unsigned dest); void RemoveIndex(unsigned i); void RemoveRange(RangeArg range); diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index c3654d30f..7c756e756 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -202,10 +202,16 @@ handle_playlistmove([[maybe_unused]] Client &client, Request args, [[maybe_unused]] Response &r) { const char *const name = args.front(); - unsigned from = args.ParseUnsigned(1); + + RangeArg from = args.ParseRange(1); + if (from.IsOpenEnded()) { + r.Error(ACK_ERROR_ARG, "Open-ended range not supported"); + return CommandResult::ERROR; + } + unsigned to = args.ParseUnsigned(2); - if (from == to) + if (from.IsEmpty() || from.start == to) /* this doesn't check whether the playlist exists, but what the hell.. */ return CommandResult::OK;