diff --git a/NEWS b/NEWS index 16ad3e322..7c4d36bba 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.21 (not yet released) +* protocol + - "tagtypes" can be used to hide tags ver 0.20.5 (not yet released) * tags diff --git a/doc/protocol.xml b/doc/protocol.xml index 7996abd94..8cf783f76 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -2252,6 +2252,95 @@ OK + + + + + tagtypes + + + + + Shows a list of available tag types. It is an + intersection of the metadata_to_use + setting and this client's tag mask. + + + + About the tag mask: each client can decide to disable + any number of tag types, which will be omitted from + responses to this client. That is a good idea, because + it makes responses smaller. The following + tagtypes sub commands configure this + list. + + + + + + + + tagtypes + disable + NAME + + + + + Remove one or more tags from the list of tag types the + client is interested in. These will be omitted from + responses to this client. + + + + + + + + tagtypes + enable + NAME + + + + + Re-enable one or more tags from the list of tag types + for this client. These will no longer be hidden from + responses to this client. + + + + + + + + tagtypes + clear + + + + + Clear the list of tag types this client is interested + in. This means that MPD will + not send any tags to this client. + + + + + + + + tagtypes + all + + + + + Announce that this client is interested in all tag + types. This is the default setting for new clients. + + + @@ -2410,18 +2499,6 @@ OK - - - - tagtypes - - - - - Shows a list of available song metadata. - - - diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx index 1406f669b..61d86a12e 100644 --- a/src/TagPrint.cxx +++ b/src/TagPrint.cxx @@ -40,8 +40,10 @@ tag_print(Response &r, TagType type, const char *value) void tag_print_values(Response &r, const Tag &tag) { + const auto tag_mask = r.GetTagMask(); for (const auto &i : tag) - tag_print(r, i.type, i.value); + if (tag_mask.Test(i.type)) + tag_print(r, i.type, i.value); } void diff --git a/src/client/Client.hxx b/src/client/Client.hxx index 759e611d9..7ce128ce3 100644 --- a/src/client/Client.hxx +++ b/src/client/Client.hxx @@ -23,6 +23,7 @@ #include "check.h" #include "ClientMessage.hxx" #include "command/CommandListBuilder.hxx" +#include "tag/Mask.hxx" #include "event/FullyBufferedSocket.hxx" #include "event/TimeoutMonitor.hxx" #include "Compiler.h" @@ -70,6 +71,11 @@ public: /** idle flags that the client wants to receive */ unsigned idle_subscriptions; + /** + * The tags this client is interested in. + */ + TagMask tag_mask = TagMask::All(); + /** * A list of channel names this client is subscribed to. */ diff --git a/src/client/Response.cxx b/src/client/Response.cxx index 31eb6a78e..b21ac91b5 100644 --- a/src/client/Response.cxx +++ b/src/client/Response.cxx @@ -23,6 +23,12 @@ #include "util/FormatString.hxx" #include "util/AllocatedString.hxx" +TagMask +Response::GetTagMask() const +{ + return GetClient().tag_mask; +} + bool Response::Write(const void *data, size_t length) { diff --git a/src/client/Response.hxx b/src/client/Response.hxx index 280dc1d55..a133e96a3 100644 --- a/src/client/Response.hxx +++ b/src/client/Response.hxx @@ -22,11 +22,13 @@ #include "check.h" #include "protocol/Ack.hxx" +#include "Compiler.h" #include #include class Client; +class TagMask; class Response { Client &client; @@ -59,6 +61,13 @@ public: return client; } + /** + * Accessor for Client::tag_mask. Can be used if caller wants + * to avoid including Client.hxx. + */ + gcc_pure + TagMask GetTagMask() const; + void SetCommand(const char *_command) { command = _command; } diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 7d65898dd..45aed51ad 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -187,7 +187,7 @@ static constexpr struct command commands[] = { { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, - { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, + { "tagtypes", PERMISSION_READ, 0, -1, handle_tagtypes }, { "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput }, #ifdef ENABLE_DATABASE { "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount }, diff --git a/src/command/ClientCommands.cxx b/src/command/ClientCommands.cxx index 3aa89b0e6..c5f70b94d 100644 --- a/src/command/ClientCommands.cxx +++ b/src/command/ClientCommands.cxx @@ -24,6 +24,8 @@ #include "client/Client.hxx" #include "client/Response.hxx" #include "TagPrint.hxx" +#include "tag/ParseName.hxx" +#include "util/StringAPI.hxx" CommandResult handle_close(gcc_unused Client &client, gcc_unused Request args, @@ -53,10 +55,58 @@ handle_password(Client &client, Request args, Response &r) return CommandResult::OK; } -CommandResult -handle_tagtypes(gcc_unused Client &client, gcc_unused Request request, - Response &r) +static TagMask +ParseTagMask(Request request) { - tag_print_types(r); - return CommandResult::OK; + if (request.IsEmpty()) + throw ProtocolError(ACK_ERROR_ARG, "Not enough arguments"); + + TagMask result = TagMask::None(); + + for (const char *name : request) { + auto type = tag_name_parse_i(name); + if (type == TAG_NUM_OF_ITEM_TYPES) + throw ProtocolError(ACK_ERROR_ARG, "Unknown tag type"); + + result |= type; + } + + return result; +} + +CommandResult +handle_tagtypes(Client &client, Request request, Response &r) +{ + if (request.IsEmpty()) { + tag_print_types(r); + return CommandResult::OK; + } + + const char *cmd = request.shift(); + if (StringIsEqual(cmd, "all")) { + if (!request.IsEmpty()) { + r.Error(ACK_ERROR_ARG, "Too many arguments"); + return CommandResult::ERROR; + } + + client.tag_mask = TagMask::All(); + return CommandResult::OK; + } else if (StringIsEqual(cmd, "clear")) { + if (!request.IsEmpty()) { + r.Error(ACK_ERROR_ARG, "Too many arguments"); + return CommandResult::ERROR; + } + + client.tag_mask = TagMask::None(); + return CommandResult::OK; + } else if (StringIsEqual(cmd, "enable")) { + client.tag_mask |= ParseTagMask(request); + return CommandResult::OK; + } else if (StringIsEqual(cmd, "disable")) { + client.tag_mask &= ~ParseTagMask(request); + return CommandResult::OK; + } else { + r.Error(ACK_ERROR_ARG, "Unknown sub command"); + return CommandResult::ERROR; + } } diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 48487fe8e..013d4dff2 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -97,7 +97,8 @@ print_tag(TagType type, const char *value, void *ctx) { auto &r = *(Response *)ctx; - tag_print(r, type, value); + if (r.GetClient().tag_mask.Test(type)) + tag_print(r, type, value); } CommandResult diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx index b5e0b69f7..31f971cd8 100644 --- a/src/db/DatabasePrint.cxx +++ b/src/db/DatabasePrint.cxx @@ -196,8 +196,9 @@ PrintUniqueTag(Response &r, TagType tag_type, assert(value != nullptr); tag_print(r, tag_type, value); + const auto tag_mask = r.GetTagMask(); for (const auto &item : tag) - if (item.type != tag_type) + if (item.type != tag_type && tag_mask.Test(item.type)) tag_print(r, item.type, item.value); }