Skip to content

Commit 3850716

Browse files
committed
command/Database: add "sort" parameter to "find" and "search"
Implement the second part of https://bugs.musicpd.org/view.php?id=3990
1 parent 1e0a60e commit 3850716

File tree

7 files changed

+178
-9
lines changed

7 files changed

+178
-9
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
ver 0.21 (not yet released)
22
* protocol
33
- "tagtypes" can be used to hide tags
4+
- "find" and "search" can sort
45

56
ver 0.20.5 (not yet released)
67
* tags

doc/protocol.xml

+14
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,7 @@ OK
15901590
<arg choice="req"><replaceable>TYPE</replaceable></arg>
15911591
<arg choice="req"><replaceable>WHAT</replaceable></arg>
15921592
<arg choice="opt"><replaceable>...</replaceable></arg>
1593+
<arg choice="opt">sort <replaceable>TYPE</replaceable></arg>
15931594
<arg choice="opt">window <replaceable>START</replaceable>:<replaceable>END</replaceable></arg>
15941595
</cmdsynopsis>
15951596
</term>
@@ -1636,6 +1637,18 @@ OK
16361637
<varname>WHAT</varname> is what to find.
16371638
</para>
16381639

1640+
<para>
1641+
<varname>sort</varname> sorts the result by the
1642+
specified tag. Without <varname>sort</varname>, the
1643+
order is undefined. Only the first tag value will be
1644+
used, if multiple of the same type exist. To sort by
1645+
"Artist", "Album" or "AlbumArtist", you should specify
1646+
"ArtistSort", "AlbumSort" or "AlbumArtistSort" instead.
1647+
These will automatically fall back to the former if
1648+
"*Sort" doesn't exist. "AlbumArtist" falls back to just
1649+
"Artist".
1650+
</para>
1651+
16391652
<para>
16401653
<varname>window</varname> can be used to query only a
16411654
portion of the real response. The parameter is two
@@ -1833,6 +1846,7 @@ OK
18331846
<arg choice="req"><replaceable>TYPE</replaceable></arg>
18341847
<arg choice="req"><replaceable>WHAT</replaceable></arg>
18351848
<arg choice="opt"><replaceable>...</replaceable></arg>
1849+
<arg choice="opt">sort <replaceable>TYPE</replaceable></arg>
18361850
<arg choice="opt">window <replaceable>START</replaceable>:<replaceable>END</replaceable></arg>
18371851
</cmdsynopsis>
18381852
</term>

src/command/DatabaseCommands.cxx

+11
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ handle_match(Client &client, Request args, Response &r, bool fold_case)
6767
} else
6868
window.SetAll();
6969

70+
TagType sort = TAG_NUM_OF_ITEM_TYPES;
71+
if (args.size >= 2 && StringIsEqual(args[args.size - 2], "sort")) {
72+
sort = tag_name_parse_i(args.back());
73+
if (sort == TAG_NUM_OF_ITEM_TYPES)
74+
throw ProtocolError(ACK_ERROR_ARG, "Unknown sort tag");
75+
76+
args.pop_back();
77+
args.pop_back();
78+
}
79+
7080
SongFilter filter;
7181
if (!filter.Parse(args, fold_case)) {
7282
r.Error(ACK_ERROR_ARG, "incorrect arguments");
@@ -77,6 +87,7 @@ handle_match(Client &client, Request args, Response &r, bool fold_case)
7787

7888
db_selection_print(r, client.partition,
7989
selection, true, false,
90+
sort,
8091
window.start, window.end);
8192
return CommandResult::OK;
8293
}

src/db/DatabasePrint.cxx

+74-9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "Selection.hxx"
2323
#include "SongFilter.hxx"
2424
#include "SongPrint.hxx"
25+
#include "DetachedSong.hxx"
2526
#include "TimePrint.hxx"
2627
#include "TagPrint.hxx"
2728
#include "client/Response.hxx"
@@ -139,10 +140,36 @@ PrintPlaylistFull(Response &r, bool base,
139140
time_print(r, "Last-Modified", playlist.mtime);
140141
}
141142

143+
static bool
144+
CompareNumeric(const char *a, const char *b)
145+
{
146+
long a_value = strtol(a, nullptr, 10);
147+
long b_value = strtol(b, nullptr, 10);
148+
149+
return a_value < b_value;
150+
}
151+
152+
static bool
153+
CompareTags(TagType type, const Tag &a, const Tag &b)
154+
{
155+
const char *a_value = a.GetSortValue(type);
156+
const char *b_value = b.GetSortValue(type);
157+
158+
switch (type) {
159+
case TAG_DISC:
160+
case TAG_TRACK:
161+
return CompareNumeric(a_value, b_value);
162+
163+
default:
164+
return strcmp(a_value, b_value) < 0;
165+
}
166+
}
167+
142168
void
143169
db_selection_print(Response &r, Partition &partition,
144170
const DatabaseSelection &selection,
145171
bool full, bool base,
172+
TagType sort,
146173
unsigned window_start, unsigned window_end)
147174
{
148175
const Database &db = partition.GetDatabaseOrThrow();
@@ -161,16 +188,53 @@ db_selection_print(Response &r, Partition &partition,
161188
std::ref(r), base, _1, _2)
162189
: VisitPlaylist();
163190

164-
if (window_start > 0 ||
165-
window_end < (unsigned)std::numeric_limits<int>::max())
166-
s = [s, window_start, window_end, &i](const LightSong &song){
167-
const bool in_window = i >= window_start && i < window_end;
168-
++i;
169-
if (in_window)
170-
s(song);
171-
};
191+
if (sort == TAG_NUM_OF_ITEM_TYPES) {
192+
if (window_start > 0 ||
193+
window_end < (unsigned)std::numeric_limits<int>::max())
194+
s = [s, window_start, window_end, &i](const LightSong &song){
195+
const bool in_window = i >= window_start && i < window_end;
196+
++i;
197+
if (in_window)
198+
s(song);
199+
};
172200

173-
db.Visit(selection, d, s, p);
201+
db.Visit(selection, d, s, p);
202+
} else {
203+
// TODO: allow the database plugin to sort internally
204+
205+
/* the client has asked us to sort the result; this is
206+
pretty expensive, because instead of streaming the
207+
result to the client, we need to copy it all into
208+
this std::vector, and then sort it */
209+
std::vector<DetachedSong> songs;
210+
211+
{
212+
auto collect_songs = [&songs](const LightSong &song){
213+
songs.emplace_back(song);
214+
};
215+
216+
db.Visit(selection, d, collect_songs, p);
217+
}
218+
219+
std::stable_sort(songs.begin(), songs.end(),
220+
[sort](const DetachedSong &a, const DetachedSong &b){
221+
return CompareTags(sort, a.GetTag(),
222+
b.GetTag());
223+
});
224+
225+
if (window_end < songs.size())
226+
songs.erase(std::next(songs.begin(), window_end),
227+
songs.end());
228+
229+
if (window_start >= songs.size())
230+
return;
231+
232+
songs.erase(songs.begin(),
233+
std::next(songs.begin(), window_start));
234+
235+
for (const auto &song : songs)
236+
s((LightSong)song);
237+
}
174238
}
175239

176240
void
@@ -179,6 +243,7 @@ db_selection_print(Response &r, Partition &partition,
179243
bool full, bool base)
180244
{
181245
db_selection_print(r, partition, selection, full, base,
246+
TAG_NUM_OF_ITEM_TYPES,
182247
0, std::numeric_limits<int>::max());
183248
}
184249

src/db/DatabasePrint.hxx

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
#ifndef MPD_DB_PRINT_H
2121
#define MPD_DB_PRINT_H
2222

23+
#include <stdint.h>
24+
25+
enum TagType : uint8_t;
2326
class TagMask;
2427
class SongFilter;
2528
struct DatabaseSelection;
@@ -39,6 +42,7 @@ void
3942
db_selection_print(Response &r, Partition &partition,
4043
const DatabaseSelection &selection,
4144
bool full, bool base,
45+
TagType sort,
4246
unsigned window_start, unsigned window_end);
4347

4448
void

src/tag/Tag.cxx

+65
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,68 @@ Tag::HasType(TagType type) const
9696
{
9797
return GetValue(type) != nullptr;
9898
}
99+
100+
static TagType
101+
DecaySort(TagType type)
102+
{
103+
switch (type) {
104+
case TAG_ARTIST_SORT:
105+
return TAG_ARTIST;
106+
107+
case TAG_ALBUM_SORT:
108+
return TAG_ALBUM;
109+
110+
case TAG_ALBUM_ARTIST_SORT:
111+
return TAG_ALBUM_ARTIST;
112+
113+
default:
114+
return TAG_NUM_OF_ITEM_TYPES;
115+
}
116+
}
117+
118+
static TagType
119+
Fallback(TagType type)
120+
{
121+
switch (type) {
122+
case TAG_ALBUM_ARTIST:
123+
return TAG_ARTIST;
124+
125+
case TAG_MUSICBRAINZ_ALBUMARTISTID:
126+
return TAG_MUSICBRAINZ_ARTISTID;
127+
128+
default:
129+
return TAG_NUM_OF_ITEM_TYPES;
130+
}
131+
}
132+
133+
const char *
134+
Tag::GetSortValue(TagType type) const
135+
{
136+
const char *value = GetValue(type);
137+
if (value != nullptr)
138+
return value;
139+
140+
/* try without *_SORT */
141+
const auto no_sort_type = DecaySort(type);
142+
if (no_sort_type != TAG_NUM_OF_ITEM_TYPES) {
143+
value = GetValue(no_sort_type);
144+
if (value != nullptr)
145+
return value;
146+
}
147+
148+
/* fall back from TAG_ALBUM_ARTIST to TAG_ALBUM */
149+
150+
type = Fallback(type);
151+
if (type != TAG_NUM_OF_ITEM_TYPES)
152+
return GetSortValue(type);
153+
154+
if (no_sort_type != TAG_NUM_OF_ITEM_TYPES) {
155+
type = Fallback(no_sort_type);
156+
if (type != TAG_NUM_OF_ITEM_TYPES)
157+
return GetSortValue(type);
158+
}
159+
160+
/* finally fall back to empty string */
161+
162+
return "";
163+
}

src/tag/Tag.hxx

+9
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ struct Tag {
142142
gcc_pure
143143
bool HasType(TagType type) const;
144144

145+
/**
146+
* Returns a value for sorting on the specified type, with
147+
* automatic fallbacks to the next best tag type
148+
* (e.g. #TAG_ALBUM_ARTIST falls back to #TAG_ARTIST). If
149+
* there is no such value, returns an empty string.
150+
*/
151+
gcc_pure
152+
const char *GetSortValue(TagType type) const;
153+
145154
class const_iterator {
146155
friend struct Tag;
147156
const TagItem *const*cursor;

0 commit comments

Comments
 (0)