queue/Print: implement sorting
This commit is contained in:
parent
166ce0da5a
commit
c3be961ccf
2
NEWS
2
NEWS
@ -1,6 +1,6 @@
|
|||||||
ver 0.24 (not yet released)
|
ver 0.24 (not yet released)
|
||||||
* protocol
|
* protocol
|
||||||
- "playlistfind"/"playlistsearch" have a "window" parameter
|
- "playlistfind"/"playlistsearch" have "sort" and "window" parameters
|
||||||
* player
|
* player
|
||||||
- add option "mixramp_analyzer" to scan MixRamp tags on-the-fly
|
- add option "mixramp_analyzer" to scan MixRamp tags on-the-fly
|
||||||
* tags
|
* tags
|
||||||
|
@ -773,10 +773,19 @@ Whenever possible, ids should be used.
|
|||||||
|
|
||||||
.. _command_playlistfind:
|
.. _command_playlistfind:
|
||||||
|
|
||||||
:command:`playlistfind {FILTER} [window {START:END}]`
|
:command:`playlistfind {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||||
Search the queue for songs matching
|
Search the queue for songs matching
|
||||||
``FILTER`` (see :ref:`Filters <filter_syntax>`).
|
``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
|
``window`` can be used to query only a portion of the real
|
||||||
response. The parameter is two zero-based queue positions; a
|
response. The parameter is two zero-based queue positions; a
|
||||||
start index (including) and an end index (excluding). The end
|
start index (including) and an end index (excluding). The end
|
||||||
@ -799,7 +808,7 @@ Whenever possible, ids should be used.
|
|||||||
|
|
||||||
.. _command_playlistsearch:
|
.. _command_playlistsearch:
|
||||||
|
|
||||||
:command:`playlistsearch {FILTER} [window {START:END}]`
|
:command:`playlistsearch {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||||
Search the queue for songs matching
|
Search the queue for songs matching
|
||||||
``FILTER`` (see :ref:`Filters <filter_syntax>`).
|
``FILTER`` (see :ref:`Filters <filter_syntax>`).
|
||||||
Parameters have the same meaning as for :ref:`find
|
Parameters have the same meaning as for :ref:`find
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "protocol/RangeArg.hxx"
|
#include "protocol/RangeArg.hxx"
|
||||||
#include "db/DatabaseQueue.hxx"
|
#include "db/DatabaseQueue.hxx"
|
||||||
#include "db/Selection.hxx"
|
#include "db/Selection.hxx"
|
||||||
|
#include "tag/ParseName.hxx"
|
||||||
#include "song/Filter.hxx"
|
#include "song/Filter.hxx"
|
||||||
#include "SongLoader.hxx"
|
#include "SongLoader.hxx"
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
@ -286,6 +287,19 @@ handle_playlistid(Client &client, Request args, Response &r)
|
|||||||
return CommandResult::OK;
|
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
|
static CommandResult
|
||||||
handle_playlist_match(Client &client, Request args, Response &r,
|
handle_playlist_match(Client &client, Request args, Response &r,
|
||||||
bool fold_case)
|
bool fold_case)
|
||||||
@ -298,6 +312,21 @@ handle_playlist_match(Client &client, Request args, Response &r,
|
|||||||
args.pop_back();
|
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;
|
SongFilter filter;
|
||||||
try {
|
try {
|
||||||
filter.Parse(args, fold_case);
|
filter.Parse(args, fold_case);
|
||||||
@ -311,6 +340,8 @@ handle_playlist_match(Client &client, Request args, Response &r,
|
|||||||
QueueSelection selection;
|
QueueSelection selection;
|
||||||
selection.filter = &filter;
|
selection.filter = &filter;
|
||||||
selection.window = window;
|
selection.window = window;
|
||||||
|
selection.sort = sort;
|
||||||
|
selection.descending = descending;
|
||||||
|
|
||||||
playlist_print_find(r, client.GetPlaylist(), selection);
|
playlist_print_find(r, client.GetPlaylist(), selection);
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
|
@ -24,11 +24,14 @@
|
|||||||
#include "SongPrint.hxx"
|
#include "SongPrint.hxx"
|
||||||
#include "song/DetachedSong.hxx"
|
#include "song/DetachedSong.hxx"
|
||||||
#include "song/LightSong.hxx"
|
#include "song/LightSong.hxx"
|
||||||
|
#include "tag/Sort.hxx"
|
||||||
#include "client/Response.hxx"
|
#include "client/Response.hxx"
|
||||||
#include "PlaylistError.hxx"
|
#include "PlaylistError.hxx"
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send detailed information about a range of songs in the queue to a
|
* Send detailed information about a range of songs in the queue to a
|
||||||
* client.
|
* client.
|
||||||
@ -101,10 +104,70 @@ queue_print_changes_position(Response &r, const Queue &queue,
|
|||||||
i, queue.PositionToId(i));
|
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
|
void
|
||||||
PrintQueue(Response &r, const Queue &queue,
|
PrintQueue(Response &r, const Queue &queue,
|
||||||
const QueueSelection &selection)
|
const QueueSelection &selection)
|
||||||
{
|
{
|
||||||
|
if (selection.sort != TAG_NUM_OF_ITEM_TYPES) {
|
||||||
|
PrintSortedQueue(r, queue, selection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto window = selection.window;
|
auto window = selection.window;
|
||||||
|
|
||||||
if (!window.CheckClip(queue.GetLength()))
|
if (!window.CheckClip(queue.GetLength()))
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "protocol/RangeArg.hxx"
|
#include "protocol/RangeArg.hxx"
|
||||||
|
#include "tag/Type.h"
|
||||||
|
|
||||||
struct Queue;
|
struct Queue;
|
||||||
class SongFilter;
|
class SongFilter;
|
||||||
@ -36,6 +37,18 @@ struct QueueSelection {
|
|||||||
|
|
||||||
RangeArg window = RangeArg::All();
|
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]]
|
[[gnu::pure]]
|
||||||
bool MatchPosition(const Queue &queue,
|
bool MatchPosition(const Queue &queue,
|
||||||
unsigned position) const noexcept;
|
unsigned position) const noexcept;
|
||||||
|
Loading…
Reference in New Issue
Block a user