release v0.22.4
-----BEGIN PGP SIGNATURE----- iQJEBAABCgAuFiEEA5IzWngIOJSkMBxDI26KWMbbRRIFAmAJqgAQHG1heEBtdXNp Y3BkLm9yZwAKCRAjbopYxttFEg2QEACJLeN2mk2RU7Iqxbh/ekwm6aTM8D6bx8RH Xys4l1YAFQ0cg7sKZwMqefedGJG2j8CORbihYIF6Z8EvFsAiI6I3LjElfXrmnAc0 Y9SnWHIR5vxlSQgyqPlJ0jl213uzoHHpel8PpEJbTtYONT+8f3fQEuLpO4/uWOIT S6mlX16vI0/Ydp//8UIazUUvjar1pPvBnSEZ0JZsZl8RTYlS/4SOfqpHnhhWnWpO 9RXlLP9Zo68rJzNhUPwRj7NkyVEkg74xpjHOWoyeTMNQ6tKMQn8b4jb/LcBfj6hk I7mof5oX0aS+GyYaehKi9c9Az7wUcBxnnaN02qlAaSutcuox7ce70fKMtiAXRN0o T9mFSJm1JKqHZb1dFvxqSqFjVr7eO9XCxHqaEqTbXCT+CL/6AJQZi5SVcX4gCY1P NSM3Jnydjr73WFLmEfjCkWLTdtiJhY/2Q/J6+vcILMb3W2y5FaSHZTOFbxVG4nMM spoQ27b7PoB9MbxLR3QJkYLa0WE3FrTORYgsH8Po7ZcCU9+JvqDSBnGXxx+Yv+JJ dKMI5bEAvPziodSHHQXgD4lhx744JuiLAJNtlSYJvev1s2Irf2TtMHdmGERHQZwH 5cr9sQLgyHCLvTBDGt1dVZq/Z0T/PCkweIa5cT6ZBAim1hs7g20g8ksyFK2ZPUbB vEGBNcuMIg== =CEEr -----END PGP SIGNATURE----- Merge tag 'v0.22.4' release v0.22.4
This commit is contained in:
commit
8279cafd6d
8
NEWS
8
NEWS
|
@ -2,7 +2,11 @@ ver 0.23 (not yet released)
|
|||
* protocol
|
||||
- new command "getvol"
|
||||
|
||||
ver 0.22.4 (not yet released)
|
||||
ver 0.22.4 (2021/01/21)
|
||||
* protocol
|
||||
- add command "binarylimit" to allow larger chunk sizes
|
||||
- fix "readpicture" on 32 bit machines
|
||||
- show duration and tags of songs in virtual playlist (CUE) folders
|
||||
* storage
|
||||
- curl: fix several WebDAV protocol bugs
|
||||
* decoder
|
||||
|
@ -11,6 +15,8 @@ ver 0.22.4 (not yet released)
|
|||
- ffmpeg: detect the output sample format
|
||||
* output
|
||||
- moveoutput: fix always_on and tag lost on move
|
||||
* Android
|
||||
- enable https:// support (via OpenSSL)
|
||||
|
||||
ver 0.22.3 (2020/11/06)
|
||||
* playlist
|
||||
|
|
|
@ -103,7 +103,7 @@ class AndroidNdkToolchain:
|
|||
llvm_bin = os.path.join(llvm_path, 'bin')
|
||||
self.cc = os.path.join(llvm_bin, 'clang')
|
||||
self.cxx = os.path.join(llvm_bin, 'clang++')
|
||||
common_flags += ' -target ' + llvm_triple + ' -integrated-as -gcc-toolchain ' + toolchain_path
|
||||
common_flags += ' -target ' + llvm_triple + ' -gcc-toolchain ' + toolchain_path
|
||||
|
||||
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
||||
|
||||
|
@ -172,6 +172,7 @@ thirdparty_libs = [
|
|||
wildmidi,
|
||||
gme,
|
||||
ffmpeg,
|
||||
openssl,
|
||||
curl,
|
||||
libexpat,
|
||||
libnfs,
|
||||
|
|
|
@ -130,7 +130,8 @@ audio_output
|
|||
|
||||
replaygain <off or album or track or auto>
|
||||
If specified, mpd will adjust the volume of songs played using ReplayGain
|
||||
tags (see http://www.replaygain.org/). Setting this to "album" will
|
||||
tags (see https://wiki.hydrogenaud.io/index.php?title=Replaygain).
|
||||
Setting this to "album" will
|
||||
adjust volume using the album's ReplayGain tags, while setting it to "track"
|
||||
will adjust it using the track ReplayGain tags. "auto" uses the track
|
||||
ReplayGain tags if random play is activated otherwise the album ReplayGain
|
||||
|
|
|
@ -372,7 +372,8 @@ input {
|
|||
# the argument "off", "album", "track" or "auto". "auto" is a special mode that
|
||||
# chooses between "track" and "album" depending on the current state of
|
||||
# random playback. If random playback is enabled then "track" mode is used.
|
||||
# See <http://www.replaygain.org> for more details about ReplayGain.
|
||||
# See <https://wiki.hydrogenaud.io/index.php?title=Replaygain> for
|
||||
# more details about ReplayGain.
|
||||
# This setting is off by default.
|
||||
#
|
||||
#replaygain "album"
|
||||
|
|
|
@ -69,11 +69,14 @@ that, the specified number of bytes of binary data follows, then a
|
|||
newline, and finally the ``OK`` line.
|
||||
|
||||
If the object to be transmitted is large, the server may choose a
|
||||
reasonable chunk size and transmit only a portion. 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.
|
||||
reasonable chunk size and transmit only a portion. The maximum chunk
|
||||
size can be changed by clients with the :ref:`binarylimit
|
||||
<command_binarylimit>` command.
|
||||
|
||||
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::
|
||||
|
||||
|
@ -739,7 +742,7 @@ Whenever possible, ids should be used.
|
|||
|
||||
.. _command_playlistfind:
|
||||
|
||||
:command:`playlistfind {TAG} {NEEDLE}`
|
||||
:command:`playlistfind {FILTER}`
|
||||
Finds songs in the queue with strict
|
||||
matching.
|
||||
|
||||
|
@ -760,7 +763,7 @@ Whenever possible, ids should be used.
|
|||
|
||||
.. _command_playlistsearch:
|
||||
|
||||
:command:`playlistsearch {TAG} {NEEDLE}`
|
||||
:command:`playlistsearch {FILTER}`
|
||||
Searches case-insensitively for partial matches in the
|
||||
queue.
|
||||
|
||||
|
@ -1367,6 +1370,17 @@ Connection settings
|
|||
:command:`ping`
|
||||
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`
|
||||
|
@ -1595,3 +1609,4 @@ client-to-client messages are local to the current partition.
|
|||
.. [#since_0_19] Since :program:`MPD` 0.19
|
||||
.. [#since_0_20] Since :program:`MPD` 0.20
|
||||
.. [#since_0_21] Since :program:`MPD` 0.21
|
||||
.. [#since_0_22_4] Since :program:`MPD` 0.22.4
|
||||
|
|
|
@ -167,7 +167,7 @@ Compiling for Android
|
|||
You need:
|
||||
|
||||
* Android SDK
|
||||
* Android NDK
|
||||
* `Android NDK r22 <https://developer.android.com/ndk/downloads>`_
|
||||
|
||||
Just like with the native build, unpack the :program:`MPD` source
|
||||
tarball and change into the directory. Then, instead of
|
||||
|
|
|
@ -10,11 +10,6 @@ class FfmpegProject(Project):
|
|||
self.configure_args = configure_args
|
||||
self.cppflags = cppflags
|
||||
|
||||
def _filter_cflags(self, flags):
|
||||
# FFmpeg expects the GNU as syntax
|
||||
flags = flags.replace(' -integrated-as ', ' -no-integrated-as ')
|
||||
return flags
|
||||
|
||||
def build(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
|
@ -36,8 +31,8 @@ class FfmpegProject(Project):
|
|||
'--cc=' + toolchain.cc,
|
||||
'--cxx=' + toolchain.cxx,
|
||||
'--nm=' + toolchain.nm,
|
||||
'--extra-cflags=' + self._filter_cflags(toolchain.cflags) + ' ' + toolchain.cppflags + ' ' + self.cppflags,
|
||||
'--extra-cxxflags=' + self._filter_cflags(toolchain.cxxflags) + ' ' + toolchain.cppflags + ' ' + self.cppflags,
|
||||
'--extra-cflags=' + toolchain.cflags + ' ' + toolchain.cppflags + ' ' + self.cppflags,
|
||||
'--extra-cxxflags=' + toolchain.cxxflags + ' ' + toolchain.cppflags + ' ' + self.cppflags,
|
||||
'--extra-ldflags=' + toolchain.ldflags,
|
||||
'--extra-libs=' + toolchain.libs,
|
||||
'--ar=' + toolchain.ar,
|
||||
|
|
|
@ -7,6 +7,7 @@ from build.meson import MesonProject
|
|||
from build.cmake import CmakeProject
|
||||
from build.autotools import AutotoolsProject
|
||||
from build.ffmpeg import FfmpegProject
|
||||
from build.openssl import OpenSSLProject
|
||||
from build.boost import BoostProject
|
||||
|
||||
libmpdclient = MesonProject(
|
||||
|
@ -376,9 +377,15 @@ ffmpeg = FfmpegProject(
|
|||
],
|
||||
)
|
||||
|
||||
openssl = OpenSSLProject(
|
||||
'https://www.openssl.org/source/openssl-3.0.0-alpha10.tar.gz',
|
||||
'b1699acf2148db31f12edf5ebfdf12a92bfd3f0e60538d169710408a3cd3b138',
|
||||
'include/openssl/ossl_typ.h',
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.73.0.tar.xz',
|
||||
'7c4c7ca4ea88abe00fea4740dcf81075c031b1d0bb23aff2d5efde20a3c2408a',
|
||||
'http://curl.haxx.se/download/curl-7.74.0.tar.xz',
|
||||
'999d5f2c403cf6e25d58319fdd596611e455dd195208746bc6e6d197a77e878b',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
@ -399,7 +406,7 @@ curl = AutotoolsProject(
|
|||
'--disable-netrc',
|
||||
'--disable-progress-meter',
|
||||
'--disable-alt-svc',
|
||||
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
|
||||
'--without-gnutls', '--without-nss', '--without-libssh2',
|
||||
],
|
||||
|
||||
patches='src/lib/curl/patches',
|
||||
|
@ -434,7 +441,7 @@ libnfs = AutotoolsProject(
|
|||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'https://dl.bintray.com/boostorg/release/1.74.0/source/boost_1_74_0.tar.bz2',
|
||||
'83bfc1507731a0906e387fc28b7ef5417d591429e51e788417fe9ff025e116b1',
|
||||
'https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.bz2',
|
||||
'953db31e016db7bb207f11432bef7df100516eeb746843fa0486a222e3fd49cb',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import subprocess
|
||||
|
||||
from build.makeproject import MakeProject
|
||||
|
||||
class OpenSSLProject(MakeProject):
|
||||
def __init__(self, url, md5, installed,
|
||||
**kwargs):
|
||||
MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs)
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
return MakeProject.get_make_args(self, toolchain) + [
|
||||
'CC=' + toolchain.cc,
|
||||
'CFLAGS=' + toolchain.cflags,
|
||||
'CPPFLAGS=' + toolchain.cppflags,
|
||||
'AR=' + toolchain.ar,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'build_libs',
|
||||
]
|
||||
|
||||
def build(self, toolchain):
|
||||
src = self.unpack(toolchain, out_of_tree=False)
|
||||
|
||||
# OpenSSL has a weird target architecture scheme with lots of
|
||||
# hard-coded architectures; this table translates between our
|
||||
# "toolchain_arch" (HOST_TRIPLET) and the OpenSSL target
|
||||
openssl_archs = {
|
||||
# not using "android-*" because those OpenSSL targets want
|
||||
# to know where the SDK is, but our own build scripts
|
||||
# prepared everything already to look like a regular Linux
|
||||
# build
|
||||
'arm-linux-androideabi': 'linux-generic32',
|
||||
'aarch64-linux-android': 'linux-aarch64',
|
||||
'i686-linux-android': 'linux-x86-clang',
|
||||
'x86_64-linux-android': 'linux-x86_64-clang',
|
||||
|
||||
# Kobo
|
||||
'arm-linux-gnueabihf': 'linux-generic32',
|
||||
|
||||
# Windows
|
||||
'i686-w64-mingw32': 'mingw',
|
||||
'x86_64-w64-mingw32': 'mingw64',
|
||||
}
|
||||
|
||||
openssl_arch = openssl_archs[toolchain.arch]
|
||||
|
||||
subprocess.check_call(['./Configure',
|
||||
'no-shared',
|
||||
'no-module', 'no-engine', 'no-static-engine',
|
||||
'no-async',
|
||||
'no-tests',
|
||||
'no-asm', # "asm" causes build failures on Windows
|
||||
openssl_arch,
|
||||
'--prefix=' + toolchain.install_prefix],
|
||||
cwd=src, env=toolchain.env)
|
||||
MakeProject.build(self, toolchain, src)
|
|
@ -20,7 +20,7 @@ class Project:
|
|||
self.base = base
|
||||
|
||||
if name is None or version is None:
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*)$', self.base)
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-alpha\d+)?)$', self.base)
|
||||
if name is None: name = m.group(1)
|
||||
if version is None: version = m.group(2)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
if (path == nullptr || path.empty())
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char *slash = path.Find('/');
|
||||
if (slash == nullptr)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue