queue/Print: implement sorting

This commit is contained in:
Max Kellermann 2022-02-14 06:39:39 +01:00
parent 166ce0da5a
commit c3be961ccf
5 changed files with 119 additions and 3 deletions

2
NEWS
View File

@ -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

View File

@ -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 <filter_syntax>`).
``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 <filter_syntax>`).
Parameters have the same meaning as for :ref:`find

View File

@ -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;

View File

@ -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 <fmt/format.h>
#include <algorithm>
/**
* 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<unsigned>
CollectQueue(const Queue &queue, const QueueSelection &selection) noexcept
{
std::vector<unsigned> 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()))

View File

@ -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;