From a6173e0eae8404a0a21cc28cc6a878aa1c9104fe Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 25 Oct 2021 08:43:42 +0200 Subject: [PATCH] command/playlist: add position parameter to "playlistadd" Closes https://github.com/MusicPlayerDaemon/MPD/issues/1106 --- NEWS | 2 +- doc/protocol.rst | 8 ++++-- src/PlaylistFile.cxx | 23 ++++++++++++++++ src/PlaylistFile.hxx | 7 +++++ src/command/AllCommands.cxx | 2 +- src/command/PlaylistCommands.cxx | 46 ++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 410b41abe..c5ebc9136 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ ver 0.23.3 (not yet released) * protocol - - add optional position parameter to "add" + - add optional position parameter to "add" and "playlistadd" * output - alsa: add option "stop_dsd_silence" to work around DSD DAC noise * macOS: fix libfmt related build failure diff --git a/doc/protocol.rst b/doc/protocol.rst index a4c73fd0a..896b38b31 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -695,7 +695,7 @@ Whenever possible, ids should be used. can also be a single file. The position parameter is the same as in :ref:`addid - `. + `. [#since_0_23_3]_ Clients that are connected via local socket may add arbitrary local files (URI is an absolute path). Example:: @@ -933,12 +933,15 @@ remote playlists (absolute URI with a supported scheme). .. _command_playlistadd: -:command:`playlistadd {NAME} {URI}` +:command:`playlistadd {NAME} {URI} [POSITION]` Adds ``URI`` to the playlist `NAME.m3u`. `NAME.m3u` will be created if it does not exist. + The ``POSITION`` parameter specifies where the songs will be + inserted into the playlist. [#since_0_23_3]_ + .. _command_playlistclear: :command:`playlistclear {NAME}` @@ -1649,3 +1652,4 @@ client-to-client messages are local to the current partition. .. [#since_0_22_4] Since :program:`MPD` 0.22.4 .. [#since_0_23] Since :program:`MPD` 0.23 .. [#since_0_23_1] Since :program:`MPD` 0.23.1 +.. [#since_0_23_3] Since :program:`MPD` 0.23.3 diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx index 1b72d0ad8..38a9da87e 100644 --- a/src/PlaylistFile.cxx +++ b/src/PlaylistFile.cxx @@ -268,6 +268,29 @@ PlaylistFileEditor::PlaylistFileEditor(const char *name_utf8, { } +void +PlaylistFileEditor::Insert(std::size_t i, const char *uri) +{ + if (i > size()) + throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad position"); + + if (size() >= playlist_max_length) + throw PlaylistError(PlaylistResult::TOO_LARGE, + "Stored playlist is too large"); + + contents.emplace(std::next(contents.begin(), i), uri); +} + +void +PlaylistFileEditor::Insert(std::size_t i, const DetachedSong &song) +{ + const char *uri = playlist_saveAbsolutePaths + ? song.GetRealURI() + : song.GetURI(); + + Insert(i, uri); +} + void PlaylistFileEditor::MoveIndex(unsigned src, unsigned dest) { diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx index 2f1c1bc04..a22d274a4 100644 --- a/src/PlaylistFile.hxx +++ b/src/PlaylistFile.hxx @@ -51,6 +51,13 @@ public: */ explicit PlaylistFileEditor(const char *name_utf8, LoadMode load_mode); + auto size() const noexcept { + return contents.size(); + } + + 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 RemoveIndex(unsigned i); diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 36b9987fa..643a950c1 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -157,7 +157,7 @@ static constexpr struct command commands[] = { { "play", PERMISSION_PLAYER, 0, 1, handle_play }, { "playid", PERMISSION_PLAYER, 0, 1, handle_playid }, { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, - { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, + { "playlistadd", PERMISSION_CONTROL, 2, 3, handle_playlistadd }, { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, { "playlistfind", PERMISSION_READ, 1, -1, handle_playlistfind }, diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index 8c6c45ddf..d3398fc22 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -22,8 +22,10 @@ #include "PositionArg.hxx" #include "Request.hxx" #include "Instance.hxx" +#include "db/Interface.hxx" #include "db/Selection.hxx" #include "db/DatabasePlaylist.hxx" +#include "db/DatabaseSong.hxx" #include "PlaylistSave.hxx" #include "PlaylistFile.hxx" #include "PlaylistError.hxx" @@ -201,12 +203,56 @@ handle_playlistclear([[maybe_unused]] Client &client, return CommandResult::OK; } +static CommandResult +handle_playlistadd_position(Client &client, const char *playlist_name, + const char *uri, unsigned position, + Response &r) +{ + PlaylistFileEditor editor{ + playlist_name, + PlaylistFileEditor::LoadMode::TRY, + }; + + if (position > editor.size()) { + r.Error(ACK_ERROR_ARG, "Bad position"); + return CommandResult::ERROR; + } + + if (uri_has_scheme(uri)) { + editor.Insert(position, uri); + } else { +#ifdef ENABLE_DATABASE + const auto &db = client.GetDatabaseOrThrow(); + const auto *storage = client.GetStorage(); + const DatabaseSelection selection(uri, true, nullptr); + + db.Visit(selection, [&editor, &position, storage](const auto &song){ + editor.Insert(position, + DatabaseDetachSong(storage, song)); + ++position; + }); +#else + (void)client; + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif + } + + editor.Save(); + + return CommandResult::OK; +} + CommandResult handle_playlistadd(Client &client, Request args, [[maybe_unused]] Response &r) { const char *const playlist = args[0]; const char *const uri = args[1]; + if (args.size >= 3) + return handle_playlistadd_position(client, playlist, uri, + args.ParseUnsigned(2), r); + if (uri_has_scheme(uri)) { const SongLoader loader(client); spl_append_uri(playlist, loader, uri);