From c3be961ccfd02c334d876c35907a1d415929d9aa Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 14 Feb 2022 06:39:39 +0100 Subject: [PATCH] queue/Print: implement sorting --- NEWS | 2 +- doc/protocol.rst | 13 ++++++-- src/command/QueueCommands.cxx | 31 +++++++++++++++++ src/queue/Print.cxx | 63 +++++++++++++++++++++++++++++++++++ src/queue/Selection.hxx | 13 ++++++++ 5 files changed, 119 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index ac1849b86..ca663fa23 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,6 @@ ver 0.24 (not yet released) * protocol - - "playlistfind"/"playlistsearch" have a "window" parameter + - "playlistfind"/"playlistsearch" have "sort" and "window" parameters * player - add option "mixramp_analyzer" to scan MixRamp tags on-the-fly * tags diff --git a/doc/protocol.rst b/doc/protocol.rst index 32e9e5d07..53702a7b1 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -773,10 +773,19 @@ Whenever possible, ids should be used. .. _command_playlistfind: -:command:`playlistfind {FILTER} [window {START:END}]` +:command:`playlistfind {FILTER} [sort {TYPE}] [window {START:END}]` Search the queue for songs matching ``FILTER`` (see :ref:`Filters `). + ``sort`` sorts the result by the specified tag. The sort is + descending if the tag is prefixed with a minus ('-'). Only the + first tag value will be used, if multiple of the same type exist. + To sort by "Artist", "Album" or "AlbumArtist", you should specify + "ArtistSort", "AlbumSort" or "AlbumArtistSort" instead. These + will automatically fall back to the former if "\*Sort" doesn't + exist. "AlbumArtist" falls back to just "Artist". The type + "Last-Modified" can sort by file modification time. + ``window`` can be used to query only a portion of the real response. The parameter is two zero-based queue positions; a start index (including) and an end index (excluding). The end @@ -799,7 +808,7 @@ Whenever possible, ids should be used. .. _command_playlistsearch: -:command:`playlistsearch {FILTER} [window {START:END}]` +:command:`playlistsearch {FILTER} [sort {TYPE}] [window {START:END}]` Search the queue for songs matching ``FILTER`` (see :ref:`Filters `). Parameters have the same meaning as for :ref:`find diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index 65debdc60..8fc9a1dd0 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -24,6 +24,7 @@ #include "protocol/RangeArg.hxx" #include "db/DatabaseQueue.hxx" #include "db/Selection.hxx" +#include "tag/ParseName.hxx" #include "song/Filter.hxx" #include "SongLoader.hxx" #include "song/DetachedSong.hxx" @@ -286,6 +287,19 @@ handle_playlistid(Client &client, Request args, Response &r) return CommandResult::OK; } +static TagType +ParseSortTag(const char *s) +{ + if (StringIsEqualIgnoreCase(s, "Last-Modified")) + return TagType(SORT_TAG_LAST_MODIFIED); + + TagType tag = tag_name_parse_i(s); + if (tag == TAG_NUM_OF_ITEM_TYPES) + throw ProtocolError(ACK_ERROR_ARG, "Unknown sort tag"); + + return tag; +} + static CommandResult handle_playlist_match(Client &client, Request args, Response &r, bool fold_case) @@ -298,6 +312,21 @@ handle_playlist_match(Client &client, Request args, Response &r, args.pop_back(); } + TagType sort = TAG_NUM_OF_ITEM_TYPES; + bool descending = false; + if (args.size >= 2 && StringIsEqual(args[args.size - 2], "sort")) { + const char *s = args.back(); + if (*s == '-') { + descending = true; + ++s; + } + + sort = ParseSortTag(s); + + args.pop_back(); + args.pop_back(); + } + SongFilter filter; try { filter.Parse(args, fold_case); @@ -311,6 +340,8 @@ handle_playlist_match(Client &client, Request args, Response &r, QueueSelection selection; selection.filter = &filter; selection.window = window; + selection.sort = sort; + selection.descending = descending; playlist_print_find(r, client.GetPlaylist(), selection); return CommandResult::OK; diff --git a/src/queue/Print.cxx b/src/queue/Print.cxx index 49d38c3a9..f4ecffb8e 100644 --- a/src/queue/Print.cxx +++ b/src/queue/Print.cxx @@ -24,11 +24,14 @@ #include "SongPrint.hxx" #include "song/DetachedSong.hxx" #include "song/LightSong.hxx" +#include "tag/Sort.hxx" #include "client/Response.hxx" #include "PlaylistError.hxx" #include +#include + /** * Send detailed information about a range of songs in the queue to a * client. @@ -101,10 +104,70 @@ queue_print_changes_position(Response &r, const Queue &queue, i, queue.PositionToId(i)); } +[[gnu::pure]] +static std::vector +CollectQueue(const Queue &queue, const QueueSelection &selection) noexcept +{ + std::vector v; + + for (unsigned i = 0; i < queue.GetLength(); i++) + if (selection.MatchPosition(queue, i)) + v.emplace_back(i); + + return v; +} + +static void +PrintSortedQueue(Response &r, const Queue &queue, + const QueueSelection &selection) +{ + /* collect all matching songs */ + auto v = CollectQueue(queue, selection); + + auto window = selection.window; + if (!window.CheckClip(v.size())) + throw PlaylistError::BadRange(); + + /* sort them */ + const auto sort = selection.sort; + const auto descending = selection.descending; + + if (sort == TagType(SORT_TAG_LAST_MODIFIED)) + std::stable_sort(v.begin(), v.end(), + [&queue, descending](unsigned a_pos, unsigned b_pos){ + if (descending) + std::swap(a_pos, b_pos); + + const auto &a = queue.Get(a_pos); + const auto &b = queue.Get(b_pos); + + return a.GetLastModified() < b.GetLastModified(); + }); + else + std::stable_sort(v.begin(), v.end(), + [&queue, sort, descending](unsigned a_pos, + unsigned b_pos){ + const auto &a = queue.Get(a_pos); + const auto &b = queue.Get(b_pos); + + return CompareTags(sort, descending, + a.GetTag(), + b.GetTag()); + }); + + for (unsigned i = window.start; i < window.end; ++i) + queue_print_song_info(r, queue, v[i]); +} + void PrintQueue(Response &r, const Queue &queue, const QueueSelection &selection) { + if (selection.sort != TAG_NUM_OF_ITEM_TYPES) { + PrintSortedQueue(r, queue, selection); + return; + } + auto window = selection.window; if (!window.CheckClip(queue.GetLength())) diff --git a/src/queue/Selection.hxx b/src/queue/Selection.hxx index 67ba57eb8..f6b8ed74f 100644 --- a/src/queue/Selection.hxx +++ b/src/queue/Selection.hxx @@ -20,6 +20,7 @@ #pragma once #include "protocol/RangeArg.hxx" +#include "tag/Type.h" struct Queue; class SongFilter; @@ -36,6 +37,18 @@ struct QueueSelection { RangeArg window = RangeArg::All(); + /** + * Sort the result by the given tag. #TAG_NUM_OF_ITEM_TYPES + * means don't sort. #SORT_TAG_LAST_MODIFIED sorts by + * "Last-Modified" (not technically a tag). + */ + TagType sort = TAG_NUM_OF_ITEM_TYPES; + + /** + * If #sort is set, this flag can reverse the sort order. + */ + bool descending = false; + [[gnu::pure]] bool MatchPosition(const Queue &queue, unsigned position) const noexcept;