Merge tag 'v0.22.4'

release v0.22.4
This commit is contained in:
Max Kellermann
2021-01-21 17:42:26 +01:00
33 changed files with 353 additions and 101 deletions

View File

@@ -91,7 +91,14 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
if (song.audio_format.IsDefined())
r.Format("Format: %s\n", ToString(song.audio_format).c_str());
tag_print(r, song.tag);
tag_print_values(r, song.tag);
const auto duration = song.GetDuration();
if (!duration.IsNegative())
r.Format("Time: %i\n"
"duration: %1.3f\n",
duration.RoundS(),
duration.ToDoubleS());
}
void

View File

@@ -84,6 +84,12 @@ public:
*/
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:
static constexpr size_t MAX_SUBSCRIPTIONS = 16;
@@ -122,6 +128,7 @@ public:
~Client() noexcept;
using FullyBufferedSocket::GetEventLoop;
using FullyBufferedSocket::GetOutputMaxSize;
gcc_pure
bool IsExpired() const noexcept {

View File

@@ -59,7 +59,7 @@ Response::Format(const char *fmt, ...) noexcept
bool
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) &&
Write(payload.data, payload.size) &&

View File

@@ -75,9 +75,9 @@ public:
bool Write(const void *data, size_t length) noexcept;
bool Write(const char *data) noexcept;
bool FormatV(const char *fmt, std::va_list args) noexcept;
bool Format(const char *fmt, ...) noexcept;
static constexpr size_t MAX_BINARY_SIZE = 8192;
gcc_printf(2,3)
bool Format(const char *fmt, ...) noexcept;
/**
* Write a binary chunk; this writes the "binary" line, the

View File

@@ -87,6 +87,7 @@ static constexpr struct command commands[] = {
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
{ "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
{ "albumart", PERMISSION_READ, 2, 2, handle_album_art },
{ "binarylimit", PERMISSION_NONE, 1, 1, handle_binary_limit },
{ "channels", PERMISSION_READ, 0, 0, handle_channels },
{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },

View File

@@ -40,6 +40,21 @@ handle_ping([[maybe_unused]] Client &client, [[maybe_unused]] Request args,
return CommandResult::OK;
}
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)
{

View File

@@ -32,6 +32,9 @@ handle_close(Client &client, Request request, Response &response);
CommandResult
handle_ping(Client &client, Request request, Response &response);
CommandResult
handle_binary_limit(Client &client, Request request, Response &response);
CommandResult
handle_password(Client &client, Request request, Response &response);

View File

@@ -43,6 +43,7 @@
#include "thread/Mutex.hxx"
#include "Log.hxx"
#include <algorithm>
#include <cassert>
#include <cinttypes> /* for PRIu64 */
@@ -202,17 +203,26 @@ read_stream_art(Response &r, const char *uri, size_t offset)
const offset_type art_file_size = is->GetSize();
uint8_t buffer[Response::MAX_BINARY_SIZE];
size_t read_size;
if (offset > art_file_size) {
r.Error(ACK_ERROR_ARG, "Offset too large");
return CommandResult::ERROR;
}
{
std::size_t buffer_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);
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.WriteBinary({buffer, read_size});
r.WriteBinary({buffer.get(), read_size});
return CommandResult::OK;
}
@@ -293,14 +303,16 @@ public:
return;
}
response.Format("size: %" PRIoffset "\n", buffer.size);
response.Format("size: %zu\n", buffer.size);
if (mime_type != nullptr)
response.Format("type: %s\n", mime_type);
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);
response.WriteBinary(buffer);

View File

@@ -54,12 +54,12 @@ public:
return ParseCommandArgInt(data[idx], min_value, max_value);
}
int ParseUnsigned(unsigned idx) const {
unsigned ParseUnsigned(unsigned idx) const {
assert(idx < size);
return ParseCommandArgUnsigned(data[idx]);
}
int ParseUnsigned(unsigned idx, unsigned max_value) const {
unsigned ParseUnsigned(unsigned idx, unsigned max_value) const {
assert(idx < size);
return ParseCommandArgUnsigned(data[idx], max_value);
}

View File

@@ -18,11 +18,11 @@
*/
#include "Directory.hxx"
#include "ExportedSong.hxx"
#include "SongSort.hxx"
#include "Song.hxx"
#include "Mount.hxx"
#include "db/LightDirectory.hxx"
#include "song/LightSong.hxx"
#include "db/Uri.hxx"
#include "db/DatabaseLock.hxx"
#include "db/Interface.hxx"
@@ -234,7 +234,7 @@ Directory::Walk(bool recursive, const SongFilter *filter,
if (visit_song) {
for (auto &song : songs){
const LightSong song2 = song.Export();
const auto song2 = song.Export();
if (filter == nullptr || filter->Match(song2))
visit_song(song2);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DB_SIMPLE_EXPORTED_SONG_HXX
#define MPD_DB_SIMPLE_EXPORTED_SONG_HXX
#include "song/LightSong.hxx"
#include "tag/Tag.hxx"
/**
* The return type for Song::Export(). In addition to implementing
* #LightSong, it hold allocations necessary to represent the #Song as
* a #LightSong, e.g. a merged #Tag.
*/
class ExportedSong : public LightSong {
Tag tag_buffer;
public:
using LightSong::LightSong;
ExportedSong(const char *_uri, Tag &&_tag) noexcept
:LightSong(_uri, tag_buffer),
tag_buffer(std::move(_tag)) {}
};
#endif

View File

@@ -233,25 +233,25 @@ SimpleDatabase::GetSong(std::string_view uri) const
"No such song");
const Song *song = r.directory->FindSong(r.rest);
protect.unlock();
if (song == nullptr)
throw DatabaseError(DatabaseErrorCode::NOT_FOUND,
"No such song");
light_song.Construct(song->Export());
exported_song.Construct(song->Export());
protect.unlock();
#ifndef NDEBUG
++borrowed_song_count;
#endif
return &light_song.Get();
return &exported_song.Get();
}
void
SimpleDatabase::ReturnSong([[maybe_unused]] const LightSong *song) const noexcept
{
assert(song != nullptr);
assert(song == prefixed_light_song || song == &light_song.Get());
assert(song == prefixed_light_song || song == &exported_song.Get());
if (prefixed_light_song != nullptr) {
delete prefixed_light_song;
@@ -262,7 +262,7 @@ SimpleDatabase::ReturnSong([[maybe_unused]] const LightSong *song) const noexcep
--borrowed_song_count;
#endif
light_song.Destruct();
exported_song.Destruct();
}
}
@@ -316,7 +316,7 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
if (visit_song) {
Song *song = r.directory->FindSong(r.rest);
if (song != nullptr) {
const LightSong song2 = song->Export();
const auto song2 = song->Export();
if (selection.Match(song2))
visit_song(song2);

View File

@@ -20,10 +20,10 @@
#ifndef MPD_SIMPLE_DATABASE_PLUGIN_HXX
#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
#include "ExportedSong.hxx"
#include "db/Interface.hxx"
#include "db/Ptr.hxx"
#include "fs/AllocatedPath.hxx"
#include "song/LightSong.hxx"
#include "util/Manual.hxx"
#include "util/Compiler.h"
#include "config.h"
@@ -63,7 +63,7 @@ class SimpleDatabase : public Database {
/**
* A buffer for GetSong().
*/
mutable Manual<LightSong> light_song;
mutable Manual<ExportedSong> exported_song;
#ifndef NDEBUG
mutable unsigned borrowed_song_count;

View File

@@ -18,11 +18,15 @@
*/
#include "Song.hxx"
#include "ExportedSong.hxx"
#include "Directory.hxx"
#include "tag/Tag.hxx"
#include "tag/Builder.hxx"
#include "song/DetachedSong.hxx"
#include "song/LightSong.hxx"
#include "fs/Traits.hxx"
#include "time/ChronoUtil.hxx"
#include "util/IterableSplitString.hxx"
Song::Song(DetachedSong &&other, Directory &_parent) noexcept
:tag(std::move(other.WritableTag())),
@@ -53,17 +57,87 @@ Song::GetURI() const noexcept
}
}
LightSong
/**
* Path name traversal of a #Directory.
*/
gcc_pure
static const Directory *
FindTargetDirectory(const Directory &base, StringView path) noexcept
{
const auto *directory = &base;
for (const StringView name : IterableSplitString(path, '/')) {
if (name.empty() || name.Equals("."))
continue;
directory = name.Equals("..")
? directory->parent
: directory->FindChild(name);
if (directory == nullptr)
break;
}
return directory;
}
/**
* Path name traversal of a #Song.
*/
gcc_pure
static const Song *
FindTargetSong(const Directory &_directory, StringView target) noexcept
{
auto [path, last] = target.SplitLast('/');
if (last == nullptr) {
last = path;
path = nullptr;
}
if (last.empty())
return nullptr;
const auto *directory = FindTargetDirectory(_directory, path);
if (directory == nullptr)
return nullptr;
return directory->FindSong(last);
}
ExportedSong
Song::Export() const noexcept
{
LightSong dest(filename.c_str(), tag);
const auto *target_song = !target.empty()
? FindTargetSong(parent, (std::string_view)target)
: nullptr;
Tag merged_tag;
if (target_song != nullptr) {
/* if we found the target song (which may be the
underlying song file of a CUE file), merge the tags
from that song with this song's tags (from the CUE
file) */
TagBuilder builder(tag);
builder.Complement(target_song->tag);
merged_tag = builder.Commit();
}
ExportedSong dest = merged_tag.IsDefined()
? ExportedSong(filename.c_str(), std::move(merged_tag))
: ExportedSong(filename.c_str(), tag);
if (!parent.IsRoot())
dest.directory = parent.GetPath();
if (!target.empty())
dest.real_uri = target.c_str();
dest.mtime = mtime;
dest.start_time = start_time;
dest.end_time = end_time;
dest.audio_format = audio_format;
dest.mtime = IsNegative(mtime) && target_song != nullptr
? target_song->mtime
: mtime;
dest.start_time = start_time.IsZero() && target_song != nullptr
? target_song->start_time
: start_time;
dest.end_time = end_time.IsZero() && target_song != nullptr
? target_song->end_time
: end_time;
dest.audio_format = audio_format.IsDefined() || target_song == nullptr
? audio_format
: target_song->audio_format;
return dest;
}

View File

@@ -32,8 +32,8 @@
#include <string>
struct StringView;
struct LightSong;
struct Directory;
class ExportedSong;
class DetachedSong;
class Storage;
class ArchiveFile;
@@ -153,7 +153,7 @@ struct Song {
std::string GetURI() const noexcept;
gcc_pure
LightSong Export() const noexcept;
ExportedSong Export() const noexcept;
};
typedef boost::intrusive::list<Song,

View File

@@ -48,6 +48,10 @@ public:
BufferedSocket::Close();
}
std::size_t GetOutputMaxSize() const noexcept {
return output.max_size();
}
private:
/**
* @return the number of bytes written to the socket, 0 if the

View File

@@ -369,8 +369,15 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block)
proxy_user = block.GetBlockValue("proxy_user");
proxy_password = block.GetBlockValue("proxy_password");
verify_peer = block.GetBlockValue("verify_peer", true);
verify_host = block.GetBlockValue("verify_host", true);
#ifdef ANDROID
// TODO: figure out how to use Android's CA certificates and re-enable verify
constexpr bool default_verify = false;
#else
constexpr bool default_verify = true;
#endif
verify_peer = block.GetBlockValue("verify_peer", default_verify);
verify_host = block.GetBlockValue("verify_host", default_verify);
}
static void

View File

@@ -40,6 +40,13 @@
#include <array>
#if GCC_CHECK_VERSION(11,0)
#pragma GCC diagnostic push
/* bogus GCC 11 warning "ovector may be used uninitialized" in the
ovector.size() call */
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
class RegexPointer {
protected:
pcre *re = nullptr;
@@ -63,4 +70,8 @@ public:
}
};
#if GCC_CHECK_VERSION(11,0)
#pragma GCC diagnostic pop
#endif
#endif

View File

@@ -33,6 +33,7 @@
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <cassert>
#include <iterator>
#include <stdexcept>

View File

@@ -468,19 +468,11 @@ CurlStorage::GetInfo(std::string_view uri_utf8, [[maybe_unused]] bool follow)
gcc_pure
static std::string_view
UriPathOrSlash(const char *uri, bool relative) noexcept
UriPathOrSlash(const char *uri) noexcept
{
auto path = uri_get_path(uri);
if (path.data() == nullptr)
path = "/";
else if (relative) {
// search after first slash
path = path.substr(1);
auto slash = path.find('/');
if (slash != std::string_view::npos)
path = path.substr(slash);
}
return path;
}
@@ -489,15 +481,13 @@ UriPathOrSlash(const char *uri, bool relative) noexcept
*/
class HttpListDirectoryOperation final : public PropfindOperation {
const std::string base_path;
const std::string base_path_relative;
MemoryStorageDirectoryReader::List entries;
public:
HttpListDirectoryOperation(CurlGlobal &curl, const char *uri)
:PropfindOperation(curl, uri, 1),
base_path(CurlUnescape(GetEasy(), UriPathOrSlash(uri, false))),
base_path_relative(CurlUnescape(GetEasy(), UriPathOrSlash(uri, true))) {}
base_path(CurlUnescape(GetEasy(), UriPathOrSlash(uri))) {}
std::unique_ptr<StorageDirectoryReader> Perform() {
DeferStart();
@@ -523,15 +513,9 @@ private:
/* kludge: ignoring case in this comparison to avoid
false negatives if the web server uses a different
case */
if (uri_has_scheme(path)) {
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
} else {
path = StringAfterPrefixIgnoreCase(path, base_path_relative.c_str());
}
if (path == nullptr || path.empty()) {
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
if (path == nullptr || path.empty())
return nullptr;
}
const char *slash = path.Find('/');
if (slash == nullptr)

View File

@@ -235,7 +235,7 @@ public:
w = Write();
}
size_t n = std::min(r.size, w.size);
const auto n = std::min(r.size, w.size);
std::move(r.data, r.data + n, w.data);
Append(n);

View File

@@ -25,7 +25,7 @@
#include <string.h>
PeakBuffer::~PeakBuffer()
PeakBuffer::~PeakBuffer() noexcept
{
delete normal_buffer;
delete peak_buffer;
@@ -57,7 +57,7 @@ PeakBuffer::Read() const noexcept
}
void
PeakBuffer::Consume(size_t length) noexcept
PeakBuffer::Consume(std::size_t length) noexcept
{
if (normal_buffer != nullptr && !normal_buffer->empty()) {
normal_buffer->Consume(length);
@@ -75,25 +75,25 @@ PeakBuffer::Consume(size_t length) noexcept
}
}
static size_t
AppendTo(DynamicFifoBuffer<uint8_t> &buffer,
static std::size_t
AppendTo(DynamicFifoBuffer<std::byte> &buffer,
const void *data, size_t length) noexcept
{
assert(data != nullptr);
assert(length > 0);
size_t total = 0;
std::size_t total = 0;
do {
const auto p = buffer.Write();
if (p.empty())
break;
const size_t nbytes = std::min(length, p.size);
const std::size_t nbytes = std::min(length, p.size);
memcpy(p.data, data, nbytes);
buffer.Append(nbytes);
data = (const uint8_t *)data + nbytes;
data = (const std::byte *)data + nbytes;
length -= nbytes;
total += nbytes;
} while (length > 0);
@@ -102,22 +102,22 @@ AppendTo(DynamicFifoBuffer<uint8_t> &buffer,
}
bool
PeakBuffer::Append(const void *data, size_t length)
PeakBuffer::Append(const void *data, std::size_t length)
{
if (length == 0)
return true;
if (peak_buffer != nullptr && !peak_buffer->empty()) {
size_t nbytes = AppendTo(*peak_buffer, data, length);
std::size_t nbytes = AppendTo(*peak_buffer, data, length);
return nbytes == length;
}
if (normal_buffer == nullptr)
normal_buffer = new DynamicFifoBuffer<uint8_t>(normal_size);
normal_buffer = new DynamicFifoBuffer<std::byte>(normal_size);
size_t nbytes = AppendTo(*normal_buffer, data, length);
std::size_t nbytes = AppendTo(*normal_buffer, data, length);
if (nbytes > 0) {
data = (const uint8_t *)data + nbytes;
data = (const std::byte *)data + nbytes;
length -= nbytes;
if (length == 0)
return true;
@@ -125,7 +125,7 @@ PeakBuffer::Append(const void *data, size_t length)
if (peak_buffer == nullptr) {
if (peak_size > 0)
peak_buffer = new DynamicFifoBuffer<uint8_t>(peak_size);
peak_buffer = new DynamicFifoBuffer<std::byte>(peak_size);
if (peak_buffer == nullptr)
return false;
}

View File

@@ -23,7 +23,6 @@
#include "Compiler.h"
#include <cstddef>
#include <cstdint>
template<typename T> struct WritableBuffer;
template<typename T> class DynamicFifoBuffer;
@@ -34,16 +33,16 @@ template<typename T> class DynamicFifoBuffer;
* kernel when it has been consumed.
*/
class PeakBuffer {
size_t normal_size, peak_size;
std::size_t normal_size, peak_size;
DynamicFifoBuffer<uint8_t> *normal_buffer, *peak_buffer;
DynamicFifoBuffer<std::byte> *normal_buffer, *peak_buffer;
public:
PeakBuffer(size_t _normal_size, size_t _peak_size)
PeakBuffer(std::size_t _normal_size, std::size_t _peak_size) noexcept
:normal_size(_normal_size), peak_size(_peak_size),
normal_buffer(nullptr), peak_buffer(nullptr) {}
PeakBuffer(PeakBuffer &&other)
PeakBuffer(PeakBuffer &&other) noexcept
:normal_size(other.normal_size), peak_size(other.peak_size),
normal_buffer(other.normal_buffer),
peak_buffer(other.peak_buffer) {
@@ -51,20 +50,24 @@ public:
other.peak_buffer = nullptr;
}
~PeakBuffer();
~PeakBuffer() noexcept;
PeakBuffer(const PeakBuffer &) = delete;
PeakBuffer &operator=(const PeakBuffer &) = delete;
std::size_t max_size() const noexcept {
return normal_size + peak_size;
}
gcc_pure
bool empty() const noexcept;
gcc_pure
WritableBuffer<void> Read() const noexcept;
void Consume(size_t length) noexcept;
void Consume(std::size_t length) noexcept;
bool Append(const void *data, size_t length);
bool Append(const void *data, std::size_t length);
};
#endif