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
|
||||
- apply Unicode normalization to case-insensitive filter expressions
|
||||
- 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"
|
||||
- allow range in listplaylist and listplaylistinfo
|
||||
- "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
|
||||
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
|
||||
|
|
|
@ -379,6 +379,7 @@ sources = [
|
|||
'src/client/File.cxx',
|
||||
'src/client/Response.cxx',
|
||||
'src/client/ThreadBackgroundCommand.cxx',
|
||||
'src/client/ProtocolFeature.cxx',
|
||||
'src/Listen.cxx',
|
||||
'src/LogInit.cxx',
|
||||
'src/ls.cxx',
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "IClient.hxx"
|
||||
#include "Message.hxx"
|
||||
#include "ProtocolFeature.hxx"
|
||||
#include "command/CommandResult.hxx"
|
||||
#include "command/CommandListBuilder.hxx"
|
||||
#include "input/LastInputStream.hxx"
|
||||
|
@ -111,6 +112,11 @@ private:
|
|||
*/
|
||||
std::unique_ptr<BackgroundCommand> background_command;
|
||||
|
||||
/**
|
||||
* Bitmask of protocol features.
|
||||
*/
|
||||
ProtocolFeature protocol_feature = ProtocolFeature::None();
|
||||
|
||||
public:
|
||||
Client(EventLoop &loop, Partition &partition,
|
||||
UniqueSocketDescriptor fd, int uid,
|
||||
|
@ -167,6 +173,29 @@ public:
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -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 },
|
||||
{ "prio", PERMISSION_PLAYER, 2, -1, handle_prio },
|
||||
{ "prioid", PERMISSION_PLAYER, 2, -1, handle_prioid },
|
||||
{ "protocol", PERMISSION_NONE, 0, -1, handle_protocol },
|
||||
{ "random", PERMISSION_PLAYER, 1, 1, handle_random },
|
||||
{ "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid },
|
||||
{ "readcomments", PERMISSION_READ, 1, 1, handle_read_comments },
|
||||
|
|
|
@ -109,3 +109,67 @@ handle_tagtypes(Client &client, Request request, Response &r)
|
|||
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
|
||||
handle_tagtypes(Client &client, Request request, Response &response);
|
||||
|
||||
CommandResult
|
||||
handle_protocol(Client &client, Request request, Response &response);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -160,7 +160,7 @@ handle_lsinfo_relative(Client &client, Response &r, const char *uri)
|
|||
(void)client;
|
||||
#endif
|
||||
|
||||
if (isRootDirectory(uri)) {
|
||||
if (!client.ProtocolFeatureEnabled(PF_HIDE_PLAYLISTS_IN_ROOT) && isRootDirectory(uri)) {
|
||||
try {
|
||||
print_spl_list(r, ListPlaylistFiles());
|
||||
} catch (...) {
|
||||
|
|
Loading…
Reference in New Issue