Merge branch 'protocol_features' of https://github.com/jcorporation/MPD

This commit is contained in:
Max Kellermann 2024-10-26 08:18:56 +02:00
commit 449f8af7d0
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", "stickernamestypes", "playlistlength", "searchplaylist" - new commands "stickernames", "stickertypes", "stickernamestypes", "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", "gt", "contains" and "starts_with" - "sticker find" supports sort and window parameter and new sticker compare operators "eq", "lt", "gt", "contains" and "starts_with"

View File

@ -1688,6 +1688,44 @@ Connection settings
Shows the list of tag types configured Shows the list of tag types configured
by the ``metadata_to_use`` setting. by the ``metadata_to_use`` setting.
.. _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

@ -112,3 +112,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 (...) {