Skip to content

Commit

Permalink
This commit adds a new protocol command to toggle protocol features
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jcorporation committed Sep 28, 2024
1 parent 124c0e6 commit 314fe8c
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 1 deletion.
38 changes: 38 additions & 0 deletions doc/protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
29 changes: 29 additions & 0 deletions src/client/Client.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
*/
Expand Down
74 changes: 74 additions & 0 deletions src/client/ProtocolFeature.cxx
Original file line number Diff line number Diff line change
@@ -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;
}
110 changes: 110 additions & 0 deletions src/client/ProtocolFeature.hxx
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions src/command/AllCommands.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
64 changes: 64 additions & 0 deletions src/command/ClientCommands.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
3 changes: 3 additions & 0 deletions src/command/ClientCommands.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/command/OtherCommands.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (...) {
Expand Down

0 comments on commit 314fe8c

Please sign in to comment.