command/Database: add "sort" parameter to "find" and "search"
Implement the second part of https://bugs.musicpd.org/view.php?id=3990
This commit is contained in:
parent
1e0a60e73d
commit
3850716522
1
NEWS
1
NEWS
|
@ -1,6 +1,7 @@
|
|||
ver 0.21 (not yet released)
|
||||
* protocol
|
||||
- "tagtypes" can be used to hide tags
|
||||
- "find" and "search" can sort
|
||||
|
||||
ver 0.20.5 (not yet released)
|
||||
* tags
|
||||
|
|
|
@ -1590,6 +1590,7 @@ OK
|
|||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||
<arg choice="opt">sort <replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="opt">window <replaceable>START</replaceable>:<replaceable>END</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
|
@ -1636,6 +1637,18 @@ OK
|
|||
<varname>WHAT</varname> is what to find.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<varname>sort</varname> sorts the result by the
|
||||
specified tag. Without <varname>sort</varname>, the
|
||||
order is undefined. 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".
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<varname>window</varname> can be used to query only a
|
||||
portion of the real response. The parameter is two
|
||||
|
@ -1833,6 +1846,7 @@ OK
|
|||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||
<arg choice="opt">sort <replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="opt">window <replaceable>START</replaceable>:<replaceable>END</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
|
|
|
@ -67,6 +67,16 @@ handle_match(Client &client, Request args, Response &r, bool fold_case)
|
|||
} else
|
||||
window.SetAll();
|
||||
|
||||
TagType sort = TAG_NUM_OF_ITEM_TYPES;
|
||||
if (args.size >= 2 && StringIsEqual(args[args.size - 2], "sort")) {
|
||||
sort = tag_name_parse_i(args.back());
|
||||
if (sort == TAG_NUM_OF_ITEM_TYPES)
|
||||
throw ProtocolError(ACK_ERROR_ARG, "Unknown sort tag");
|
||||
|
||||
args.pop_back();
|
||||
args.pop_back();
|
||||
}
|
||||
|
||||
SongFilter filter;
|
||||
if (!filter.Parse(args, fold_case)) {
|
||||
r.Error(ACK_ERROR_ARG, "incorrect arguments");
|
||||
|
@ -77,6 +87,7 @@ handle_match(Client &client, Request args, Response &r, bool fold_case)
|
|||
|
||||
db_selection_print(r, client.partition,
|
||||
selection, true, false,
|
||||
sort,
|
||||
window.start, window.end);
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "Selection.hxx"
|
||||
#include "SongFilter.hxx"
|
||||
#include "SongPrint.hxx"
|
||||
#include "DetachedSong.hxx"
|
||||
#include "TimePrint.hxx"
|
||||
#include "TagPrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
|
@ -139,10 +140,36 @@ PrintPlaylistFull(Response &r, bool base,
|
|||
time_print(r, "Last-Modified", playlist.mtime);
|
||||
}
|
||||
|
||||
static bool
|
||||
CompareNumeric(const char *a, const char *b)
|
||||
{
|
||||
long a_value = strtol(a, nullptr, 10);
|
||||
long b_value = strtol(b, nullptr, 10);
|
||||
|
||||
return a_value < b_value;
|
||||
}
|
||||
|
||||
static bool
|
||||
CompareTags(TagType type, const Tag &a, const Tag &b)
|
||||
{
|
||||
const char *a_value = a.GetSortValue(type);
|
||||
const char *b_value = b.GetSortValue(type);
|
||||
|
||||
switch (type) {
|
||||
case TAG_DISC:
|
||||
case TAG_TRACK:
|
||||
return CompareNumeric(a_value, b_value);
|
||||
|
||||
default:
|
||||
return strcmp(a_value, b_value) < 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
db_selection_print(Response &r, Partition &partition,
|
||||
const DatabaseSelection &selection,
|
||||
bool full, bool base,
|
||||
TagType sort,
|
||||
unsigned window_start, unsigned window_end)
|
||||
{
|
||||
const Database &db = partition.GetDatabaseOrThrow();
|
||||
|
@ -161,16 +188,53 @@ db_selection_print(Response &r, Partition &partition,
|
|||
std::ref(r), base, _1, _2)
|
||||
: VisitPlaylist();
|
||||
|
||||
if (window_start > 0 ||
|
||||
window_end < (unsigned)std::numeric_limits<int>::max())
|
||||
s = [s, window_start, window_end, &i](const LightSong &song){
|
||||
const bool in_window = i >= window_start && i < window_end;
|
||||
++i;
|
||||
if (in_window)
|
||||
s(song);
|
||||
};
|
||||
if (sort == TAG_NUM_OF_ITEM_TYPES) {
|
||||
if (window_start > 0 ||
|
||||
window_end < (unsigned)std::numeric_limits<int>::max())
|
||||
s = [s, window_start, window_end, &i](const LightSong &song){
|
||||
const bool in_window = i >= window_start && i < window_end;
|
||||
++i;
|
||||
if (in_window)
|
||||
s(song);
|
||||
};
|
||||
|
||||
db.Visit(selection, d, s, p);
|
||||
db.Visit(selection, d, s, p);
|
||||
} else {
|
||||
// TODO: allow the database plugin to sort internally
|
||||
|
||||
/* the client has asked us to sort the result; this is
|
||||
pretty expensive, because instead of streaming the
|
||||
result to the client, we need to copy it all into
|
||||
this std::vector, and then sort it */
|
||||
std::vector<DetachedSong> songs;
|
||||
|
||||
{
|
||||
auto collect_songs = [&songs](const LightSong &song){
|
||||
songs.emplace_back(song);
|
||||
};
|
||||
|
||||
db.Visit(selection, d, collect_songs, p);
|
||||
}
|
||||
|
||||
std::stable_sort(songs.begin(), songs.end(),
|
||||
[sort](const DetachedSong &a, const DetachedSong &b){
|
||||
return CompareTags(sort, a.GetTag(),
|
||||
b.GetTag());
|
||||
});
|
||||
|
||||
if (window_end < songs.size())
|
||||
songs.erase(std::next(songs.begin(), window_end),
|
||||
songs.end());
|
||||
|
||||
if (window_start >= songs.size())
|
||||
return;
|
||||
|
||||
songs.erase(songs.begin(),
|
||||
std::next(songs.begin(), window_start));
|
||||
|
||||
for (const auto &song : songs)
|
||||
s((LightSong)song);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -179,6 +243,7 @@ db_selection_print(Response &r, Partition &partition,
|
|||
bool full, bool base)
|
||||
{
|
||||
db_selection_print(r, partition, selection, full, base,
|
||||
TAG_NUM_OF_ITEM_TYPES,
|
||||
0, std::numeric_limits<int>::max());
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
#ifndef MPD_DB_PRINT_H
|
||||
#define MPD_DB_PRINT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum TagType : uint8_t;
|
||||
class TagMask;
|
||||
class SongFilter;
|
||||
struct DatabaseSelection;
|
||||
|
@ -39,6 +42,7 @@ void
|
|||
db_selection_print(Response &r, Partition &partition,
|
||||
const DatabaseSelection &selection,
|
||||
bool full, bool base,
|
||||
TagType sort,
|
||||
unsigned window_start, unsigned window_end);
|
||||
|
||||
void
|
||||
|
|
|
@ -96,3 +96,68 @@ Tag::HasType(TagType type) const
|
|||
{
|
||||
return GetValue(type) != nullptr;
|
||||
}
|
||||
|
||||
static TagType
|
||||
DecaySort(TagType type)
|
||||
{
|
||||
switch (type) {
|
||||
case TAG_ARTIST_SORT:
|
||||
return TAG_ARTIST;
|
||||
|
||||
case TAG_ALBUM_SORT:
|
||||
return TAG_ALBUM;
|
||||
|
||||
case TAG_ALBUM_ARTIST_SORT:
|
||||
return TAG_ALBUM_ARTIST;
|
||||
|
||||
default:
|
||||
return TAG_NUM_OF_ITEM_TYPES;
|
||||
}
|
||||
}
|
||||
|
||||
static TagType
|
||||
Fallback(TagType type)
|
||||
{
|
||||
switch (type) {
|
||||
case TAG_ALBUM_ARTIST:
|
||||
return TAG_ARTIST;
|
||||
|
||||
case TAG_MUSICBRAINZ_ALBUMARTISTID:
|
||||
return TAG_MUSICBRAINZ_ARTISTID;
|
||||
|
||||
default:
|
||||
return TAG_NUM_OF_ITEM_TYPES;
|
||||
}
|
||||
}
|
||||
|
||||
const char *
|
||||
Tag::GetSortValue(TagType type) const
|
||||
{
|
||||
const char *value = GetValue(type);
|
||||
if (value != nullptr)
|
||||
return value;
|
||||
|
||||
/* try without *_SORT */
|
||||
const auto no_sort_type = DecaySort(type);
|
||||
if (no_sort_type != TAG_NUM_OF_ITEM_TYPES) {
|
||||
value = GetValue(no_sort_type);
|
||||
if (value != nullptr)
|
||||
return value;
|
||||
}
|
||||
|
||||
/* fall back from TAG_ALBUM_ARTIST to TAG_ALBUM */
|
||||
|
||||
type = Fallback(type);
|
||||
if (type != TAG_NUM_OF_ITEM_TYPES)
|
||||
return GetSortValue(type);
|
||||
|
||||
if (no_sort_type != TAG_NUM_OF_ITEM_TYPES) {
|
||||
type = Fallback(no_sort_type);
|
||||
if (type != TAG_NUM_OF_ITEM_TYPES)
|
||||
return GetSortValue(type);
|
||||
}
|
||||
|
||||
/* finally fall back to empty string */
|
||||
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -142,6 +142,15 @@ struct Tag {
|
|||
gcc_pure
|
||||
bool HasType(TagType type) const;
|
||||
|
||||
/**
|
||||
* Returns a value for sorting on the specified type, with
|
||||
* automatic fallbacks to the next best tag type
|
||||
* (e.g. #TAG_ALBUM_ARTIST falls back to #TAG_ARTIST). If
|
||||
* there is no such value, returns an empty string.
|
||||
*/
|
||||
gcc_pure
|
||||
const char *GetSortValue(TagType type) const;
|
||||
|
||||
class const_iterator {
|
||||
friend struct Tag;
|
||||
const TagItem *const*cursor;
|
||||
|
|
Loading…
Reference in New Issue