Commit 995aafe9 authored by Max Kellermann's avatar Max Kellermann

protocol: add command "binarylimit"

Increasing the protocol version to 0.22.4 to allow clients to detect this feature. Closes https://github.com/MusicPlayerDaemon/MPD/issues/1038
parent 6e33566c
ver 0.22.4 (not yet released) ver 0.22.4 (not yet released)
* protocol * protocol
- add command "binarylimit" to allow larger chunk sizes
- fix "readpicture" on 32 bit machines - fix "readpicture" on 32 bit machines
- show duration and tags of songs in virtual playlist (CUE) folders - show duration and tags of songs in virtual playlist (CUE) folders
* storage * storage
......
...@@ -69,11 +69,14 @@ that, the specified number of bytes of binary data follows, then a ...@@ -69,11 +69,14 @@ that, the specified number of bytes of binary data follows, then a
newline, and finally the ``OK`` line. newline, and finally the ``OK`` line.
If the object to be transmitted is large, the server may choose a If the object to be transmitted is large, the server may choose a
reasonable chunk size and transmit only a portion. Usually, the reasonable chunk size and transmit only a portion. The maximum chunk
response also contains a ``size`` line which specifies the total size can be changed by clients with the :ref:`binarylimit
(uncropped) size, and the command usually has a way to specify an <command_binarylimit>` command.
offset into the object; this way, the client can copy the whole file
without blocking the connection for too long. Usually, the response also contains a ``size`` line which specifies
the total (uncropped) size, and the command usually has a way to
specify an offset into the object; this way, the client can copy the
whole file without blocking the connection for too long.
Example:: Example::
...@@ -1355,6 +1358,17 @@ Connection settings ...@@ -1355,6 +1358,17 @@ Connection settings
:command:`ping` :command:`ping`
Does nothing but return "OK". Does nothing but return "OK".
.. _command_binarylimit:
:command:`binarylimit SIZE` [#since_0_22_4]_
Set the maximum :ref:`binary response <binary>` size for the
current connection to the specified number of bytes.
A bigger value means less overhead for transmitting large
entities, but it also means that the connection is blocked for a
longer time.
.. _command_tagtypes: .. _command_tagtypes:
:command:`tagtypes` :command:`tagtypes`
...@@ -1583,3 +1597,4 @@ client-to-client messages are local to the current partition. ...@@ -1583,3 +1597,4 @@ client-to-client messages are local to the current partition.
.. [#since_0_19] Since :program:`MPD` 0.19 .. [#since_0_19] Since :program:`MPD` 0.19
.. [#since_0_20] Since :program:`MPD` 0.20 .. [#since_0_20] Since :program:`MPD` 0.20
.. [#since_0_21] Since :program:`MPD` 0.21 .. [#since_0_21] Since :program:`MPD` 0.21
.. [#since_0_22_4] Since :program:`MPD` 0.22.4
...@@ -32,7 +32,7 @@ version_conf = configuration_data() ...@@ -32,7 +32,7 @@ version_conf = configuration_data()
version_conf.set_quoted('PACKAGE', meson.project_name()) version_conf.set_quoted('PACKAGE', meson.project_name())
version_conf.set_quoted('PACKAGE_NAME', meson.project_name()) version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
version_conf.set_quoted('VERSION', meson.project_version()) version_conf.set_quoted('VERSION', meson.project_version())
version_conf.set_quoted('PROTOCOL_VERSION', '0.22.0') version_conf.set_quoted('PROTOCOL_VERSION', '0.22.4')
configure_file(output: 'Version.h', configuration: version_conf) configure_file(output: 'Version.h', configuration: version_conf)
conf = configuration_data() conf = configuration_data()
......
...@@ -84,6 +84,12 @@ public: ...@@ -84,6 +84,12 @@ public:
*/ */
TagMask tag_mask = TagMask::All(); TagMask tag_mask = TagMask::All();
/**
* The maximum number of bytes transmitted in a binary
* response. Can be changed with the "binarylimit" command.
*/
size_t binary_limit = 8192;
private: private:
static constexpr size_t MAX_SUBSCRIPTIONS = 16; static constexpr size_t MAX_SUBSCRIPTIONS = 16;
...@@ -122,6 +128,7 @@ public: ...@@ -122,6 +128,7 @@ public:
~Client() noexcept; ~Client() noexcept;
using FullyBufferedSocket::GetEventLoop; using FullyBufferedSocket::GetEventLoop;
using FullyBufferedSocket::GetOutputMaxSize;
gcc_pure gcc_pure
bool IsExpired() const noexcept { bool IsExpired() const noexcept {
......
...@@ -59,7 +59,7 @@ Response::Format(const char *fmt, ...) noexcept ...@@ -59,7 +59,7 @@ Response::Format(const char *fmt, ...) noexcept
bool bool
Response::WriteBinary(ConstBuffer<void> payload) noexcept Response::WriteBinary(ConstBuffer<void> payload) noexcept
{ {
assert(payload.size <= MAX_BINARY_SIZE); assert(payload.size <= client.binary_limit);
return Format("binary: %zu\n", payload.size) && return Format("binary: %zu\n", payload.size) &&
Write(payload.data, payload.size) && Write(payload.data, payload.size) &&
......
...@@ -79,8 +79,6 @@ public: ...@@ -79,8 +79,6 @@ public:
gcc_printf(2,3) gcc_printf(2,3)
bool Format(const char *fmt, ...) noexcept; bool Format(const char *fmt, ...) noexcept;
static constexpr size_t MAX_BINARY_SIZE = 8192;
/** /**
* Write a binary chunk; this writes the "binary" line, the * Write a binary chunk; this writes the "binary" line, the
* given chunk and the trailing newline. * given chunk and the trailing newline.
......
...@@ -87,6 +87,7 @@ static constexpr struct command commands[] = { ...@@ -87,6 +87,7 @@ static constexpr struct command commands[] = {
{ "addid", PERMISSION_ADD, 1, 2, handle_addid }, { "addid", PERMISSION_ADD, 1, 2, handle_addid },
{ "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid }, { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
{ "albumart", PERMISSION_READ, 2, 2, handle_album_art }, { "albumart", PERMISSION_READ, 2, 2, handle_album_art },
{ "binarylimit", PERMISSION_NONE, 1, 1, handle_binary_limit },
{ "channels", PERMISSION_READ, 0, 0, handle_channels }, { "channels", PERMISSION_READ, 0, 0, handle_channels },
{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
......
...@@ -41,6 +41,21 @@ handle_ping([[maybe_unused]] Client &client, [[maybe_unused]] Request args, ...@@ -41,6 +41,21 @@ handle_ping([[maybe_unused]] Client &client, [[maybe_unused]] Request args,
} }
CommandResult CommandResult
handle_binary_limit(Client &client, Request args,
[[maybe_unused]] Response &r)
{
size_t value = args.ParseUnsigned(0, client.GetOutputMaxSize() - 4096);
if (value < 64) {
r.Error(ACK_ERROR_ARG, "Value too small");
return CommandResult::ERROR;
}
client.binary_limit = value;
return CommandResult::OK;
}
CommandResult
handle_password(Client &client, Request args, Response &r) handle_password(Client &client, Request args, Response &r)
{ {
unsigned permission = 0; unsigned permission = 0;
......
...@@ -33,6 +33,9 @@ CommandResult ...@@ -33,6 +33,9 @@ CommandResult
handle_ping(Client &client, Request request, Response &response); handle_ping(Client &client, Request request, Response &response);
CommandResult CommandResult
handle_binary_limit(Client &client, Request request, Response &response);
CommandResult
handle_password(Client &client, Request request, Response &response); handle_password(Client &client, Request request, Response &response);
CommandResult CommandResult
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <algorithm>
#include <cassert> #include <cassert>
#include <cinttypes> /* for PRIu64 */ #include <cinttypes> /* for PRIu64 */
...@@ -205,28 +206,26 @@ read_stream_art(Response &r, const char *uri, size_t offset) ...@@ -205,28 +206,26 @@ read_stream_art(Response &r, const char *uri, size_t offset)
const offset_type art_file_size = is->GetSize(); const offset_type art_file_size = is->GetSize();
if (offset >= art_file_size) { if (offset > art_file_size) {
if (offset > art_file_size) { r.Error(ACK_ERROR_ARG, "Offset too large");
r.Error(ACK_ERROR_ARG, "Offset too large"); return CommandResult::ERROR;
return CommandResult::ERROR;
} else {
r.Format("size: %" PRIoffset "\n", art_file_size);
r.WriteBinary(nullptr);
return CommandResult::OK;
}
} }
uint8_t buffer[Response::MAX_BINARY_SIZE]; std::size_t buffer_size =
size_t read_size; std::min<offset_type>(art_file_size - offset,
r.GetClient().binary_limit);
{ std::unique_ptr<std::byte[]> buffer(new std::byte[buffer_size]);
std::size_t read_size = 0;
if (buffer_size > 0) {
std::unique_lock<Mutex> lock(mutex); std::unique_lock<Mutex> lock(mutex);
is->Seek(lock, offset); is->Seek(lock, offset);
read_size = is->Read(lock, &buffer, sizeof(buffer)); read_size = is->Read(lock, buffer.get(), buffer_size);
} }
r.Format("size: %" PRIoffset "\n", art_file_size); r.Format("size: %" PRIoffset "\n", art_file_size);
r.WriteBinary({buffer, read_size}); r.WriteBinary({buffer.get(), read_size});
return CommandResult::OK; return CommandResult::OK;
} }
...@@ -313,8 +312,10 @@ public: ...@@ -313,8 +312,10 @@ public:
response.Format("type: %s\n", mime_type); response.Format("type: %s\n", mime_type);
buffer.size -= offset; buffer.size -= offset;
if (buffer.size > Response::MAX_BINARY_SIZE)
buffer.size = Response::MAX_BINARY_SIZE; const std::size_t binary_limit = response.GetClient().binary_limit;
if (buffer.size > binary_limit)
buffer.size = binary_limit;
buffer.data = OffsetPointer(buffer.data, offset); buffer.data = OffsetPointer(buffer.data, offset);
response.WriteBinary(buffer); response.WriteBinary(buffer);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment