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:
parent
124c0e66ee
commit
23c2bba483
2
NEWS
2
NEWS
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
|
@ -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 },
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 (...) {
|
||||||
|
|
Loading…
Reference in New Issue