This commit adds a new protocol command to toggle protocol features

for a client connection. It works like the tag_mask and the associated
tagtypes command.

New commands:

- protocol
  Shows enabled protocol features.

- protocol available
  Show all available protocol features.

- protocol enable {feature...}
  Enables protocol features.

- protocol disable {feature...}
  Disables protocol features.

- protocol all
  Enables all available protocol features.

- protocol clear
  Disables all protocol features.

This commit adds also the first protocol feature.

hide_playlists_in_root
  Disables the listing of playlists in the root folder
  for the lsinfo command.
This commit is contained in:
jcorporation 2024-09-28 16:36:43 +02:00
parent 124c0e66ee
commit 23c2bba483
10 changed files with 322 additions and 2 deletions

2
NEWS
View File

@ -10,7 +10,7 @@ ver 0.24 (not yet released)
- show PCRE support in "config" response - show PCRE support in "config" response
- apply Unicode normalization to case-insensitive filter expressions - apply Unicode normalization to case-insensitive filter expressions
- stickers on playlists and some tag types - stickers on playlists and some tag types
- new commands "stickernames", "stickertypes", "playlistlength", "searchplaylist" - new commands "stickernames", "stickertypes", "playlistlength", "searchplaylist", "protocol"
- new "search"/"find" filter "added-since" - new "search"/"find" filter "added-since"
- allow range in listplaylist and listplaylistinfo - allow range in listplaylist and listplaylistinfo
- "sticker find" supports sort and window parameter and new sticker compare operators "eq", "lt" and "gt" - "sticker find" supports sort and window parameter and new sticker compare operators "eq", "lt" and "gt"

View File

@ -1678,6 +1678,44 @@ Connection settings
Announce that this client is interested in all tag Announce that this client is interested in all tag
types. This is the default setting for new clients. types. This is the default setting for new clients.
.. _command_protocol:
:command:`protocol`
Shows a list of enabled protocol features.
Available features:
- ``hide_playlists_in_root``: disables the listing of
stored playlists for the :ref:`lsinfo <command_lsinfo>`.
The following ``protocol`` sub commands configure the
protocol features.
.. _command_protocol_disable:
:command:`protocol disable {FEATURE...}`
Disables one or more features.
.. _command_protocol_enable:
:command:`protocol enable {FEATURE...}`
Enables one or more features.
.. _command_protocol_clear:
:command:`protocol clear`
Disables all protocol features.
.. _command_protocol_all:
:command:`protocol all`
Enables all protocol features.
.. _command_protocol_available:
:command:`protocol available`
Lists all available protocol features.
.. _partition_commands: .. _partition_commands:
Partition commands Partition commands

View File

@ -379,6 +379,7 @@ sources = [
'src/client/File.cxx', 'src/client/File.cxx',
'src/client/Response.cxx', 'src/client/Response.cxx',
'src/client/ThreadBackgroundCommand.cxx', 'src/client/ThreadBackgroundCommand.cxx',
'src/client/ProtocolFeature.cxx',
'src/Listen.cxx', 'src/Listen.cxx',
'src/LogInit.cxx', 'src/LogInit.cxx',
'src/ls.cxx', 'src/ls.cxx',

View File

@ -5,6 +5,7 @@
#include "IClient.hxx" #include "IClient.hxx"
#include "Message.hxx" #include "Message.hxx"
#include "ProtocolFeature.hxx"
#include "command/CommandResult.hxx" #include "command/CommandResult.hxx"
#include "command/CommandListBuilder.hxx" #include "command/CommandListBuilder.hxx"
#include "input/LastInputStream.hxx" #include "input/LastInputStream.hxx"
@ -111,6 +112,11 @@ private:
*/ */
std::unique_ptr<BackgroundCommand> background_command; std::unique_ptr<BackgroundCommand> background_command;
/**
* Bitmask of protocol features.
*/
ProtocolFeature protocol_feature = ProtocolFeature::None();
public: public:
Client(EventLoop &loop, Partition &partition, Client(EventLoop &loop, Partition &partition,
UniqueSocketDescriptor fd, int uid, UniqueSocketDescriptor fd, int uid,
@ -167,6 +173,29 @@ public:
permission = _permission; permission = _permission;
} }
ProtocolFeature GetProtocolFeatures() const noexcept {
return protocol_feature;
}
void SetProtocolFeatures(ProtocolFeature features, bool enable) noexcept {
if (enable)
protocol_feature.Set(features);
else
protocol_feature.Unset(features);
}
void AllProtocolFeatures() noexcept {
protocol_feature.SetAll();
}
void ClearProtocolFeatures() noexcept {
protocol_feature.Clear();
}
bool ProtocolFeatureEnabled(enum ProtocolFeatureType value) noexcept {
return protocol_feature.Test(value);
}
/** /**
* Send "idle" response to this client. * Send "idle" response to this client.
*/ */

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#include "ProtocolFeature.hxx"
#include "Client.hxx"
#include "Response.hxx"
#include "util/StringAPI.hxx"
#include <cassert>
#include <fmt/format.h>
struct feature_type_table {
const char *name;
ProtocolFeatureType type;
};
static constexpr struct feature_type_table protocol_feature_names_init[] = {
{"hide_playlists_in_root", PF_HIDE_PLAYLISTS_IN_ROOT},
};
/**
* This function converts the #tag_item_names_init array to an
* associative array at compile time. This is a kludge because C++20
* doesn't support designated initializers for arrays, unlike C99.
*/
static constexpr auto
MakeProtocolFeatureNames() noexcept
{
std::array<const char *, PF_NUM_OF_ITEM_TYPES> result{};
static_assert(std::size(protocol_feature_names_init) == result.size());
for (const auto &i : protocol_feature_names_init) {
/* no duplicates allowed */
assert(result[i.type] == nullptr);
result[i.type] = i.name;
}
return result;
}
constinit const std::array<const char *, PF_NUM_OF_ITEM_TYPES> protocol_feature_names = MakeProtocolFeatureNames();
void
protocol_features_print(Client &client, Response &r) noexcept
{
const auto protocol_feature = client.GetProtocolFeatures();
for (unsigned i = 0; i < PF_NUM_OF_ITEM_TYPES; i++)
if (protocol_feature.Test(ProtocolFeatureType(i)))
r.Fmt(FMT_STRING("feature: {}\n"), protocol_feature_names[i]);
}
void
protocol_features_print_all(Response &r) noexcept
{
for (unsigned i = 0; i < PF_NUM_OF_ITEM_TYPES; i++)
r.Fmt(FMT_STRING("feature: {}\n"), protocol_feature_names[i]);
}
ProtocolFeatureType
protocol_feature_parse_i(const char *name) noexcept
{
for (unsigned i = 0; i < PF_NUM_OF_ITEM_TYPES; ++i) {
assert(protocol_feature_names[i] != nullptr);
if (StringIsEqualIgnoreCase(name, protocol_feature_names[i]))
return (ProtocolFeatureType)i;
}
return PF_NUM_OF_ITEM_TYPES;
}

View File

@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
#pragma once
#include <string_view>
#include <cstdint>
class Client;
class Response;
/**
* Codes for the type of a protocol feature.
*/
enum ProtocolFeatureType : uint8_t {
PF_HIDE_PLAYLISTS_IN_ROOT,
PF_NUM_OF_ITEM_TYPES
};
class ProtocolFeature {
using protocol_feature_t = uint_least8_t;
/* must have enough bits to represent all protocol features
supported by MPD */
static_assert(PF_NUM_OF_ITEM_TYPES <= sizeof(protocol_feature_t) * 8);
protocol_feature_t value;
explicit constexpr ProtocolFeature(protocol_feature_t _value) noexcept
:value(_value) {}
public:
constexpr ProtocolFeature() noexcept = default;
constexpr ProtocolFeature(ProtocolFeatureType _value) noexcept
:value(protocol_feature_t(1) << protocol_feature_t(_value)) {}
static constexpr ProtocolFeature None() noexcept {
return ProtocolFeature(protocol_feature_t(0));
}
static constexpr ProtocolFeature All() noexcept {
return ~None();
}
constexpr ProtocolFeature operator~() const noexcept {
return ProtocolFeature(~value);
}
constexpr ProtocolFeature operator&(ProtocolFeature other) const noexcept {
return ProtocolFeature(value & other.value);
}
constexpr ProtocolFeature operator|(ProtocolFeature other) const noexcept {
return ProtocolFeature(value | other.value);
}
constexpr ProtocolFeature operator^(ProtocolFeature other) const noexcept {
return ProtocolFeature(value ^ other.value);
}
constexpr ProtocolFeature &operator&=(ProtocolFeature other) noexcept {
value &= other.value;
return *this;
}
constexpr ProtocolFeature &operator|=(ProtocolFeature other) noexcept {
value |= other.value;
return *this;
}
constexpr ProtocolFeature &operator^=(ProtocolFeature other) noexcept {
value ^= other.value;
return *this;
}
constexpr bool TestAny() const noexcept {
return value != 0;
}
constexpr bool Test(ProtocolFeatureType feature) const noexcept {
return (*this & feature).TestAny();
}
constexpr void Set(ProtocolFeature features) noexcept {
*this |= features;
}
constexpr void Unset(ProtocolFeature features) noexcept {
*this &= ~ProtocolFeature(features);
}
constexpr void SetAll() noexcept {
*this = ProtocolFeature::All();
}
constexpr void Clear() noexcept {
*this = ProtocolFeature::None();
}
};
void
protocol_features_print(Client &client, Response &r) noexcept;
void
protocol_features_print_all(Response &r) noexcept;
ProtocolFeatureType
protocol_feature_parse_i(const char *name) noexcept;

View File

@ -156,6 +156,7 @@ static constexpr struct command commands[] = {
{ "previous", PERMISSION_PLAYER, 0, 0, handle_previous }, { "previous", PERMISSION_PLAYER, 0, 0, handle_previous },
{ "prio", PERMISSION_PLAYER, 2, -1, handle_prio }, { "prio", PERMISSION_PLAYER, 2, -1, handle_prio },
{ "prioid", PERMISSION_PLAYER, 2, -1, handle_prioid }, { "prioid", PERMISSION_PLAYER, 2, -1, handle_prioid },
{ "protocol", PERMISSION_NONE, 0, -1, handle_protocol },
{ "random", PERMISSION_PLAYER, 1, 1, handle_random }, { "random", PERMISSION_PLAYER, 1, 1, handle_random },
{ "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid }, { "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid },
{ "readcomments", PERMISSION_READ, 1, 1, handle_read_comments }, { "readcomments", PERMISSION_READ, 1, 1, handle_read_comments },

View File

@ -109,3 +109,67 @@ handle_tagtypes(Client &client, Request request, Response &r)
return CommandResult::ERROR; return CommandResult::ERROR;
} }
} }
static ProtocolFeature
ParseProtocolFeature(Request request)
{
if (request.empty())
throw ProtocolError(ACK_ERROR_ARG, "Not enough arguments");
ProtocolFeature result = ProtocolFeature::None();
for (const char *name : request) {
auto type = protocol_feature_parse_i(name);
if (type == PF_NUM_OF_ITEM_TYPES)
throw ProtocolError(ACK_ERROR_ARG, "Unknown protcol feature");
result |= type;
}
return result;
}
CommandResult
handle_protocol(Client &client, Request request, Response &r)
{
if (request.empty()) {
protocol_features_print(client, r);
return CommandResult::OK;
}
const char *cmd = request.shift();
if (StringIsEqual(cmd, "all")) {
if (!request.empty()) {
r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
}
client.AllProtocolFeatures();
return CommandResult::OK;
} else if (StringIsEqual(cmd, "clear")) {
if (!request.empty()) {
r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
}
client.ClearProtocolFeatures();
return CommandResult::OK;
} else if (StringIsEqual(cmd, "enable")) {
client.SetProtocolFeatures(ParseProtocolFeature(request), true);
return CommandResult::OK;
} else if (StringIsEqual(cmd, "disable")) {
client.SetProtocolFeatures(ParseProtocolFeature(request), false);
return CommandResult::OK;
} else if (StringIsEqual(cmd, "available")) {
if (!request.empty()) {
r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR;
}
protocol_features_print_all(r);
return CommandResult::OK;
} else {
r.Error(ACK_ERROR_ARG, "Unknown sub command");
return CommandResult::ERROR;
}
}

View File

@ -25,4 +25,7 @@ handle_password(Client &client, Request request, Response &response);
CommandResult CommandResult
handle_tagtypes(Client &client, Request request, Response &response); handle_tagtypes(Client &client, Request request, Response &response);
CommandResult
handle_protocol(Client &client, Request request, Response &response);
#endif #endif

View File

@ -160,7 +160,7 @@ handle_lsinfo_relative(Client &client, Response &r, const char *uri)
(void)client; (void)client;
#endif #endif
if (isRootDirectory(uri)) { if (!client.ProtocolFeatureEnabled(PF_HIDE_PLAYLISTS_IN_ROOT) && isRootDirectory(uri)) {
try { try {
print_spl_list(r, ListPlaylistFiles()); print_spl_list(r, ListPlaylistFiles());
} catch (...) { } catch (...) {