diff --git a/NEWS b/NEWS index 0c183efb0..34591a0b9 100644 --- a/NEWS +++ b/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 diff --git a/android/build.py b/android/build.py index 5bee46304..eb6600111 100755 --- a/android/build.py +++ b/android/build.py @@ -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, diff --git a/doc/mpd.conf.5.rst b/doc/mpd.conf.5.rst index 6b364c513..90d515c16 100644 --- a/doc/mpd.conf.5.rst +++ b/doc/mpd.conf.5.rst @@ -130,7 +130,8 @@ audio_output replaygain 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 diff --git a/doc/mpdconf.example b/doc/mpdconf.example index a097fc652..6f36cf8b5 100644 --- a/doc/mpdconf.example +++ b/doc/mpdconf.example @@ -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 for more details about ReplayGain. +# See for +# more details about ReplayGain. # This setting is off by default. # #replaygain "album" diff --git a/doc/protocol.rst b/doc/protocol.rst index feced3d71..3618cb2d1 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -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. + +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 ` 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 diff --git a/doc/user.rst b/doc/user.rst index 694ce0cc5..e21b07d36 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -167,7 +167,7 @@ Compiling for Android You need: * Android SDK -* Android NDK +* `Android NDK r22 `_ Just like with the native build, unpack the :program:`MPD` source tarball and change into the directory. Then, instead of diff --git a/python/build/ffmpeg.py b/python/build/ffmpeg.py index 6c0753618..ae2ff6b00 100644 --- a/python/build/ffmpeg.py +++ b/python/build/ffmpeg.py @@ -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, diff --git a/python/build/libs.py b/python/build/libs.py index c9cfd1d25..517e892b3 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -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', ) diff --git a/python/build/openssl.py b/python/build/openssl.py new file mode 100644 index 000000000..a7350e6ac --- /dev/null +++ b/python/build/openssl.py @@ -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) diff --git a/python/build/project.py b/python/build/project.py index b78c89238..a0cfa60ba 100644 --- a/python/build/project.py +++ b/python/build/project.py @@ -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) diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx index 7e8729ad0..5ad300081 100644 --- a/src/SongPrint.cxx +++ b/src/SongPrint.cxx @@ -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 diff --git a/src/client/Client.hxx b/src/client/Client.hxx index d19d08760..deb4b8caa 100644 --- a/src/client/Client.hxx +++ b/src/client/Client.hxx @@ -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 { diff --git a/src/client/Response.cxx b/src/client/Response.cxx index 3900df75e..0c9145a50 100644 --- a/src/client/Response.cxx +++ b/src/client/Response.cxx @@ -59,7 +59,7 @@ Response::Format(const char *fmt, ...) noexcept bool Response::WriteBinary(ConstBuffer 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) && diff --git a/src/client/Response.hxx b/src/client/Response.hxx index 1bf0e7c1e..4c7e40e8a 100644 --- a/src/client/Response.hxx +++ b/src/client/Response.hxx @@ -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 diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 1fb125229..9a36b9bdb 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -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 }, diff --git a/src/command/ClientCommands.cxx b/src/command/ClientCommands.cxx index f0e3470be..01bd462f2 100644 --- a/src/command/ClientCommands.cxx +++ b/src/command/ClientCommands.cxx @@ -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) { diff --git a/src/command/ClientCommands.hxx b/src/command/ClientCommands.hxx index fc0dc42e1..de766e802 100644 --- a/src/command/ClientCommands.hxx +++ b/src/command/ClientCommands.hxx @@ -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); diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index cb9a264ee..ce750414c 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -43,6 +43,7 @@ #include "thread/Mutex.hxx" #include "Log.hxx" +#include #include #include /* 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(art_file_size - offset, + r.GetClient().binary_limit); + + std::unique_ptr buffer(new std::byte[buffer_size]); + + std::size_t read_size = 0; + if (buffer_size > 0) { std::unique_lock 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); diff --git a/src/command/Request.hxx b/src/command/Request.hxx index f6f0072a0..7a459bb0f 100644 --- a/src/command/Request.hxx +++ b/src/command/Request.hxx @@ -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); } diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx index 1b34655bd..1465922eb 100644 --- a/src/db/plugins/simple/Directory.cxx +++ b/src/db/plugins/simple/Directory.cxx @@ -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); } diff --git a/src/db/plugins/simple/ExportedSong.hxx b/src/db/plugins/simple/ExportedSong.hxx new file mode 100644 index 000000000..886be078b --- /dev/null +++ b/src/db/plugins/simple/ExportedSong.hxx @@ -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 diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx index f08938480..90dda7a01 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx @@ -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); diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.hxx b/src/db/plugins/simple/SimpleDatabasePlugin.hxx index a2245faa1..4fe68014e 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.hxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx @@ -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 light_song; + mutable Manual exported_song; #ifndef NDEBUG mutable unsigned borrowed_song_count; diff --git a/src/db/plugins/simple/Song.cxx b/src/db/plugins/simple/Song.cxx index 03ec19f50..8db47d03f 100644 --- a/src/db/plugins/simple/Song.cxx +++ b/src/db/plugins/simple/Song.cxx @@ -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; } diff --git a/src/db/plugins/simple/Song.hxx b/src/db/plugins/simple/Song.hxx index 8750a91b3..eb425dc5c 100644 --- a/src/db/plugins/simple/Song.hxx +++ b/src/db/plugins/simple/Song.hxx @@ -32,8 +32,8 @@ #include 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 +#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 diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx index 61a815554..6c3978a9d 100644 --- a/src/output/plugins/sles/SlesOutputPlugin.cxx +++ b/src/output/plugins/sles/SlesOutputPlugin.cxx @@ -33,6 +33,7 @@ #include #include +#include #include #include diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx index 8e39e6edb..d05b90a31 100644 --- a/src/storage/plugins/CurlStorage.cxx +++ b/src/storage/plugins/CurlStorage.cxx @@ -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 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) diff --git a/src/util/ForeignFifoBuffer.hxx b/src/util/ForeignFifoBuffer.hxx index d6912d3c2..65f6bce94 100644 --- a/src/util/ForeignFifoBuffer.hxx +++ b/src/util/ForeignFifoBuffer.hxx @@ -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); diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx index dcc6f92aa..6af9df266 100644 --- a/src/util/PeakBuffer.cxx +++ b/src/util/PeakBuffer.cxx @@ -25,7 +25,7 @@ #include -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 &buffer, +static std::size_t +AppendTo(DynamicFifoBuffer &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 &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(normal_size); + normal_buffer = new DynamicFifoBuffer(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(peak_size); + peak_buffer = new DynamicFifoBuffer(peak_size); if (peak_buffer == nullptr) return false; } diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx index 0684937b4..febf8bd73 100644 --- a/src/util/PeakBuffer.hxx +++ b/src/util/PeakBuffer.hxx @@ -23,7 +23,6 @@ #include "Compiler.h" #include -#include template struct WritableBuffer; template class DynamicFifoBuffer; @@ -34,16 +33,16 @@ template class DynamicFifoBuffer; * kernel when it has been consumed. */ class PeakBuffer { - size_t normal_size, peak_size; + std::size_t normal_size, peak_size; - DynamicFifoBuffer *normal_buffer, *peak_buffer; + DynamicFifoBuffer *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 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