Commit ae178c77 authored by Max Kellermann's avatar Max Kellermann

DatabaseCommands: "list" allows grouping

parent 22206512
...@@ -6,6 +6,7 @@ ver 0.19 (not yet released) ...@@ -6,6 +6,7 @@ ver 0.19 (not yet released)
- "playlistadd" supports file:/// - "playlistadd" supports file:///
- "idle" with unrecognized event name fails - "idle" with unrecognized event name fails
- "list" on album artist falls back to the artist tag - "list" on album artist falls back to the artist tag
- "list" allows grouping
* database * database
- proxy: forward "idle" events - proxy: forward "idle" events
- proxy: copy "Last-Modified" from remote directories - proxy: copy "Last-Modified" from remote directories
......
...@@ -1559,6 +1559,9 @@ OK ...@@ -1559,6 +1559,9 @@ OK
<arg choice="opt"><replaceable>FILTERTYPE</replaceable></arg> <arg choice="opt"><replaceable>FILTERTYPE</replaceable></arg>
<arg choice="opt"><replaceable>FILTERWHAT</replaceable></arg> <arg choice="opt"><replaceable>FILTERWHAT</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg> <arg choice="opt"><replaceable>...</replaceable></arg>
<arg choice="opt">group</arg>
<arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
</cmdsynopsis> </cmdsynopsis>
</term> </term>
<listitem> <listitem>
...@@ -1573,6 +1576,13 @@ OK ...@@ -1573,6 +1576,13 @@ OK
linkend="command_find"><command>find</command> linkend="command_find"><command>find</command>
command</link>. command</link>.
</para> </para>
<para>
The <parameter>group</parameter> keyword may be used
(repeatedly) to group the results by one or more tags.
The following example lists all album names,
grouped by their respective (album) artist:
</para>
<programlisting>list album group albumartist</programlisting>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
#include "SongFilter.hxx" #include "SongFilter.hxx"
#include "protocol/Result.hxx" #include "protocol/Result.hxx"
#include <string.h>
CommandResult CommandResult
handle_listfiles_db(Client &client, const char *uri) handle_listfiles_db(Client &client, const char *uri)
{ {
...@@ -191,6 +193,7 @@ handle_list(Client &client, int argc, char *argv[]) ...@@ -191,6 +193,7 @@ handle_list(Client &client, int argc, char *argv[])
} }
SongFilter *filter = nullptr; SongFilter *filter = nullptr;
uint32_t group_mask = 0;
if (args.size == 1) { if (args.size == 1) {
/* for compatibility with < 0.12.0 */ /* for compatibility with < 0.12.0 */
...@@ -204,6 +207,22 @@ handle_list(Client &client, int argc, char *argv[]) ...@@ -204,6 +207,22 @@ handle_list(Client &client, int argc, char *argv[])
filter = new SongFilter((unsigned)TAG_ARTIST, args.shift()); filter = new SongFilter((unsigned)TAG_ARTIST, args.shift());
} }
while (args.size >= 2 &&
strcmp(args[args.size - 2], "group") == 0) {
const char *s = args[args.size - 1];
TagType gt = tag_name_parse_i(s);
if (gt == TAG_NUM_OF_ITEM_TYPES) {
command_error(client, ACK_ERROR_ARG,
"Unknown tag type: %s", s);
return CommandResult::ERROR;
}
group_mask |= 1u << unsigned(gt);
args.pop_back();
args.pop_back();
}
if (!args.IsEmpty()) { if (!args.IsEmpty()) {
filter = new SongFilter(); filter = new SongFilter();
if (!filter->Parse(args, false)) { if (!filter->Parse(args, false)) {
...@@ -216,7 +235,7 @@ handle_list(Client &client, int argc, char *argv[]) ...@@ -216,7 +235,7 @@ handle_list(Client &client, int argc, char *argv[])
Error error; Error error;
CommandResult ret = CommandResult ret =
listAllUniqueTags(client, tagType, filter, error) listAllUniqueTags(client, tagType, group_mask, filter, error)
? CommandResult::OK ? CommandResult::OK
: print_error(client, error); : print_error(client, error);
......
...@@ -238,14 +238,24 @@ PrintSongURIVisitor(Client &client, const LightSong &song) ...@@ -238,14 +238,24 @@ PrintSongURIVisitor(Client &client, const LightSong &song)
static bool static bool
PrintUniqueTag(Client &client, TagType tag_type, PrintUniqueTag(Client &client, TagType tag_type,
const char *value) const Tag &tag)
{ {
const char *value = tag.GetValue(tag_type);
assert(value != nullptr);
client_printf(client, "%s: %s\n", tag_item_names[tag_type], value); client_printf(client, "%s: %s\n", tag_item_names[tag_type], value);
for (unsigned i = 0, n = tag.num_items; i < n; i++) {
const TagItem &item = *tag.items[i];
if (item.type != tag_type)
client_printf(client, "%s: %s\n",
tag_item_names[item.type], item.value);
}
return true; return true;
} }
bool bool
listAllUniqueTags(Client &client, unsigned type, listAllUniqueTags(Client &client, unsigned type, uint32_t group_mask,
const SongFilter *filter, const SongFilter *filter,
Error &error) Error &error)
{ {
...@@ -267,6 +277,7 @@ listAllUniqueTags(Client &client, unsigned type, ...@@ -267,6 +277,7 @@ listAllUniqueTags(Client &client, unsigned type,
const auto f = std::bind(PrintUniqueTag, std::ref(client), const auto f = std::bind(PrintUniqueTag, std::ref(client),
(TagType)type, _1); (TagType)type, _1);
return db->VisitUniqueTags(selection, (TagType)type, return db->VisitUniqueTags(selection, (TagType)type,
group_mask,
f, error); f, error);
} }
} }
...@@ -22,6 +22,8 @@ ...@@ -22,6 +22,8 @@
#include "Compiler.h" #include "Compiler.h"
#include <stdint.h>
class SongFilter; class SongFilter;
struct DatabaseSelection; struct DatabaseSelection;
class Client; class Client;
...@@ -51,7 +53,7 @@ searchStatsForSongsIn(Client &client, const char *name, ...@@ -51,7 +53,7 @@ searchStatsForSongsIn(Client &client, const char *name,
Error &error); Error &error);
bool bool
listAllUniqueTags(Client &client, unsigned type, listAllUniqueTags(Client &client, unsigned type, uint32_t group_mask,
const SongFilter *filter, const SongFilter *filter,
Error &error); Error &error);
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "Interface.hxx" #include "Interface.hxx"
#include "LightSong.hxx" #include "LightSong.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "tag/TagBuilder.hxx"
#include <functional> #include <functional>
#include <set> #include <set>
...@@ -38,13 +39,100 @@ struct StringLess { ...@@ -38,13 +39,100 @@ struct StringLess {
typedef std::set<const char *, StringLess> StringSet; typedef std::set<const char *, StringLess> StringSet;
struct TagLess {
gcc_pure
bool operator()(const Tag &a, const Tag &b) const {
if (a.num_items != b.num_items)
return a.num_items < b.num_items;
const unsigned n = a.num_items;
for (unsigned i = 0; i < n; ++i) {
const TagItem &ai = *a.items[i];
const TagItem &bi = *b.items[i];
if (ai.type != bi.type)
return unsigned(ai.type) < unsigned(bi.type);
const int cmp = strcmp(ai.value, bi.value);
if (cmp != 0)
return cmp < 0;
}
return false;
}
};
typedef std::set<Tag, TagLess> TagSet;
/**
* Copy all tag items of the specified type.
*/
static bool
CopyTagItem(TagBuilder &dest, TagType dest_type,
const Tag &src, TagType src_type)
{
bool found = false;
const unsigned n = src.num_items;
for (unsigned i = 0; i < n; ++i) {
if (src.items[i]->type == src_type) {
dest.AddItem(dest_type, src.items[i]->value);
found = true;
}
}
return found;
}
/**
* Copy all tag items of the specified type. Fall back to "Artist" if
* there is no "AlbumArtist".
*/
static void
CopyTagItem(TagBuilder &dest, const Tag &src, TagType type)
{
if (!CopyTagItem(dest, type, src, type) &&
type == TAG_ALBUM_ARTIST)
CopyTagItem(dest, type, src, TAG_ARTIST);
}
/**
* Copy all tag items of the types in the mask.
*/
static void
CopyTagMask(TagBuilder &dest, const Tag &src, uint32_t mask)
{
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
if ((mask & (1u << i)) != 0)
CopyTagItem(dest, src, TagType(i));
}
static void
InsertUniqueTag(TagSet &set, const Tag &src, TagType type, const char *value,
uint32_t group_mask)
{
TagBuilder builder;
if (value == nullptr)
builder.AddEmptyItem(type);
else
builder.AddItem(type, value);
CopyTagMask(builder, src, group_mask);
#if defined(__clang__) || GCC_CHECK_VERSION(4,8)
set.emplace(builder.Commit());
#else
set.insert(builder.Commit());
#endif
}
static bool static bool
CheckUniqueTag(StringSet &set, const Tag &tag, TagType type) CheckUniqueTag(TagSet &set, TagType dest_type,
const Tag &tag, TagType src_type,
uint32_t group_mask)
{ {
bool found = false; bool found = false;
for (unsigned i = 0; i < tag.num_items; ++i) { for (unsigned i = 0; i < tag.num_items; ++i) {
if (tag.items[i]->type == type) { if (tag.items[i]->type == src_type) {
set.insert(tag.items[i]->value); InsertUniqueTag(set, tag, dest_type,
tag.items[i]->value,
group_mask);
found = true; found = true;
} }
} }
...@@ -53,35 +141,42 @@ CheckUniqueTag(StringSet &set, const Tag &tag, TagType type) ...@@ -53,35 +141,42 @@ CheckUniqueTag(StringSet &set, const Tag &tag, TagType type)
} }
static bool static bool
CollectTags(StringSet &set, TagType tag_type, const LightSong &song) CollectTags(TagSet &set, TagType tag_type, uint32_t group_mask,
const LightSong &song)
{ {
static_assert(sizeof(group_mask) * 8 >= TAG_NUM_OF_ITEM_TYPES,
"Mask is too small");
assert((group_mask & (1u << unsigned(tag_type))) == 0);
assert(song.tag != nullptr); assert(song.tag != nullptr);
const Tag &tag = *song.tag; const Tag &tag = *song.tag;
if (!CheckUniqueTag(set, tag, tag_type) && if (!CheckUniqueTag(set, tag_type, tag, tag_type, group_mask) &&
(tag_type != TAG_ALBUM_ARTIST || (tag_type != TAG_ALBUM_ARTIST ||
/* fall back to "Artist" if no "AlbumArtist" was found */ /* fall back to "Artist" if no "AlbumArtist" was found */
!CheckUniqueTag(set, tag, TAG_ARTIST))) !CheckUniqueTag(set, tag_type, tag, TAG_ARTIST, group_mask)))
set.insert(""); InsertUniqueTag(set, tag, tag_type, nullptr, group_mask);
return true; return true;
} }
bool bool
VisitUniqueTags(const Database &db, const DatabaseSelection &selection, VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) Error &error)
{ {
StringSet set; TagSet set;
using namespace std::placeholders; using namespace std::placeholders;
const auto f = std::bind(CollectTags, std::ref(set), tag_type, _1); const auto f = std::bind(CollectTags, std::ref(set),
tag_type, group_mask, _1);
if (!db.Visit(selection, f, error)) if (!db.Visit(selection, f, error))
return false; return false;
for (auto value : set) for (const auto &value : set)
if (!visit_string(value, error)) if (!visit_tag(value, error))
return false; return false;
return true; return true;
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#include "Visitor.hxx" #include "Visitor.hxx"
#include "tag/TagType.h" #include "tag/TagType.h"
#include <stdint.h>
class Error; class Error;
class Database; class Database;
struct DatabaseSelection; struct DatabaseSelection;
...@@ -30,8 +32,8 @@ struct DatabaseStats; ...@@ -30,8 +32,8 @@ struct DatabaseStats;
bool bool
VisitUniqueTags(const Database &db, const DatabaseSelection &selection, VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error); Error &error);
bool bool
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "Compiler.h" #include "Compiler.h"
#include <time.h> #include <time.h>
#include <stdint.h>
struct DatabasePlugin; struct DatabasePlugin;
struct DatabaseStats; struct DatabaseStats;
...@@ -106,8 +107,8 @@ public: ...@@ -106,8 +107,8 @@ public:
* Visit all unique tag values. * Visit all unique tag values.
*/ */
virtual bool VisitUniqueTags(const DatabaseSelection &selection, virtual bool VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const = 0; Error &error) const = 0;
virtual bool GetStats(const DatabaseSelection &selection, virtual bool GetStats(const DatabaseSelection &selection,
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
struct LightDirectory; struct LightDirectory;
struct LightSong; struct LightSong;
struct PlaylistInfo; struct PlaylistInfo;
struct Tag;
class Error; class Error;
typedef std::function<bool(const LightDirectory &, Error &)> VisitDirectory; typedef std::function<bool(const LightDirectory &, Error &)> VisitDirectory;
...@@ -32,6 +33,6 @@ typedef std::function<bool(const LightSong &, Error &)> VisitSong; ...@@ -32,6 +33,6 @@ typedef std::function<bool(const LightSong &, Error &)> VisitSong;
typedef std::function<bool(const PlaylistInfo &, const LightDirectory &, typedef std::function<bool(const PlaylistInfo &, const LightDirectory &,
Error &)> VisitPlaylist; Error &)> VisitPlaylist;
typedef std::function<bool(const char *, Error &)> VisitString; typedef std::function<bool(const Tag &, Error &)> VisitTag;
#endif #endif
...@@ -85,12 +85,13 @@ LazyDatabase::Visit(const DatabaseSelection &selection, ...@@ -85,12 +85,13 @@ LazyDatabase::Visit(const DatabaseSelection &selection,
bool bool
LazyDatabase::VisitUniqueTags(const DatabaseSelection &selection, LazyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const Error &error) const
{ {
return EnsureOpen(error) && return EnsureOpen(error) &&
db->VisitUniqueTags(selection, tag_type, visit_string, error); db->VisitUniqueTags(selection, tag_type, group_mask, visit_tag,
error);
} }
bool bool
......
...@@ -52,8 +52,8 @@ public: ...@@ -52,8 +52,8 @@ public:
Error &error) const override; Error &error) const override;
virtual bool VisitUniqueTags(const DatabaseSelection &selection, virtual bool VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const override; Error &error) const override;
virtual bool GetStats(const DatabaseSelection &selection, virtual bool GetStats(const DatabaseSelection &selection,
......
...@@ -112,8 +112,8 @@ public: ...@@ -112,8 +112,8 @@ public:
Error &error) const override; Error &error) const override;
virtual bool VisitUniqueTags(const DatabaseSelection &selection, virtual bool VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const override; Error &error) const override;
virtual bool GetStats(const DatabaseSelection &selection, virtual bool GetStats(const DatabaseSelection &selection,
...@@ -715,7 +715,8 @@ ProxyDatabase::Visit(const DatabaseSelection &selection, ...@@ -715,7 +715,8 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
bool bool
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type,
VisitString visit_string, gcc_unused uint32_t group_mask,
VisitTag visit_tag,
Error &error) const Error &error) const
{ {
// TODO: eliminate the const_cast // TODO: eliminate the const_cast
...@@ -734,6 +735,8 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, ...@@ -734,6 +735,8 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
if (!SendConstraints(connection, selection)) if (!SendConstraints(connection, selection))
return CheckError(connection, error); return CheckError(connection, error);
// TODO: use group_mask
if (!mpd_search_commit(connection)) if (!mpd_search_commit(connection))
return CheckError(connection, error); return CheckError(connection, error);
...@@ -742,7 +745,9 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, ...@@ -742,7 +745,9 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
struct mpd_pair *pair; struct mpd_pair *pair;
while (result && while (result &&
(pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) { (pair = mpd_recv_pair_tag(connection, tag_type2)) != nullptr) {
result = visit_string(pair->value, error); TagBuilder tag;
tag.AddItem(tag_type, pair->value);
result = visit_tag(tag.Commit(), error);
mpd_return_pair(connection, pair); mpd_return_pair(connection, pair);
} }
......
...@@ -334,11 +334,12 @@ SimpleDatabase::Visit(const DatabaseSelection &selection, ...@@ -334,11 +334,12 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
bool bool
SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const Error &error) const
{ {
return ::VisitUniqueTags(*this, selection, tag_type, visit_string, return ::VisitUniqueTags(*this, selection, tag_type, group_mask,
visit_tag,
error); error);
} }
......
...@@ -116,8 +116,8 @@ public: ...@@ -116,8 +116,8 @@ public:
Error &error) const override; Error &error) const override;
virtual bool VisitUniqueTags(const DatabaseSelection &selection, virtual bool VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const override; Error &error) const override;
virtual bool GetStats(const DatabaseSelection &selection, virtual bool GetStats(const DatabaseSelection &selection,
......
...@@ -94,8 +94,8 @@ public: ...@@ -94,8 +94,8 @@ public:
Error &error) const override; Error &error) const override;
virtual bool VisitUniqueTags(const DatabaseSelection &selection, virtual bool VisitUniqueTags(const DatabaseSelection &selection,
TagType tag_type, TagType tag_type, uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const override; Error &error) const override;
virtual bool GetStats(const DatabaseSelection &selection, virtual bool GetStats(const DatabaseSelection &selection,
...@@ -721,11 +721,13 @@ UpnpDatabase::Visit(const DatabaseSelection &selection, ...@@ -721,11 +721,13 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
bool bool
UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
TagType tag, TagType tag, gcc_unused uint32_t group_mask,
VisitString visit_string, VisitTag visit_tag,
Error &error) const Error &error) const
{ {
if (!visit_string) // TODO: use group_mask
if (!visit_tag)
return true; return true;
std::vector<ContentDirectoryService> servers; std::vector<ContentDirectoryService> servers;
...@@ -754,9 +756,12 @@ UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, ...@@ -754,9 +756,12 @@ UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
} }
} }
for (const auto& value : values) for (const auto& value : values) {
if (!visit_string(value.c_str(), error)) TagBuilder builder;
builder.AddItem(tag, value.c_str());
if (!visit_tag(builder.Commit(), error))
return false; return false;
}
return true; return true;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment