diff -Nru mpd-0.23.6/android/AndroidManifest.xml mpd-0.23.8/android/AndroidManifest.xml --- mpd-0.23.6/android/AndroidManifest.xml 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/android/AndroidManifest.xml 2022-07-08 23:05:38.000000000 +0000 @@ -2,8 +2,8 @@ + android:versionCode="66" + android:versionName="0.23.7"> @@ -19,6 +19,7 @@ 1: + +SCRIPT_PATH=$(dirname $0) +BUILD_PATH="`pwd`" +TMP_PATH="$BUILD_PATH/gdb" +NDK_GDB_ARGS="--force" +ANDROID_NDK=$1 + +if [ ! -f $ANDROID_NDK/source.properties ];then + echo "usage: $0 ANDROID_NDK" + exit 1 +fi + +if [ ! -f $BUILD_PATH/libmpd.so ];then + echo "This script need to be executed from the android build directory" + exit 1 +fi + +rm -rf "$TMP_PATH" +mkdir -p "$TMP_PATH" + +ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml" +ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1` + +if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then + echo "Invalid manifest/ABI, did you try building first ?" + exit 1 +fi + +mkdir -p "$TMP_PATH"/jni +touch "$TMP_PATH"/jni/Android.mk +echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk + +DEST=obj/local/$ABI +mkdir -p "$TMP_PATH/$DEST" + +cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST" +cp "$ANDROID_MANIFEST" "$TMP_PATH" + +(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS) diff -Nru mpd-0.23.6/debian/changelog mpd-0.23.8/debian/changelog --- mpd-0.23.6/debian/changelog 2022-07-19 12:59:03.000000000 +0000 +++ mpd-0.23.8/debian/changelog 2022-07-19 09:00:02.000000000 +0000 @@ -1,8 +1,12 @@ -mpd (0.23.6-1build1) kinetic; urgency=medium +mpd (0.23.8-1) unstable; urgency=medium - * No-change rebuild against libavcodec59 + * New upstream version 0.23.8 + * Declare compliance with Debian Policy 4.6.1 + * Add myself as uploader + * Update d/copyright + * Fixed ftbs with fmtlib >= 9.0.0 (closes: #1014543) - -- Steve Langasek Tue, 19 Jul 2022 12:59:03 +0000 + -- Geoffroy Youri Berret Tue, 19 Jul 2022 11:00:02 +0200 mpd (0.23.6-1) unstable; urgency=medium diff -Nru mpd-0.23.6/debian/control mpd-0.23.8/debian/control --- mpd-0.23.6/debian/control 2022-07-19 12:59:03.000000000 +0000 +++ mpd-0.23.8/debian/control 2022-07-11 14:02:41.000000000 +0000 @@ -1,9 +1,8 @@ Source: mpd Section: sound Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: mpd maintainers -Uploaders: Florian Schlichting +Maintainer: mpd maintainers +Uploaders: Florian Schlichting , Geoffroy Youri Berret Build-Depends: debhelper-compat (= 13), meson (>= 0.56.0), libsndio-dev, @@ -68,7 +67,7 @@ python3-sphinx, # for tests: libgtest-dev -Standards-Version: 4.6.0 +Standards-Version: 4.6.1 Homepage: https://www.musicpd.org/ Vcs-Browser: https://salsa.debian.org/mpd-team/mpd Vcs-Git: https://salsa.debian.org/mpd-team/mpd.git diff -Nru mpd-0.23.6/debian/copyright mpd-0.23.8/debian/copyright --- mpd-0.23.6/debian/copyright 2022-04-17 15:17:11.000000000 +0000 +++ mpd-0.23.8/debian/copyright 2022-07-19 08:54:49.000000000 +0000 @@ -32,13 +32,14 @@ src/lib/gcrypt/* src/lib/systemd/* src/lib/yajl/* src/lib/zlib/* src/net/* src/output/plugins/sles/* src/system/* src/thread/* src/util/* test/net/* test/time/* test/util/* -Copyright: 2003-2021, Max Kellermann +Copyright: 2003-2022, Max Kellermann License: BSD-2-clause Files: src/event/CoarseTimerEvent.* src/event/FarTimerEvent.hxx src/event/FineTimerEvent.* src/event/PipeEvent.hxx src/event/TimerEvent.hxx src/event/TimerList.* src/event/TimerWheel.* - src/io/uring/* src/lib/dbus/* src/lib/nfs/Error.* src/lib/pcre/* + src/io/uring/* + src/lib/curl/Headers.hxx src/lib/dbus/* src/lib/nfs/Error.* src/lib/pcre/* src/net/HostParser.* src/net/Resolver.* src/system/KernelVersion.* src/time/* src/util/PrintException.* test/time/TestISO8601.cxx src/zeroconf/avahi/Client.* src/zeroconf/avahi/ConnectionListener.hxx @@ -87,7 +88,7 @@ 2006-2011, Decklin Foster 2011-2012, Alexander Wirt 2013-2021, Florian Schlichting - 2018-2022, Geoffroy Youri Berret + 2018-2022, Geoffroy Youri Berret License: GPL-2+ Files: debian/source_mpd.py diff -Nru mpd-0.23.6/doc/conf.py mpd-0.23.8/doc/conf.py --- mpd-0.23.6/doc/conf.py 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/doc/conf.py 2022-07-08 23:05:38.000000000 +0000 @@ -38,7 +38,7 @@ # built documents. # # The short X.Y version. -version = '0.23.6' +version = '0.23.8' # The full version, including alpha/beta/rc tags. #release = version + '~git' diff -Nru mpd-0.23.6/doc/protocol.rst mpd-0.23.8/doc/protocol.rst --- mpd-0.23.6/doc/protocol.rst 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/doc/protocol.rst 2022-07-08 23:05:38.000000000 +0000 @@ -545,6 +545,13 @@ Sets repeat state to ``STATE``, ``STATE`` should be 0 or 1. + If enabled, MPD keeps repeating the whole queue (:ref:`single mode + ` disabled) or the current song (:ref:`single mode + ` enabled). + + If :ref:`random mode ` is also enabled, the + playback order will be shuffled each time the queue gets repeated. + .. _command_setvol: :command:`setvol {VOL}` diff -Nru mpd-0.23.6/doc/user.rst mpd-0.23.8/doc/user.rst --- mpd-0.23.6/doc/user.rst 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/doc/user.rst 2022-07-08 23:05:38.000000000 +0000 @@ -301,7 +301,7 @@ ---------------------------- All neighbor plugins are disabled by default to avoid unwanted -overhead. To enable (and configure) a plugin, add a :code:`neighbor` +overhead. To enable (and configure) a plugin, add a :code:`neighbors` block to :file:`mpd.conf`: .. code-block:: none @@ -538,7 +538,7 @@ * - Name - Description - * - **plugin** + * - **name** - The name of the plugin * - **enabled yes|no** - Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled. @@ -1063,7 +1063,19 @@ Depending on the size of your music collection and the speed of the storage, this can take a while. -To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded. +To exclude a file from the update, create a file called +:file:`.mpdignore` in its parent directory. Each line of that file +may contain a list of shell wildcards. Matching files (or +directories) in the current directory and all subdirectories are +excluded. Example:: + + *.opus + 99* + +Subject to pattern matching is the file/directory name. It is (not +yet) possible to match nested path names, e.g. something like +``foo/*.flac`` is not possible. + Mounting other storages into the music directory ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff -Nru mpd-0.23.6/.github/FUNDING.yml mpd-0.23.8/.github/FUNDING.yml --- mpd-0.23.6/.github/FUNDING.yml 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/.github/FUNDING.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: MaxK -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff -Nru mpd-0.23.6/.github/ISSUE_TEMPLATE/bug_report.md mpd-0.23.8/.github/ISSUE_TEMPLATE/bug_report.md --- mpd-0.23.6/.github/ISSUE_TEMPLATE/bug_report.md 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/.github/ISSUE_TEMPLATE/bug_report.md 2022-07-08 23:05:38.000000000 +0000 @@ -18,5 +18,9 @@ +## Configuration + + + ## Log diff -Nru mpd-0.23.6/.github/ISSUE_TEMPLATE/question.md mpd-0.23.8/.github/ISSUE_TEMPLATE/question.md --- mpd-0.23.6/.github/ISSUE_TEMPLATE/question.md 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/.github/ISSUE_TEMPLATE/question.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ ---- -name: Question -about: Ask a question about MPD ---- - - -## Question diff -Nru mpd-0.23.6/.github/workflows/build.yml mpd-0.23.8/.github/workflows/build.yml --- mpd-0.23.6/.github/workflows/build.yml 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/.github/workflows/build.yml 2022-07-08 23:05:38.000000000 +0000 @@ -41,7 +41,8 @@ key: linux - name: Install dependencies run: | - sudo apt install -y --no-install-recommends \ + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ g++-10 libfmt-dev libboost-dev \ libgtest-dev \ libpcre2-dev \ @@ -75,17 +76,28 @@ - name: Full Build uses: BSFishy/meson-build@v1.0.3 with: + action: build + directory: output/full + setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled + options: --verbose + meson-version: 0.56.0 + + - name: Unit Tests + uses: BSFishy/meson-build@v1.0.3 + with: action: test directory: output/full setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled + options: --verbose meson-version: 0.56.0 - name: Mini Build uses: BSFishy/meson-build@v1.0.3 with: - action: test + action: build directory: output/mini setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false + options: --verbose meson-version: 0.56.0 build-macos: @@ -124,10 +136,20 @@ wavpack \ libmpdclient - - name: Meson Build + - name: Build + uses: BSFishy/meson-build@v1.0.3 + with: + action: build + directory: output + setup-options: -Ddocumentation=disabled -Dtest=true + options: --verbose + meson-version: 0.56.0 + + - name: Unit Tests uses: BSFishy/meson-build@v1.0.3 with: action: test directory: output setup-options: -Ddocumentation=disabled -Dtest=true + options: --verbose meson-version: 0.56.0 diff -Nru mpd-0.23.6/meson.build mpd-0.23.8/meson.build --- mpd-0.23.6/meson.build 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/meson.build 2022-07-08 23:05:38.000000000 +0000 @@ -1,7 +1,7 @@ project( 'mpd', ['c', 'cpp'], - version: '0.23.6', + version: '0.23.8', meson_version: '>= 0.56.0', default_options: [ 'c_std=c11', @@ -73,6 +73,9 @@ # clang specific warning options: '-Wunreachable-code-aggressive', '-Wused-but-marked-unused', + + # suppress bogus GCC12 warnings in libfmt headers + '-Wno-stringop-overflow', ] test_global_cxxflags = test_global_common_flags + [ diff -Nru mpd-0.23.6/NEWS mpd-0.23.8/NEWS --- mpd-0.23.6/NEWS 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/NEWS 2022-07-08 23:05:38.000000000 +0000 @@ -1,3 +1,36 @@ +ver 0.23.8 (2022/07/09) +* storage + - curl: fix crash if web server does not understand WebDAV +* input + - cdio_paranoia: fix crash if no drive was found + - cdio_paranoia: faster cancellation + - cdio_paranoia: don't scan for replay gain tags + - pipewire: fix playback of very short tracks + - pipewire: drop all buffers before manual song change + - pipewire: fix stuttering after manual song change + - snapcast: fix busy loop while paused + - snapcast: fix stuttering after resuming playback +* mixer + - better error messages + - alsa: fix setting volume before playback starts + - pipewire: fix crash bug + - pipewire: fix volume change events with PipeWire 0.3.53 + - pipewire: don't force initial volume=100% +* support libfmt 9 + +ver 0.23.7 (2022/05/09) +* database + - upnp: support pupnp 1.14 +* decoder + - ffmpeg: fix HLS seeking + - opus: fix missing song length on high-latency files +* output + - shout: require at least libshout 2.4.0 +* mixer + - pipewire: fix volume restore + - software: update volume of disabled outputs +* support libiconv + ver 0.23.6 (2022/03/14) * protocol - support filename "cover.webp" for "albumart" command diff -Nru mpd-0.23.6/python/build/libs.py mpd-0.23.8/python/build/libs.py --- mpd-0.23.6/python/build/libs.py 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/python/build/libs.py 2022-07-08 23:05:38.000000000 +0000 @@ -55,8 +55,8 @@ ) zlib = ZlibProject( - 'http://zlib.net/zlib-1.2.11.tar.xz', - '4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066', + 'http://zlib.net/zlib-1.2.12.tar.xz', + '7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18', 'lib/libz.a', ) @@ -151,8 +151,8 @@ ) ffmpeg = FfmpegProject( - 'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz', - '51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298', + 'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz', + 'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b', 'lib/libavcodec.a', [ '--disable-shared', '--enable-static', @@ -177,6 +177,8 @@ '--disable-filters', '--disable-v4l2_m2m', + '--disable-vulkan', + '--disable-parser=bmp', '--disable-parser=cavsvideo', '--disable-parser=dvbsub', @@ -380,14 +382,14 @@ ) openssl = OpenSSLProject( - 'https://www.openssl.org/source/openssl-3.0.1.tar.gz', - 'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1', + 'https://www.openssl.org/source/openssl-3.0.5.tar.gz', + 'aa7d8d9bef71ad6525c55ba11e5f4397889ce49c2c9349dcea6d3e4f0b024a7a', 'include/openssl/ossl_typ.h', ) curl = CmakeProject( - 'https://curl.se/download/curl-7.82.0.tar.xz', - '0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c', + 'https://curl.se/download/curl-7.84.0.tar.xz', + '2d118b43f547bfe5bae806d8d47b4e596ea5b25a6c1f080aef49fbcd817c5db8', 'lib/libcurl.a', [ '-DBUILD_CURL_EXE=OFF', @@ -444,7 +446,7 @@ ) boost = BoostProject( - 'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2', - '8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc', + 'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2', + '475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39', 'include/boost/version.hpp', ) diff -Nru mpd-0.23.6/src/apple/Throw.cxx mpd-0.23.8/src/apple/Throw.cxx --- mpd-0.23.6/src/apple/Throw.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/apple/Throw.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -31,6 +31,7 @@ #include "ErrorRef.hxx" #include "StringRef.hxx" +#include #include namespace Apple { @@ -57,8 +58,8 @@ const Apple::StringRef cfstr(cferr.CopyDescription()); char msg[1024]; - strcpy(msg, _msg); - size_t length = strlen(msg); + std::strcpy(msg, _msg); + size_t length = std::strlen(msg); cfstr.GetCString(msg + length, sizeof(msg) - length); throw std::runtime_error(msg); diff -Nru mpd-0.23.6/src/client/Response.hxx mpd-0.23.8/src/client/Response.hxx --- mpd-0.23.6/src/client/Response.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/client/Response.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -82,7 +82,10 @@ template bool Fmt(const S &format_str, Args&&... args) noexcept { -#if FMT_VERSION >= 70000 +#if FMT_VERSION >= 90000 + return VFmt(format_str, + fmt::make_format_args(args...)); +#elif FMT_VERSION >= 70000 return VFmt(fmt::to_string_view(format_str), fmt::make_args_checked(format_str, args...)); @@ -109,7 +112,10 @@ template void FmtError(enum ack code, const S &format_str, Args&&... args) noexcept { -#if FMT_VERSION >= 70000 +#if FMT_VERSION >= 90000 + return VFmtError(code, format_str, + fmt::make_format_args(args...)); +#elif FMT_VERSION >= 70000 return VFmtError(code, fmt::to_string_view(format_str), fmt::make_args_checked(format_str, args...)); diff -Nru mpd-0.23.6/src/command/OtherCommands.cxx mpd-0.23.8/src/command/OtherCommands.cxx --- mpd-0.23.6/src/command/OtherCommands.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/command/OtherCommands.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -333,15 +333,11 @@ } CommandResult -handle_setvol(Client &client, Request args, Response &r) +handle_setvol(Client &client, Request args, Response &) { unsigned level = args.ParseUnsigned(0, 100); - if (!volume_level_change(client.GetPartition().outputs, level)) { - r.Error(ACK_ERROR_SYSTEM, "problems setting volume"); - return CommandResult::ERROR; - } - + volume_level_change(client.GetPartition().outputs, level); return CommandResult::OK; } @@ -364,11 +360,8 @@ else if (new_volume > 100) new_volume = 100; - if (new_volume != old_volume && - !volume_level_change(outputs, new_volume)) { - r.Error(ACK_ERROR_SYSTEM, "problems setting volume"); - return CommandResult::ERROR; - } + if (new_volume != old_volume) + volume_level_change(outputs, new_volume); return CommandResult::OK; } diff -Nru mpd-0.23.6/src/decoder/Control.hxx mpd-0.23.8/src/decoder/Control.hxx --- mpd-0.23.6/src/decoder/Control.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/decoder/Control.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -257,6 +257,12 @@ return HasFailed(); } + [[gnu::pure]] + bool LockIsReplayGainEnabled() const noexcept { + const std::scoped_lock protect(mutex); + return replay_gain_mode != ReplayGainMode::OFF; + } + /** * Transition this obejct from DecoderState::START to * DecoderState::DECODE. diff -Nru mpd-0.23.6/src/decoder/plugins/FfmpegDecoderPlugin.cxx mpd-0.23.8/src/decoder/plugins/FfmpegDecoderPlugin.cxx --- mpd-0.23.6/src/decoder/plugins/FfmpegDecoderPlugin.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/decoder/plugins/FfmpegDecoderPlugin.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -384,7 +384,8 @@ FfmpegParseMetaData(const AVStream &stream, ReplayGainInfo &rg, MixRampInfo &mr) { - FfmpegParseMetaData(*stream.metadata, rg, mr); + if (stream.metadata != nullptr) + FfmpegParseMetaData(*stream.metadata, rg, mr); } static void @@ -393,7 +394,9 @@ { assert(audio_stream >= 0); - FfmpegParseMetaData(*format_context.metadata, rg, mr); + if (format_context.metadata != nullptr) + FfmpegParseMetaData(*format_context.metadata, rg, mr); + FfmpegParseMetaData(*format_context.streams[audio_stream], rg, mr); } @@ -468,7 +471,7 @@ IsSeekable(const AVFormatContext &format_context) noexcept { #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100) - return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0; + return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) == 0; #else (void)format_context; return false; @@ -530,9 +533,8 @@ : FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q); client.Ready(audio_format, - input - ? input->IsSeekable() - : IsSeekable(format_context), + (input ? input->IsSeekable() : false) + || IsSeekable(format_context), total_time); FfmpegParseMetaData(client, format_context, audio_stream); diff -Nru mpd-0.23.6/src/decoder/Thread.cxx mpd-0.23.8/src/decoder/Thread.cxx --- mpd-0.23.6/src/decoder/Thread.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/decoder/Thread.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -36,6 +36,7 @@ #include "util/RuntimeError.hxx" #include "util/Domain.hxx" #include "util/ScopeExit.hxx" +#include "util/StringCompare.hxx" #include "thread/Name.hxx" #include "tag/ApeReplayGain.hxx" #include "Log.hxx" @@ -261,12 +262,16 @@ static void MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is) { - { - const std::scoped_lock protect(bridge.dc.mutex); - if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF) - /* ReplayGain is disabled */ - return; - } + if (!bridge.dc.LockIsReplayGainEnabled()) + /* ReplayGain is disabled */ + return; + + if (is.HasMimeType() && + StringStartsWith(is.GetMimeType(), "audio/x-mpd-")) + /* skip for (virtual) files (e.g. from the + cdio_paranoia input plugin) which cannot possibly + contain tags */ + return; LoadReplayGain(bridge, is); } diff -Nru mpd-0.23.6/src/encoder/meson.build mpd-0.23.8/src/encoder/meson.build --- mpd-0.23.6/src/encoder/meson.build 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/encoder/meson.build 2022-07-08 23:05:38.000000000 +0000 @@ -3,6 +3,23 @@ encoder_features.set('ENABLE_ENCODER', need_encoder) if not need_encoder + if need_wave_encoder + # Special case for the Snapcast output plugin which only needs the + # PCM wave encoder encoder plugin + encoder_glue = static_library( + 'encoder_glue', + 'plugins/WaveEncoderPlugin.cxx', + include_directories: inc, + ) + + encoder_glue_dep = declare_dependency( + link_with: encoder_glue, + ) + + configure_file(output: 'Features.h', configuration: encoder_features) + subdir_done() + endif + encoder_glue_dep = dependency('', required: false) configure_file(output: 'Features.h', configuration: encoder_features) subdir_done() diff -Nru mpd-0.23.6/src/encoder/plugins/meson.build mpd-0.23.8/src/encoder/plugins/meson.build --- mpd-0.23.6/src/encoder/plugins/meson.build 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/encoder/plugins/meson.build 2022-07-08 23:05:38.000000000 +0000 @@ -35,7 +35,7 @@ endif encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder')) -if get_option('wave_encoder') +if get_option('wave_encoder') or need_wave_encoder encoder_plugins_sources += 'WaveEncoderPlugin.cxx' endif diff -Nru mpd-0.23.6/src/input/plugins/CdioParanoiaInputPlugin.cxx mpd-0.23.8/src/input/plugins/CdioParanoiaInputPlugin.cxx --- mpd-0.23.6/src/input/plugins/CdioParanoiaInputPlugin.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/CdioParanoiaInputPlugin.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -30,10 +30,12 @@ #include "util/RuntimeError.hxx" #include "util/Domain.hxx" #include "util/ByteOrder.hxx" +#include "util/ScopeExit.hxx" #include "fs/AllocatedPath.hxx" #include "Log.hxx" #include "config/Block.hxx" +#include #include #include @@ -48,21 +50,19 @@ CdIo_t *const cdio; CdromParanoia para; - const lsn_t lsn_from, lsn_to; - int lsn_relofs; + const lsn_t lsn_from; char buffer[CDIO_CD_FRAMESIZE_RAW]; - int buffer_lsn; + lsn_t buffer_lsn; public: CdioParanoiaInputStream(const char *_uri, Mutex &_mutex, cdrom_drive_t *_drv, CdIo_t *_cdio, bool reverse_endian, - lsn_t _lsn_from, lsn_t _lsn_to) + lsn_t _lsn_from, lsn_t lsn_to) :InputStream(_uri, _mutex), drv(_drv), cdio(_cdio), para(drv), - lsn_from(_lsn_from), lsn_to(_lsn_to), - lsn_relofs(0), + lsn_from(_lsn_from), buffer_lsn(-1) { /* Set reading mode for full paranoia, but allow @@ -173,9 +173,12 @@ if (devices == nullptr) return nullptr; - AllocatedPath path = AllocatedPath::FromFS(devices[0]); - cdio_free_device_list(devices); - return path; + AtScopeExit(devices) { cdio_free_device_list(devices); }; + + if (devices[0] == nullptr) + return nullptr; + + return AllocatedPath::FromFS(devices[0]); } static InputStreamPtr @@ -271,73 +274,62 @@ return; /* calculate current LSN */ - lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW; - offset = new_offset; + const lsn_t lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW; - { + if (lsn_relofs != buffer_lsn) { const ScopeUnlock unlock(mutex); para.Seek(lsn_from + lsn_relofs); } + + offset = new_offset; } size_t CdioParanoiaInputStream::Read(std::unique_lock &, void *ptr, size_t length) { - size_t nbytes = 0; - char *wptr = (char *) ptr; + /* end of track ? */ + if (IsEOF()) + return 0; - while (length > 0) { - /* end of track ? */ - if (lsn_from + lsn_relofs > lsn_to) - break; - - //current sector was changed ? - const int16_t *rbuf; - if (lsn_relofs != buffer_lsn) { - const ScopeUnlock unlock(mutex); - - try { - rbuf = para.Read().data; - } catch (...) { - char *s_err = cdio_cddap_errors(drv); - if (s_err) { - FmtError(cdio_domain, - "paranoia_read: {}", s_err); - cdio_cddap_free_messages(s_err); - } + //current sector was changed ? + const int16_t *rbuf; - throw; + const lsn_t lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; + const std::size_t diff = offset % CDIO_CD_FRAMESIZE_RAW; + + if (lsn_relofs != buffer_lsn) { + const ScopeUnlock unlock(mutex); + + try { + rbuf = para.Read().data; + } catch (...) { + char *s_err = cdio_cddap_errors(drv); + if (s_err) { + FmtError(cdio_domain, + "paranoia_read: {}", s_err); + cdio_cddap_free_messages(s_err); } - //store current buffer - memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); - buffer_lsn = lsn_relofs; - } else { - //use cached sector - rbuf = (const int16_t *)buffer; + throw; } - //correct offset - const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW; + //store current buffer + memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW); + buffer_lsn = lsn_relofs; + } else { + //use cached sector + rbuf = (const int16_t *)buffer; + } - assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW); + const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer + const std::size_t nbytes = std::min(length, maxwrite); - const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer - const size_t len = (length < maxwrite? length : maxwrite); + //skip diff bytes from this lsn + memcpy(ptr, ((const char *)rbuf) + diff, nbytes); - //skip diff bytes from this lsn - memcpy(wptr, ((const char *)rbuf) + diff, len); - //update pointer - wptr += len; - nbytes += len; - - //update offset - offset += len; - lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW; - //update length - length -= len; - } + //update offset + offset += nbytes; return nbytes; } @@ -345,7 +337,7 @@ bool CdioParanoiaInputStream::IsEOF() const noexcept { - return lsn_from + lsn_relofs > lsn_to; + return offset >= size; } static constexpr const char *cdio_paranoia_prefixes[] = { diff -Nru mpd-0.23.6/src/input/plugins/CurlInputPlugin.cxx mpd-0.23.8/src/input/plugins/CurlInputPlugin.cxx --- mpd-0.23.6/src/input/plugins/CurlInputPlugin.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/CurlInputPlugin.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -82,7 +82,7 @@ public: template CurlInputStream(EventLoop &event_loop, const char *_url, - const std::multimap &headers, + const Curl::Headers &headers, I &&_icy, Mutex &_mutex); @@ -92,7 +92,7 @@ CurlInputStream &operator=(const CurlInputStream &) = delete; static InputStreamPtr Open(const char *url, - const std::multimap &headers, + const Curl::Headers &headers, Mutex &mutex); private: @@ -131,8 +131,7 @@ void SeekInternal(offset_type new_offset); /* virtual methods from CurlResponseHandler */ - void OnHeaders(unsigned status, - std::multimap &&headers) override; + void OnHeaders(unsigned status, Curl::Headers &&headers) override; void OnData(ConstBuffer data) override; void OnEnd() override; void OnError(std::exception_ptr e) noexcept override; @@ -227,7 +226,7 @@ void CurlInputStream::OnHeaders(unsigned status, - std::multimap &&headers) + Curl::Headers &&headers) { assert(GetEventLoop().IsInside()); assert(!postponed_exception); @@ -391,7 +390,7 @@ template inline CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url, - const std::multimap &headers, + const Curl::Headers &headers, I &&_icy, Mutex &_mutex) :AsyncInputStream(event_loop, _url, _mutex, @@ -491,7 +490,7 @@ inline InputStreamPtr CurlInputStream::Open(const char *url, - const std::multimap &headers, + const Curl::Headers &headers, Mutex &mutex) { auto icy = std::make_shared(); @@ -510,8 +509,7 @@ } InputStreamPtr -OpenCurlInputStream(const char *uri, - const std::multimap &headers, +OpenCurlInputStream(const char *uri, const Curl::Headers &headers, Mutex &mutex) { return CurlInputStream::Open(uri, headers, mutex); diff -Nru mpd-0.23.6/src/input/plugins/CurlInputPlugin.hxx mpd-0.23.8/src/input/plugins/CurlInputPlugin.hxx --- mpd-0.23.6/src/input/plugins/CurlInputPlugin.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/CurlInputPlugin.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -20,12 +20,10 @@ #ifndef MPD_INPUT_CURL_HXX #define MPD_INPUT_CURL_HXX +#include "lib/curl/Headers.hxx" #include "input/Ptr.hxx" #include "thread/Mutex.hxx" -#include -#include - extern const struct InputPlugin input_plugin_curl; /** @@ -36,8 +34,7 @@ * Throws on error. */ InputStreamPtr -OpenCurlInputStream(const char *uri, - const std::multimap &headers, +OpenCurlInputStream(const char *uri, const Curl::Headers &headers, Mutex &mutex); #endif diff -Nru mpd-0.23.6/src/input/plugins/QobuzClient.cxx mpd-0.23.8/src/input/plugins/QobuzClient.cxx --- mpd-0.23.6/src/input/plugins/QobuzClient.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzClient.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -164,7 +164,7 @@ std::string QobuzClient::MakeUrl(const char *object, const char *method, - const std::multimap &query) const noexcept + const Curl::Headers &query) const noexcept { assert(!query.empty()); @@ -183,7 +183,7 @@ std::string QobuzClient::MakeSignedUrl(const char *object, const char *method, - const std::multimap &query) const noexcept + const Curl::Headers &query) const noexcept { assert(!query.empty()); diff -Nru mpd-0.23.6/src/input/plugins/QobuzClient.hxx mpd-0.23.8/src/input/plugins/QobuzClient.hxx --- mpd-0.23.6/src/input/plugins/QobuzClient.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzClient.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -23,12 +23,12 @@ #include "QobuzSession.hxx" #include "QobuzLoginRequest.hxx" #include "lib/curl/Init.hxx" +#include "lib/curl/Headers.hxx" #include "thread/Mutex.hxx" #include "event/DeferEvent.hxx" #include "util/IntrusiveList.hxx" #include -#include #include class QobuzSessionHandler @@ -94,10 +94,10 @@ QobuzSession GetSession() const; std::string MakeUrl(const char *object, const char *method, - const std::multimap &query) const noexcept; + const Curl::Headers &query) const noexcept; std::string MakeSignedUrl(const char *object, const char *method, - const std::multimap &query) const noexcept; + const Curl::Headers &query) const noexcept; private: void StartLogin(); diff -Nru mpd-0.23.6/src/input/plugins/QobuzErrorParser.cxx mpd-0.23.8/src/input/plugins/QobuzErrorParser.cxx --- mpd-0.23.6/src/input/plugins/QobuzErrorParser.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzErrorParser.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -38,7 +38,7 @@ }; QobuzErrorParser::QobuzErrorParser(unsigned _status, - const std::multimap &headers) + const Curl::Headers &headers) :YajlResponseParser(&qobuz_error_parser_callbacks, nullptr, this), status(_status) { diff -Nru mpd-0.23.6/src/input/plugins/QobuzErrorParser.hxx mpd-0.23.8/src/input/plugins/QobuzErrorParser.hxx --- mpd-0.23.6/src/input/plugins/QobuzErrorParser.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzErrorParser.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -20,11 +20,9 @@ #ifndef QOBUZ_ERROR_PARSER_HXX #define QOBUZ_ERROR_PARSER_HXX +#include "lib/curl/Headers.hxx" #include "lib/yajl/ResponseParser.hxx" -#include -#include - template struct ConstBuffer; struct StringView; @@ -46,8 +44,7 @@ * May throw if there is a formal error in the response * headers. */ - QobuzErrorParser(unsigned status, - const std::multimap &headers); + QobuzErrorParser(unsigned status, const Curl::Headers &headers); protected: /* virtual methods from CurlResponseParser */ diff -Nru mpd-0.23.6/src/input/plugins/QobuzLoginRequest.cxx mpd-0.23.8/src/input/plugins/QobuzLoginRequest.cxx --- mpd-0.23.6/src/input/plugins/QobuzLoginRequest.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzLoginRequest.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -77,7 +77,7 @@ return std::move(session); } -static std::multimap +static Curl::Headers MakeLoginForm(const char *app_id, const char *username, const char *email, const char *password, @@ -85,7 +85,7 @@ { assert(username != nullptr || email != nullptr); - std::multimap form{ + Curl::Headers form{ {"app_id", app_id}, {"password", password}, {"device_manufacturer_id", device_manufacturer_id}, @@ -134,8 +134,7 @@ } std::unique_ptr -QobuzLoginRequest::MakeParser(unsigned status, - std::multimap &&headers) +QobuzLoginRequest::MakeParser(unsigned status, Curl::Headers &&headers) { if (status != 200) return std::make_unique(status, headers); diff -Nru mpd-0.23.6/src/input/plugins/QobuzLoginRequest.hxx mpd-0.23.8/src/input/plugins/QobuzLoginRequest.hxx --- mpd-0.23.6/src/input/plugins/QobuzLoginRequest.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzLoginRequest.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -56,7 +56,7 @@ private: /* virtual methods from DelegateCurlResponseHandler */ std::unique_ptr MakeParser(unsigned status, - std::multimap &&headers) override; + Curl::Headers &&headers) override; void FinishParser(std::unique_ptr p) override; /* virtual methods from CurlResponseHandler */ diff -Nru mpd-0.23.6/src/input/plugins/QobuzTagScanner.cxx mpd-0.23.8/src/input/plugins/QobuzTagScanner.cxx --- mpd-0.23.6/src/input/plugins/QobuzTagScanner.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzTagScanner.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -99,8 +99,7 @@ } std::unique_ptr -QobuzTagScanner::MakeParser(unsigned status, - std::multimap &&headers) +QobuzTagScanner::MakeParser(unsigned status, Curl::Headers &&headers) { if (status != 200) return std::make_unique(status, headers); diff -Nru mpd-0.23.6/src/input/plugins/QobuzTagScanner.hxx mpd-0.23.8/src/input/plugins/QobuzTagScanner.hxx --- mpd-0.23.6/src/input/plugins/QobuzTagScanner.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzTagScanner.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -49,7 +49,7 @@ private: /* virtual methods from DelegateCurlResponseHandler */ std::unique_ptr MakeParser(unsigned status, - std::multimap &&headers) override; + Curl::Headers &&headers) override; void FinishParser(std::unique_ptr p) override; /* virtual methods from CurlResponseHandler */ diff -Nru mpd-0.23.6/src/input/plugins/QobuzTrackRequest.cxx mpd-0.23.8/src/input/plugins/QobuzTrackRequest.cxx --- mpd-0.23.6/src/input/plugins/QobuzTrackRequest.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzTrackRequest.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -93,7 +93,7 @@ std::unique_ptr QobuzTrackRequest::MakeParser(unsigned status, - std::multimap &&headers) + Curl::Headers &&headers) { if (status != 200) return std::make_unique(status, headers); diff -Nru mpd-0.23.6/src/input/plugins/QobuzTrackRequest.hxx mpd-0.23.8/src/input/plugins/QobuzTrackRequest.hxx --- mpd-0.23.6/src/input/plugins/QobuzTrackRequest.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/input/plugins/QobuzTrackRequest.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -56,7 +56,7 @@ private: /* virtual methods from DelegateCurlResponseHandler */ std::unique_ptr MakeParser(unsigned status, - std::multimap &&headers) override; + Curl::Headers &&headers) override; void FinishParser(std::unique_ptr p) override; /* virtual methods from CurlResponseHandler */ diff -Nru mpd-0.23.6/src/lib/curl/Adapter.cxx mpd-0.23.8/src/lib/curl/Adapter.cxx --- mpd-0.23.6/src/lib/curl/Adapter.cxx 1970-01-01 00:00:00.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Adapter.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -0,0 +1,204 @@ +/* + * Copyright 2008-2021 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Adapter.hxx" +#include "Easy.hxx" +#include "Handler.hxx" +#include "util/CharUtil.hxx" +#include "util/RuntimeError.hxx" +#include "util/StringStrip.hxx" +#include "util/StringView.hxx" + +#include +#include + +void +CurlResponseHandlerAdapter::Install(CurlEasy &easy) +{ + assert(state == State::UNINITIALISED); + + error_buffer[0] = 0; + easy.SetErrorBuffer(error_buffer); + + easy.SetHeaderFunction(_HeaderFunction, this); + easy.SetWriteFunction(WriteFunction, this); + + curl = easy.Get(); + + state = State::HEADERS; +} + +void +CurlResponseHandlerAdapter::FinishHeaders() +{ + assert(state >= State::HEADERS); + + if (state != State::HEADERS) + return; + + state = State::BODY; + + long status = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + + handler.OnHeaders(status, std::move(headers)); +} + +void +CurlResponseHandlerAdapter::FinishBody() +{ + FinishHeaders(); + + if (state != State::BODY) + return; + + state = State::CLOSED; + handler.OnEnd(); +} + +void +CurlResponseHandlerAdapter::Done(CURLcode result) noexcept +{ + if (postponed_error) { + state = State::CLOSED; + handler.OnError(std::move(postponed_error)); + return; + } + + try { + if (result != CURLE_OK) { + StripRight(error_buffer); + const char *msg = error_buffer; + if (*msg == 0) + msg = curl_easy_strerror(result); + throw FormatRuntimeError("CURL failed: %s", msg); + } + + FinishBody(); + } catch (...) { + state = State::CLOSED; + handler.OnError(std::current_exception()); + } +} + +[[gnu::pure]] +static bool +IsResponseBoundaryHeader(StringView s) noexcept +{ + return s.size > 5 && (s.StartsWith("HTTP/") || + /* the proprietary "ICY 200 OK" is + emitted by Shoutcast */ + s.StartsWith("ICY 2")); +} + +inline void +CurlResponseHandlerAdapter::HeaderFunction(StringView s) noexcept +{ + if (state > State::HEADERS) + return; + + if (IsResponseBoundaryHeader(s)) { + /* this is the boundary to a new response, for example + after a redirect */ + headers.clear(); + return; + } + + const char *header = s.data; + const char *end = StripRight(header, header + s.size); + + const char *value = s.Find(':'); + if (value == nullptr) + return; + + std::string name(header, value); + std::transform(name.begin(), name.end(), name.begin(), + static_cast(ToLowerASCII)); + + /* skip the colon */ + + ++value; + + /* strip the value */ + + value = StripLeft(value, end); + end = StripRight(value, end); + + headers.emplace(std::move(name), std::string(value, end)); +} + +std::size_t +CurlResponseHandlerAdapter::_HeaderFunction(char *ptr, std::size_t size, + std::size_t nmemb, + void *stream) noexcept +{ + CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream; + + size *= nmemb; + + c.HeaderFunction({ptr, size}); + return size; +} + +inline std::size_t +CurlResponseHandlerAdapter::DataReceived(const void *ptr, + std::size_t received_size) noexcept +{ + assert(received_size > 0); + + try { + FinishHeaders(); + handler.OnData({ptr, received_size}); + return received_size; + } catch (CurlResponseHandler::Pause) { + return CURL_WRITEFUNC_PAUSE; + } catch (...) { + /* from inside this libCURL callback function, we + can't do much, so we remember the exception to be + handled later by Done(), and return 0, causing the + response to be aborted with CURLE_WRITE_ERROR */ + postponed_error = std::current_exception(); + return 0; + } + +} + +std::size_t +CurlResponseHandlerAdapter::WriteFunction(char *ptr, std::size_t size, + std::size_t nmemb, + void *stream) noexcept +{ + CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream; + + size *= nmemb; + if (size == 0) + return 0; + + return c.DataReceived(ptr, size); +} diff -Nru mpd-0.23.6/src/lib/curl/Adapter.hxx mpd-0.23.8/src/lib/curl/Adapter.hxx --- mpd-0.23.6/src/lib/curl/Adapter.hxx 1970-01-01 00:00:00.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Adapter.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -0,0 +1,91 @@ +/* + * Copyright 2008-2022 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Headers.hxx" + +#include + +#include +#include + +struct StringView; +class CurlEasy; +class CurlResponseHandler; + +class CurlResponseHandlerAdapter { + CURL *curl; + + CurlResponseHandler &handler; + + Curl::Headers headers; + + /** + * An exception caught from within the WriteFunction() which + * will later be handled by Done(). + */ + std::exception_ptr postponed_error; + + /** error message provided by libcurl */ + char error_buffer[CURL_ERROR_SIZE]; + + enum class State { + UNINITIALISED, + HEADERS, + BODY, + CLOSED, + } state = State::UNINITIALISED; + +public: + explicit CurlResponseHandlerAdapter(CurlResponseHandler &_handler) noexcept + :handler(_handler) {} + + void Install(CurlEasy &easy); + + void Done(CURLcode result) noexcept; + +private: + void FinishHeaders(); + void FinishBody(); + + void HeaderFunction(StringView s) noexcept; + + /** called by curl when a new header is available */ + static std::size_t _HeaderFunction(char *ptr, + std::size_t size, std::size_t nmemb, + void *stream) noexcept; + + std::size_t DataReceived(const void *ptr, std::size_t size) noexcept; + + /** called by curl when new data is available */ + static std::size_t WriteFunction(char *ptr, + std::size_t size, std::size_t nmemb, + void *stream) noexcept; +}; diff -Nru mpd-0.23.6/src/lib/curl/Delegate.cxx mpd-0.23.8/src/lib/curl/Delegate.cxx --- mpd-0.23.6/src/lib/curl/Delegate.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Delegate.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2018 Max Kellermann + * Copyright 2008-2022 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -34,8 +34,7 @@ #include void -DelegateCurlResponseHandler::OnHeaders(unsigned status, - std::multimap &&headers) +DelegateCurlResponseHandler::OnHeaders(unsigned status, Curl::Headers &&headers) { parser = MakeParser(status, std::move(headers)); assert(parser); diff -Nru mpd-0.23.6/src/lib/curl/Delegate.hxx mpd-0.23.8/src/lib/curl/Delegate.hxx --- mpd-0.23.6/src/lib/curl/Delegate.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Delegate.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2018 Max Kellermann + * Copyright 2008-2022 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,8 +27,7 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CURL_DELEGATE_HXX -#define CURL_DELEGATE_HXX +#pragma once #include "Handler.hxx" @@ -53,7 +52,7 @@ * CurlResponseParser::OnError()). */ virtual std::unique_ptr MakeParser(unsigned status, - std::multimap &&headers) = 0; + Curl::Headers &&headers) = 0; /** * The parser has finished parsing the response body. This @@ -64,10 +63,7 @@ virtual void FinishParser(std::unique_ptr p) = 0; public: - void OnHeaders(unsigned status, - std::multimap &&headers) final; + void OnHeaders(unsigned status, Curl::Headers &&headers) final; void OnData(ConstBuffer data) final; void OnEnd() final; }; - -#endif diff -Nru mpd-0.23.6/src/lib/curl/Form.cxx mpd-0.23.8/src/lib/curl/Form.cxx --- mpd-0.23.6/src/lib/curl/Form.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Form.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -31,8 +31,7 @@ #include "String.hxx" std::string -EncodeForm(CURL *curl, - const std::multimap &fields) noexcept +EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept { std::string result; diff -Nru mpd-0.23.6/src/lib/curl/Form.hxx mpd-0.23.8/src/lib/curl/Form.hxx --- mpd-0.23.6/src/lib/curl/Form.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Form.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -30,17 +30,17 @@ #ifndef CURL_FORM_HXX #define CURL_FORM_HXX +#include "Headers.hxx" + #include #include -#include /** * Encode the given map of form fields to a * "application/x-www-form-urlencoded" string. */ std::string -EncodeForm(CURL *curl, - const std::multimap &fields) noexcept; +EncodeForm(CURL *curl, const Curl::Headers &fields) noexcept; #endif diff -Nru mpd-0.23.6/src/lib/curl/Handler.hxx mpd-0.23.8/src/lib/curl/Handler.hxx --- mpd-0.23.6/src/lib/curl/Handler.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Handler.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2018 Max Kellermann + * Copyright 2008-2021 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,14 +27,12 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef CURL_HANDLER_HXX -#define CURL_HANDLER_HXX +#pragma once +#include "Headers.hxx" #include "util/ConstBuffer.hxx" #include -#include -#include /** * Asynchronous response handler for a #CurlRequest. @@ -52,28 +50,31 @@ /** * Status line and headers have been received. + * + * Exceptions thrown by this method will be passed to + * OnError(), aborting the request. */ - virtual void OnHeaders(unsigned status, - std::multimap &&headers) = 0; + virtual void OnHeaders(unsigned status, Curl::Headers &&headers) = 0; /** * Response body data has been received. * - * May throw #Pause (but nothing else). + * May throw #Pause. + * + * Other exceptions thrown by this method will be passed to + * OnError(), aborting the request. */ virtual void OnData(ConstBuffer data) = 0; /** - * The response has ended. The method is allowed delete the - * #CurlRequest here. + * The response has ended. The method is allowed to delete the + * #CurlRequest. */ virtual void OnEnd() = 0; /** - * An error has occurred. The method is allowed delete the - * #CurlRequest here. + * An error has occurred. The method is allowed to delete the + * #CurlRequest. */ virtual void OnError(std::exception_ptr e) noexcept = 0; }; - -#endif diff -Nru mpd-0.23.6/src/lib/curl/Headers.hxx mpd-0.23.8/src/lib/curl/Headers.hxx --- mpd-0.23.6/src/lib/curl/Headers.hxx 1970-01-01 00:00:00.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Headers.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -0,0 +1,42 @@ +/* + * Copyright 2020-2021 CM4all GmbH + * All rights reserved. + * + * author: Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +namespace Curl { + +using Headers = std::multimap>; + +} // namespace Curl diff -Nru mpd-0.23.6/src/lib/curl/meson.build mpd-0.23.8/src/lib/curl/meson.build --- mpd-0.23.6/src/lib/curl/meson.build 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/meson.build 2022-07-08 23:05:38.000000000 +0000 @@ -18,6 +18,8 @@ 'Init.cxx', 'Global.cxx', 'Request.cxx', + 'Setup.cxx', + 'Adapter.cxx', 'Escape.cxx', 'Form.cxx', include_directories: inc, diff -Nru mpd-0.23.6/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch mpd-0.23.8/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch --- mpd-0.23.6/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch 2022-07-08 23:05:38.000000000 +0000 @@ -1,6 +1,8 @@ ---- curl-7.75.0.orig/CMakeLists.txt 2021-02-02 09:26:24.000000000 +0100 -+++ curl-7.75.0/CMakeLists.txt 2021-03-25 20:17:25.445684029 +0100 -@@ -1453,7 +1453,7 @@ +Index: curl-7.84.0/CMakeLists.txt +=================================================================== +--- curl-7.84.0.orig/CMakeLists.txt ++++ curl-7.84.0/CMakeLists.txt +@@ -1536,7 +1536,7 @@ set(includedir "\${prefix}/ set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS}") set(LIBCURL_LIBS "") set(libdir "${CMAKE_INSTALL_PREFIX}/lib") @@ -8,4 +10,4 @@ +foreach(_lib ${CURL_LIBS}) if(TARGET "${_lib}") set(_libname "${_lib}") - get_target_property(_libtype "${_libname}" TYPE) + get_target_property(_imported "${_libname}" IMPORTED) diff -Nru mpd-0.23.6/src/lib/curl/patches/no_netrc.patch mpd-0.23.8/src/lib/curl/patches/no_netrc.patch --- mpd-0.23.6/src/lib/curl/patches/no_netrc.patch 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/patches/no_netrc.patch 2022-07-08 23:05:38.000000000 +0000 @@ -1,20 +1,20 @@ -Index: curl-7.71.1/lib/url.c +Index: curl-7.84.0/lib/url.c =================================================================== ---- curl-7.71.1.orig/lib/url.c -+++ curl-7.71.1/lib/url.c -@@ -2871,6 +2871,7 @@ - } +--- curl-7.84.0.orig/lib/url.c ++++ curl-7.84.0/lib/url.c +@@ -3003,6 +3003,7 @@ static CURLcode override_login(struct Cu + #ifndef CURL_DISABLE_NETRC conn->bits.netrc = FALSE; +#ifndef __BIONIC__ if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) { bool netrc_user_changed = FALSE; bool netrc_passwd_changed = FALSE; -@@ -2895,6 +2896,7 @@ - conn->bits.user_passwd = TRUE; /* enable user+password */ +@@ -3079,6 +3080,7 @@ static CURLcode override_login(struct Cu + return CURLE_OUT_OF_MEMORY; } } +#endif - /* for updated strings, we update them in the URL */ - if(*userp) { + return CURLE_OK; + } diff -Nru mpd-0.23.6/src/lib/curl/Request.cxx mpd-0.23.8/src/lib/curl/Request.cxx --- mpd-0.23.6/src/lib/curl/Request.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Request.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2018 Max Kellermann + * Copyright 2008-2021 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,42 +27,27 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "config.h" #include "Request.hxx" +#include "Setup.hxx" #include "Global.hxx" -#include "Handler.hxx" #include "event/Call.hxx" -#include "util/RuntimeError.hxx" -#include "util/StringStrip.hxx" -#include "util/StringView.hxx" -#include "util/CharUtil.hxx" -#include "Version.h" #include -#include #include -#include +CurlRequest::CurlRequest(CurlGlobal &_global, CurlEasy _easy, + CurlResponseHandler &_handler) + :global(_global), handler(_handler), easy(std::move(_easy)) +{ + SetupEasy(); +} CurlRequest::CurlRequest(CurlGlobal &_global, CurlResponseHandler &_handler) :global(_global), handler(_handler) { - error_buffer[0] = 0; - - easy.SetPrivate((void *)this); - easy.SetUserAgent("Music Player Daemon " VERSION); - easy.SetHeaderFunction(_HeaderFunction, this); - easy.SetWriteFunction(WriteFunction, this); -#if !defined(ANDROID) && !defined(_WIN32) - easy.SetOption(CURLOPT_NETRC, 1L); -#endif - easy.SetErrorBuffer(error_buffer); - easy.SetNoProgress(); - easy.SetNoSignal(); - easy.SetConnectTimeout(10); - easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY); + SetupEasy(); } CurlRequest::~CurlRequest() noexcept @@ -71,6 +56,16 @@ } void +CurlRequest::SetupEasy() +{ + easy.SetPrivate((void *)this); + + handler.Install(easy); + + Curl::Setup(easy); +} + +void CurlRequest::Start() { assert(!registered); @@ -126,134 +121,9 @@ } void -CurlRequest::FinishHeaders() -{ - if (state != State::HEADERS) - return; - - state = State::BODY; - - long status = 0; - easy.GetInfo(CURLINFO_RESPONSE_CODE, &status); - - handler.OnHeaders(status, std::move(headers)); -} - -void -CurlRequest::FinishBody() -{ - FinishHeaders(); - - if (state != State::BODY) - return; - - state = State::CLOSED; - handler.OnEnd(); -} - -void CurlRequest::Done(CURLcode result) noexcept { Stop(); - try { - if (result != CURLE_OK) { - StripRight(error_buffer); - const char *msg = error_buffer; - if (*msg == 0) - msg = curl_easy_strerror(result); - throw FormatRuntimeError("CURL failed: %s", msg); - } - - FinishBody(); - } catch (...) { - state = State::CLOSED; - handler.OnError(std::current_exception()); - } -} - -[[gnu::pure]] -static bool -IsResponseBoundaryHeader(StringView s) noexcept -{ - return s.size > 5 && (s.StartsWith("HTTP/") || - /* the proprietary "ICY 200 OK" is - emitted by Shoutcast */ - s.StartsWith("ICY 2")); -} - -inline void -CurlRequest::HeaderFunction(StringView s) noexcept -{ - if (state > State::HEADERS) - return; - - if (IsResponseBoundaryHeader(s)) { - /* this is the boundary to a new response, for example - after a redirect */ - headers.clear(); - return; - } - - const char *header = s.data; - const char *end = StripRight(header, header + s.size); - - const char *value = s.Find(':'); - if (value == nullptr) - return; - - std::string name(header, value); - std::transform(name.begin(), name.end(), name.begin(), - static_cast(ToLowerASCII)); - - /* skip the colon */ - - ++value; - - /* strip the value */ - - value = StripLeft(value, end); - end = StripRight(value, end); - - headers.emplace(std::move(name), std::string(value, end)); -} - -size_t -CurlRequest::_HeaderFunction(char *ptr, size_t size, size_t nmemb, - void *stream) noexcept -{ - CurlRequest &c = *(CurlRequest *)stream; - - size *= nmemb; - - c.HeaderFunction({ptr, size}); - return size; -} - -inline size_t -CurlRequest::DataReceived(const void *ptr, size_t received_size) noexcept -{ - assert(received_size > 0); - - try { - FinishHeaders(); - handler.OnData({ptr, received_size}); - return received_size; - } catch (CurlResponseHandler::Pause) { - return CURL_WRITEFUNC_PAUSE; - } - -} - -size_t -CurlRequest::WriteFunction(char *ptr, size_t size, size_t nmemb, - void *stream) noexcept -{ - CurlRequest &c = *(CurlRequest *)stream; - - size *= nmemb; - if (size == 0) - return 0; - - return c.DataReceived(ptr, size); + handler.Done(result); } diff -Nru mpd-0.23.6/src/lib/curl/Request.hxx mpd-0.23.8/src/lib/curl/Request.hxx --- mpd-0.23.6/src/lib/curl/Request.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Request.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2018 Max Kellermann + * Copyright 2008-2021 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -31,39 +31,34 @@ #define CURL_REQUEST_HXX #include "Easy.hxx" +#include "Adapter.hxx" -#include -#include +#include struct StringView; class CurlGlobal; class CurlResponseHandler; +/** + * A non-blocking HTTP request integrated via #CurlGlobal into the + * #EventLoop. + * + * To start sending the request, call Start(). + */ class CurlRequest final { CurlGlobal &global; - CurlResponseHandler &handler; + CurlResponseHandlerAdapter handler; /** the curl handle */ CurlEasy easy; - enum class State { - HEADERS, - BODY, - CLOSED, - } state = State::HEADERS; - - std::multimap headers; - - /** error message provided by libcurl */ - char error_buffer[CURL_ERROR_SIZE]; - bool registered = false; public: - /** - * To start sending the request, call Start(). - */ + CurlRequest(CurlGlobal &_global, CurlEasy easy, + CurlResponseHandler &_handler); + CurlRequest(CurlGlobal &_global, CurlResponseHandler &_handler); @@ -136,7 +131,7 @@ easy.SetPost(value); } - void SetRequestBody(const void *data, size_t size) { + void SetRequestBody(const void *data, std::size_t size) { easy.SetRequestBody(data, size); } @@ -148,6 +143,8 @@ void Done(CURLcode result) noexcept; private: + void SetupEasy(); + /** * Frees the current "libcurl easy" handle, and everything * associated with it. @@ -156,18 +153,6 @@ void FinishHeaders(); void FinishBody(); - - size_t DataReceived(const void *ptr, size_t size) noexcept; - - void HeaderFunction(StringView s) noexcept; - - /** called by curl when new data is available */ - static size_t _HeaderFunction(char *ptr, size_t size, size_t nmemb, - void *stream) noexcept; - - /** called by curl when new data is available */ - static size_t WriteFunction(char *ptr, size_t size, size_t nmemb, - void *stream) noexcept; }; #endif diff -Nru mpd-0.23.6/src/lib/curl/Setup.cxx mpd-0.23.8/src/lib/curl/Setup.cxx --- mpd-0.23.6/src/lib/curl/Setup.cxx 1970-01-01 00:00:00.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Setup.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -0,0 +1,51 @@ +/* + * Copyright 2008-2021 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Setup.hxx" +#include "Easy.hxx" +#include "Version.h" + +#include + +namespace Curl { + +void +Setup(CurlEasy &easy) +{ + easy.SetUserAgent("Music Player Daemon " VERSION); +#if !defined(ANDROID) && !defined(_WIN32) + easy.SetOption(CURLOPT_NETRC, 1L); +#endif + easy.SetNoProgress(); + easy.SetNoSignal(); + easy.SetConnectTimeout(10); + easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY); +} + +} // namespace Curl diff -Nru mpd-0.23.6/src/lib/curl/Setup.hxx mpd-0.23.8/src/lib/curl/Setup.hxx --- mpd-0.23.6/src/lib/curl/Setup.hxx 1970-01-01 00:00:00.000000000 +0000 +++ mpd-0.23.8/src/lib/curl/Setup.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -0,0 +1,39 @@ +/* + * Copyright 2008-2021 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +class CurlEasy; + +namespace Curl { + +void +Setup(CurlEasy &easy); + +} // namespace Curl diff -Nru mpd-0.23.6/src/lib/icu/meson.build mpd-0.23.8/src/lib/icu/meson.build --- mpd-0.23.6/src/lib/icu/meson.build 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/icu/meson.build 2022-07-08 23:05:38.000000000 +0000 @@ -12,17 +12,23 @@ icu_sources += 'Win32.cxx' endif +iconv_dep = [] if icu_dep.found() icu_sources += [ 'Util.cxx', 'Init.cxx', ] elif not get_option('iconv').disabled() - have_iconv = compiler.has_function('iconv', prefix : '#include ') - conf.set('HAVE_ICONV', have_iconv) + # an installed iconv library will make the builtin iconv() unavailable, + # so search for the library first and pass it as (possible) dependency + iconv_dep = compiler.find_library('libiconv', required: false) + have_iconv = compiler.has_function('iconv', + dependencies: iconv_dep, + prefix : '#include ') if not have_iconv and get_option('iconv').enabled() error('iconv() not available') endif + conf.set('HAVE_ICONV', have_iconv) endif icu = static_library( @@ -31,6 +37,7 @@ include_directories: inc, dependencies: [ icu_dep, + iconv_dep, fmt_dep, ], ) diff -Nru mpd-0.23.6/src/lib/upnp/Compat.hxx mpd-0.23.8/src/lib/upnp/Compat.hxx --- mpd-0.23.6/src/lib/upnp/Compat.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/upnp/Compat.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -24,6 +24,10 @@ /* libupnp versions until 1.10.1 redefine "bool" and "true" */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wkeyword-macro" + +/* libupnp 1.8.4 uses a flawed kludge to suppress this warning in + inline function __list_add_valid() */ +#pragma GCC diagnostic ignored "-Wunused-but-set-parameter" #endif #include diff -Nru mpd-0.23.6/src/lib/upnp/Discovery.cxx mpd-0.23.8/src/lib/upnp/Discovery.cxx --- mpd-0.23.6/src/lib/upnp/Discovery.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/upnp/Discovery.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -55,7 +55,7 @@ void UPnPDeviceDirectory::Downloader::OnHeaders(unsigned status, - std::multimap &&) + Curl::Headers &&) { if (status != 200) { Destroy(); diff -Nru mpd-0.23.6/src/lib/upnp/Discovery.hxx mpd-0.23.8/src/lib/upnp/Discovery.hxx --- mpd-0.23.6/src/lib/upnp/Discovery.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/upnp/Discovery.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -113,8 +113,7 @@ } /* virtual methods from CurlResponseHandler */ - void OnHeaders(unsigned status, - std::multimap &&headers) override; + void OnHeaders(unsigned status, Curl::Headers &&headers) override; void OnData(ConstBuffer data) override; void OnEnd() override; void OnError(std::exception_ptr e) noexcept override; diff -Nru mpd-0.23.6/src/lib/upnp/Init.cxx mpd-0.23.8/src/lib/upnp/Init.cxx --- mpd-0.23.6/src/lib/upnp/Init.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/upnp/Init.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -35,13 +35,7 @@ static void DoInit(const char* iface) { - -#ifdef UPNP_ENABLE_IPV6 - auto code = UpnpInit2(iface, 0); -#else - auto code = UpnpInit(iface, 0); -#endif - if (code != UPNP_E_SUCCESS) + if (auto code = UpnpInit2(iface, 0); code != UPNP_E_SUCCESS) throw FormatRuntimeError("UpnpInit() failed: %s", UpnpGetErrorMessage(code)); diff -Nru mpd-0.23.6/src/lib/xiph/OggSyncState.cxx mpd-0.23.8/src/lib/xiph/OggSyncState.cxx --- mpd-0.23.6/src/lib/xiph/OggSyncState.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/lib/xiph/OggSyncState.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -67,7 +67,7 @@ bool OggSyncState::ExpectPageSeek(ogg_page &page) { - size_t remaining_skipped = 32768; + size_t remaining_skipped = 65536; while (true) { int r = ogg_sync_pageseek(&oy, &page); diff -Nru mpd-0.23.6/src/Log.hxx mpd-0.23.8/src/Log.hxx --- mpd-0.23.6/src/Log.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/Log.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -45,7 +45,10 @@ LogFmt(LogLevel level, const Domain &domain, const S &format_str, Args&&... args) noexcept { -#if FMT_VERSION >= 70000 +#if FMT_VERSION >= 90000 + return LogVFmt(level, domain, format_str, + fmt::make_format_args(args...)); +#elif FMT_VERSION >= 70000 return LogVFmt(level, domain, fmt::to_string_view(format_str), fmt::make_args_checked(format_str, args...)); diff -Nru mpd-0.23.6/src/mixer/MixerAll.cxx mpd-0.23.8/src/mixer/MixerAll.cxx --- mpd-0.23.6/src/mixer/MixerAll.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/mixer/MixerAll.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -34,13 +34,15 @@ static int output_mixer_get_volume(const AudioOutputControl &ao) noexcept { - if (!ao.IsEnabled()) - return -1; - auto *mixer = ao.GetMixer(); if (mixer == nullptr) return -1; + /* software mixers are always considered, even if they are + disabled */ + if (!ao.IsEnabled() && !mixer->IsPlugin(software_mixer_plugin)) + return -1; + try { return mixer_get_volume(mixer); } catch (...) { @@ -71,40 +73,77 @@ return total / ok; } -static bool -output_mixer_set_volume(AudioOutputControl &ao, unsigned volume) noexcept +enum class SetVolumeResult { + NO_MIXER, + DISABLED, + ERROR, + OK, +}; + +static SetVolumeResult +output_mixer_set_volume(AudioOutputControl &ao, unsigned volume) { assert(volume <= 100); - if (!ao.IsEnabled()) - return false; - auto *mixer = ao.GetMixer(); if (mixer == nullptr) - return false; + return SetVolumeResult::NO_MIXER; + + /* software mixers are always updated, even if they are + disabled */ + if (!mixer->IsPlugin(software_mixer_plugin) && + /* "global" mixers can be used even if the output hasn't + been used yet */ + !(mixer->IsGlobal() ? ao.IsEnabled() : ao.IsReallyEnabled())) + return SetVolumeResult::DISABLED; try { mixer_set_volume(mixer, volume); - return true; + return SetVolumeResult::OK; } catch (...) { FmtError(mixer_domain, "Failed to set mixer for '{}': {}", ao.GetName(), std::current_exception()); - return false; + std::throw_with_nested(std::runtime_error(fmt::format("Failed to set mixer for '{}'", + ao.GetName()))); } } -bool -MultipleOutputs::SetVolume(unsigned volume) noexcept +void +MultipleOutputs::SetVolume(unsigned volume) { assert(volume <= 100); - bool success = false; - for (const auto &ao : outputs) - success = output_mixer_set_volume(*ao, volume) - || success; + SetVolumeResult result = SetVolumeResult::NO_MIXER; + std::exception_ptr error; - return success; + for (const auto &ao : outputs) { + try { + auto r = output_mixer_set_volume(*ao, volume); + if (r > result) + result = r; + } catch (...) { + /* remember the first error */ + if (!error) { + error = std::current_exception(); + result = SetVolumeResult::ERROR; + } + } + } + + switch (result) { + case SetVolumeResult::NO_MIXER: + throw std::runtime_error{"No mixer"}; + + case SetVolumeResult::DISABLED: + throw std::runtime_error{"All outputs are disabled"}; + + case SetVolumeResult::ERROR: + std::rethrow_exception(error); + + case SetVolumeResult::OK: + break; + } } static int diff -Nru mpd-0.23.6/src/mixer/MixerControl.cxx mpd-0.23.8/src/mixer/MixerControl.cxx --- mpd-0.23.6/src/mixer/MixerControl.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/mixer/MixerControl.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -60,9 +60,9 @@ try { mixer->Open(); mixer->open = true; - mixer->failed = false; + mixer->failure = {}; } catch (...) { - mixer->failed = true; + mixer->failure = std::current_exception(); throw; } } @@ -75,6 +75,7 @@ mixer->Close(); mixer->open = false; + mixer->failure = {}; } void @@ -95,20 +96,6 @@ mixer_close(mixer); } -/* - * Close the mixer due to failure. The mutex must be locked before - * calling this function. - */ -static void -mixer_failed(Mixer *mixer) -{ - assert(mixer->open); - - mixer_close_internal(mixer); - - mixer->failed = true; -} - int mixer_get_volume(Mixer *mixer) { @@ -116,7 +103,7 @@ assert(mixer != nullptr); - if (mixer->plugin.global && !mixer->failed) + if (mixer->plugin.global && !mixer->failure) mixer_open(mixer); const std::scoped_lock protect(mixer->mutex); @@ -125,7 +112,8 @@ try { volume = mixer->GetVolume(); } catch (...) { - mixer_failed(mixer); + mixer_close_internal(mixer); + mixer->failure = std::current_exception(); throw; } } else @@ -140,11 +128,13 @@ assert(mixer != nullptr); assert(volume <= 100); - if (mixer->plugin.global && !mixer->failed) + if (mixer->plugin.global && !mixer->failure) mixer_open(mixer); const std::scoped_lock protect(mixer->mutex); if (mixer->open) mixer->SetVolume(volume); + else if (mixer->failure) + std::rethrow_exception(mixer->failure); } diff -Nru mpd-0.23.6/src/mixer/MixerInternal.hxx mpd-0.23.8/src/mixer/MixerInternal.hxx --- mpd-0.23.6/src/mixer/MixerInternal.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/mixer/MixerInternal.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -25,6 +25,8 @@ #include "thread/Mutex.hxx" #include "util/Compiler.h" +#include + class MixerListener; class Mixer { @@ -40,15 +42,15 @@ Mutex mutex; /** - * Is the mixer device currently open? + * Contains error details if this mixer has failed. If set, + * it should not be reopened automatically. */ - bool open = false; + std::exception_ptr failure; /** - * Has this mixer failed, and should not be reopened - * automatically? + * Is the mixer device currently open? */ - bool failed = false; + bool open = false; public: explicit Mixer(const MixerPlugin &_plugin, @@ -63,6 +65,10 @@ return &plugin == &other; } + bool IsGlobal() const noexcept { + return plugin.global; + } + /** * Open mixer device * diff -Nru mpd-0.23.6/src/mixer/Volume.cxx mpd-0.23.8/src/mixer/Volume.cxx --- mpd-0.23.6/src/mixer/Volume.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/mixer/Volume.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -71,16 +71,16 @@ return true; } -static bool +static void hardware_volume_change(MultipleOutputs &outputs, unsigned volume) { /* reset the cache */ last_hardware_volume = -1; - return outputs.SetVolume(volume); + outputs.SetVolume(volume); } -bool +void volume_level_change(MultipleOutputs &outputs, unsigned volume) { assert(volume <= 100); @@ -89,7 +89,7 @@ idle_add(IDLE_MIXER); - return hardware_volume_change(outputs, volume); + hardware_volume_change(outputs, volume); } bool diff -Nru mpd-0.23.6/src/mixer/Volume.hxx mpd-0.23.8/src/mixer/Volume.hxx --- mpd-0.23.6/src/mixer/Volume.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/mixer/Volume.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -30,7 +30,10 @@ int volume_level_get(const MultipleOutputs &outputs) noexcept; -bool +/** + * Throws on error. + */ +void volume_level_change(MultipleOutputs &outputs, unsigned volume); bool diff -Nru mpd-0.23.6/src/output/Control.hxx mpd-0.23.8/src/output/Control.hxx --- mpd-0.23.6/src/output/Control.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/output/Control.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -291,6 +291,13 @@ /** * Caller must lock the mutex. */ + bool IsReallyEnabled() const noexcept { + return really_enabled; + } + + /** + * Caller must lock the mutex. + */ bool IsEnabled() const noexcept { return enabled; } diff -Nru mpd-0.23.6/src/output/MultipleOutputs.hxx mpd-0.23.8/src/output/MultipleOutputs.hxx --- mpd-0.23.6/src/output/MultipleOutputs.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/output/MultipleOutputs.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -141,10 +141,11 @@ /** * Sets the volume on all available mixers. * + * Throws on error. + * * @param volume the volume (range 0..100) - * @return true on success, false on failure */ - bool SetVolume(unsigned volume) noexcept; + void SetVolume(unsigned volume); /** * Similar to GetVolume(), but gets the volume only for diff -Nru mpd-0.23.6/src/output/plugins/meson.build mpd-0.23.8/src/output/plugins/meson.build --- mpd-0.23.6/src/output/plugins/meson.build 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/output/plugins/meson.build 2022-07-08 23:05:38.000000000 +0000 @@ -10,6 +10,7 @@ ] need_encoder = false +need_wave_encoder = false if alsa_dep.found() output_plugins_sources += 'AlsaOutputPlugin.cxx' @@ -99,7 +100,7 @@ need_encoder = true endif -libshout_dep = dependency('shout', required: get_option('shout')) +libshout_dep = dependency('shout', version: '>= 2.4.0', required: get_option('shout')) output_features.set('HAVE_SHOUT', libshout_dep.found()) if libshout_dep.found() output_plugins_sources += 'ShoutOutputPlugin.cxx' @@ -127,9 +128,7 @@ output_features.set('HAVE_YAJL', yajl_dep.found()) - # TODO: the Snapcast plugin needs just the "wave" encoder, but this - # enables all available encoders - need_encoder = true + need_wave_encoder = true endif enable_solaris_output = get_option('solaris_output') diff -Nru mpd-0.23.6/src/output/plugins/OSXOutputPlugin.cxx mpd-0.23.8/src/output/plugins/OSXOutputPlugin.cxx --- mpd-0.23.6/src/output/plugins/OSXOutputPlugin.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/output/plugins/OSXOutputPlugin.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -160,13 +160,13 @@ static constexpr AudioObjectPropertyAddress default_system_output_device{ kAudioHardwarePropertyDefaultSystemOutputDevice, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster, + kAudioObjectPropertyElementMain, }; static constexpr AudioObjectPropertyAddress default_output_device{ kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster + kAudioObjectPropertyElementMain }; const auto &aopa = @@ -195,9 +195,9 @@ OSXOutput::GetVolume() { static constexpr AudioObjectPropertyAddress aopa = { - kAudioHardwareServiceDeviceProperty_VirtualMasterVolume, + kAudioHardwareServiceDeviceProperty_VirtualMainVolume, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster, + kAudioObjectPropertyElementMain, }; const auto vol = AudioObjectGetPropertyDataT(dev_id, @@ -211,9 +211,9 @@ { Float32 vol = new_volume / 100.0; static constexpr AudioObjectPropertyAddress aopa = { - kAudioHardwareServiceDeviceProperty_VirtualMasterVolume, + kAudioHardwareServiceDeviceProperty_VirtualMainVolume, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster + kAudioObjectPropertyElementMain }; UInt32 size = sizeof(vol); OSStatus status = AudioObjectSetPropertyData(dev_id, @@ -366,25 +366,25 @@ static constexpr AudioObjectPropertyAddress aopa_device_streams = { kAudioDevicePropertyStreams, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster + kAudioObjectPropertyElementMain }; static constexpr AudioObjectPropertyAddress aopa_stream_direction = { kAudioStreamPropertyDirection, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster + kAudioObjectPropertyElementMain }; static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = { kAudioStreamPropertyAvailablePhysicalFormats, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster + kAudioObjectPropertyElementMain }; static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster + kAudioObjectPropertyElementMain }; OSStatus err; @@ -484,7 +484,7 @@ static constexpr AudioObjectPropertyAddress aopa = { kAudioDevicePropertyHogMode, kAudioObjectPropertyScopeOutput, - kAudioObjectPropertyElementMaster + kAudioObjectPropertyElementMain }; pid_t hog_pid; @@ -538,7 +538,7 @@ static constexpr AudioObjectPropertyAddress aopa_name{ kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster, + kAudioObjectPropertyElementMain, }; char actual_name[256]; @@ -561,7 +561,7 @@ static constexpr AudioObjectPropertyAddress aopa_hw_devices{ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster, + kAudioObjectPropertyElementMain, }; const auto ids = diff -Nru mpd-0.23.6/src/output/plugins/PipeWireOutputPlugin.cxx mpd-0.23.8/src/output/plugins/PipeWireOutputPlugin.cxx --- mpd-0.23.6/src/output/plugins/PipeWireOutputPlugin.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/output/plugins/PipeWireOutputPlugin.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -24,6 +24,7 @@ #include "../Error.hxx" #include "mixer/plugins/PipeWireMixerPlugin.hxx" #include "pcm/Silence.hxx" +#include "lib/fmt/ExceptionFormatter.hxx" #include "system/Error.hxx" #include "util/BitReverse.hxx" #include "util/Domain.hxx" @@ -56,6 +57,7 @@ #include #include +#include #include #include @@ -85,7 +87,14 @@ uint32_t target_id = PW_ID_ANY; - float volume = 1.0; + /** + * The current volume level (0.0 .. 1.0). + * + * This get initialized to -1 which means "unknown", so + * restore_volume will not attempt to override PipeWire's + * initial volume level. + */ + float volume = -1; PipeWireMixer *mixer = nullptr; unsigned channels; @@ -217,26 +226,34 @@ o.Drained(); } - void ControlInfo(const struct pw_stream_control *control) noexcept { - float sum = 0; - unsigned c; - for (c = 0; c < control->n_values; c++) - sum += control->values[c]; + void OnChannelVolumes(const struct pw_stream_control &control) noexcept { + if (control.n_values < 1) + return; - sum /= control->n_values; + float sum = std::accumulate(control.values, + control.values + control.n_values, + 0.0f); + volume = std::cbrt(sum / control.n_values); if (mixer != nullptr) - pipewire_mixer_on_change(*mixer, std::cbrt(sum)); + pipewire_mixer_on_change(*mixer, volume); pw_thread_loop_signal(thread_loop, false); } - static void ControlInfo(void *data, - [[maybe_unused]] uint32_t id, + void ControlInfo([[maybe_unused]] uint32_t id, + const struct pw_stream_control &control) noexcept { + switch (id) { + case SPA_PROP_channelVolumes: + OnChannelVolumes(control); + break; + } + } + + static void ControlInfo(void *data, uint32_t id, const struct pw_stream_control *control) noexcept { auto &o = *(PipeWireOutput *)data; - if (StringIsEqual(control->name, "Channel Volumes")) - o.ControlInfo(control); + o.ControlInfo(id, *control); } #if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE) @@ -308,22 +325,38 @@ } } +/** + * Throws on error. + * + * @param volume a volume level between 0.0 and 1.0 + */ +static void +SetVolume(struct pw_stream &stream, unsigned channels, float volume) +{ + float value[MAX_CHANNELS]; + std::fill_n(value, channels, volume * volume * volume); + + if (pw_stream_set_control(&stream, + SPA_PROP_channelVolumes, channels, value, + 0) != 0) + throw std::runtime_error("pw_stream_set_control() failed"); +} + void PipeWireOutput::SetVolume(float _volume) { - const PipeWire::ThreadLoopLock lock(thread_loop); - - float newvol = _volume*_volume*_volume; + if (thread_loop == nullptr) { + /* the mixer is open (because it is a "global" mixer), + but Enable() on this output has not yet been + called */ + volume = _volume; + return; + } - if (stream != nullptr && !restore_volume) { - float vol[MAX_CHANNELS]; - std::fill_n(vol, channels, newvol); + const PipeWire::ThreadLoopLock lock(thread_loop); - if (pw_stream_set_control(stream, - SPA_PROP_channelVolumes, channels, vol, - 0) != 0) - throw std::runtime_error("pw_stream_set_control() failed"); - } + if (stream != nullptr && !restore_volume) + ::SetVolume(*stream, channels, _volume); volume = _volume; } @@ -638,8 +671,17 @@ [[maybe_unused]] const struct spa_pod *param) noexcept { if (restore_volume) { - SetVolume(volume); restore_volume = false; + + if (volume >= 0) { + try { + ::SetVolume(*stream, channels, volume); + } catch (...) { + FmtError(pipewire_output_domain, + FMT_STRING("Failed to restore volume: {}"), + std::current_exception()); + } + } } #if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE) @@ -824,6 +866,17 @@ { const PipeWire::ThreadLoopLock lock(thread_loop); + if (drained) + return; + + if (!active) { + /* there is data in the ring_buffer, but the stream is + not yet active; activate it now to ensure it is + played before this method returns */ + active = true; + pw_stream_set_active(stream, true); + } + drain_requested = true; AtScopeExit(this) { drain_requested = false; }; @@ -839,7 +892,24 @@ const PipeWire::ThreadLoopLock lock(thread_loop); interrupted = false; + if (drained) + return; + + /* clear MPD's ring buffer */ ring_buffer->reset(); + + /* clear libpipewire's buffer */ + pw_stream_flush(stream, false); + drained = true; + + /* pause the PipeWire stream so libpipewire ceases invoking + the "process" callback (we have no data until our Play() + method gets called again); the stream will be resume by + Play() after the ring_buffer has been refilled */ + if (active) { + active = false; + pw_stream_set_active(stream, false); + } } bool diff -Nru mpd-0.23.6/src/output/plugins/ShoutOutputPlugin.cxx mpd-0.23.8/src/output/plugins/ShoutOutputPlugin.cxx --- mpd-0.23.6/src/output/plugins/ShoutOutputPlugin.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/output/plugins/ShoutOutputPlugin.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -59,7 +59,7 @@ public: ShoutConfig(const ConfigBlock &block, const char *mime_type); - void Setup(shout_t *connection) const; + void Setup(shout_t &connection) const; }; struct ShoutOutput final : AudioOutput { @@ -229,41 +229,56 @@ return new ShoutOutput(block); } +static void +SetMeta(shout_t &connection, const char *name, const char *value) +{ + if (shout_set_meta(&connection, name, value) != SHOUTERR_SUCCESS) + throw std::runtime_error(shout_get_error(&connection)); +} + +static void +SetOptionalMeta(shout_t &connection, const char *name, const char *value) +{ + if (value != nullptr) + SetMeta(connection, name, value); +} + inline void -ShoutConfig::Setup(shout_t *connection) const +ShoutConfig::Setup(shout_t &connection) const { - if (shout_set_host(connection, host) != SHOUTERR_SUCCESS || - shout_set_port(connection, port) != SHOUTERR_SUCCESS || - shout_set_password(connection, passwd) != SHOUTERR_SUCCESS || - shout_set_mount(connection, mount) != SHOUTERR_SUCCESS || - shout_set_name(connection, name) != SHOUTERR_SUCCESS || - shout_set_user(connection, user) != SHOUTERR_SUCCESS || - shout_set_public(connection, is_public) != SHOUTERR_SUCCESS || - shout_set_format(connection, format) != SHOUTERR_SUCCESS || - shout_set_protocol(connection, protocol) != SHOUTERR_SUCCESS || + if (shout_set_host(&connection, host) != SHOUTERR_SUCCESS || + shout_set_port(&connection, port) != SHOUTERR_SUCCESS || + shout_set_password(&connection, passwd) != SHOUTERR_SUCCESS || + shout_set_mount(&connection, mount) != SHOUTERR_SUCCESS || + shout_set_user(&connection, user) != SHOUTERR_SUCCESS || + shout_set_public(&connection, is_public) != SHOUTERR_SUCCESS || +#ifdef SHOUT_USAGE_AUDIO + /* since libshout 2.4.3 */ + shout_set_content_format(&connection, format, SHOUT_USAGE_AUDIO, + nullptr) != SHOUTERR_SUCCESS || +#else + shout_set_format(&connection, format) != SHOUTERR_SUCCESS || +#endif + shout_set_protocol(&connection, protocol) != SHOUTERR_SUCCESS || #ifdef SHOUT_TLS - shout_set_tls(connection, tls) != SHOUTERR_SUCCESS || + shout_set_tls(&connection, tls) != SHOUTERR_SUCCESS || #endif - shout_set_agent(connection, "MPD") != SHOUTERR_SUCCESS) - throw std::runtime_error(shout_get_error(connection)); - - /* optional paramters */ + shout_set_agent(&connection, "MPD") != SHOUTERR_SUCCESS) + throw std::runtime_error(shout_get_error(&connection)); - if (genre != nullptr && shout_set_genre(connection, genre)) - throw std::runtime_error(shout_get_error(connection)); + SetMeta(connection, SHOUT_META_NAME, name); - if (description != nullptr && - shout_set_description(connection, description)) - throw std::runtime_error(shout_get_error(connection)); + /* optional paramters */ - if (url != nullptr && shout_set_url(connection, url)) - throw std::runtime_error(shout_get_error(connection)); + SetOptionalMeta(connection, SHOUT_META_GENRE, genre); + SetOptionalMeta(connection, SHOUT_META_DESCRIPTION, description); + SetOptionalMeta(connection, SHOUT_META_URL, url); if (quality != nullptr) - shout_set_audio_info(connection, SHOUT_AI_QUALITY, quality); + shout_set_audio_info(&connection, SHOUT_AI_QUALITY, quality); if (bitrate != nullptr) - shout_set_audio_info(connection, SHOUT_AI_BITRATE, bitrate); + shout_set_audio_info(&connection, SHOUT_AI_BITRATE, bitrate); } void @@ -274,7 +289,7 @@ throw std::bad_alloc{}; try { - config.Setup(shout_conn); + config.Setup(*shout_conn); } catch (...) { shout_free(shout_conn); throw; @@ -418,7 +433,7 @@ } static void -shout_tag_to_metadata(const Tag &tag, char *dest, size_t size) +shout_tag_to_metadata(const Tag &tag, char *dest, size_t size) noexcept { const char *artist = tag.GetValue(TAG_ARTIST); const char *title = tag.GetValue(TAG_TITLE); @@ -446,9 +461,15 @@ char song[1024]; shout_tag_to_metadata(tag, song, sizeof(song)); - shout_metadata_add(meta, "song", song); - shout_metadata_add(meta, "charset", "UTF-8"); - if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) { + if (SHOUTERR_SUCCESS != shout_metadata_add(meta, "song", song) || +#ifdef SHOUT_FORMAT_TEXT + /* since libshout 2.4.6 */ + SHOUTERR_SUCCESS != shout_set_metadata_utf8(shout_conn, meta) +#else + SHOUTERR_SUCCESS != shout_metadata_add(meta, "charset", "UTF-8") || + SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta) +#endif + ) { LogWarning(shout_output_domain, "error setting shout metadata"); } diff -Nru mpd-0.23.6/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx mpd-0.23.8/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx --- mpd-0.23.6/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/output/plugins/snapcast/SnapcastOutputPlugin.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -214,9 +214,8 @@ std::chrono::steady_clock::duration SnapcastOutput::Delay() const noexcept { - if (!LockHasClients() && pause) { - /* if there's no client and this output is paused, - then Pause() will not do anything, it will not fill + if (pause) { + /* Pause() will not do anything, it will not fill the buffer and it will not update the timer; therefore, we reset the timer here */ timer->Reset(); diff -Nru mpd-0.23.6/src/storage/plugins/CurlStorage.cxx mpd-0.23.8/src/storage/plugins/CurlStorage.cxx --- mpd-0.23.6/src/storage/plugins/CurlStorage.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/storage/plugins/CurlStorage.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -227,7 +227,7 @@ gcc_pure static bool -IsXmlContentType(const std::multimap &headers) noexcept +IsXmlContentType(const Curl::Headers &headers) noexcept { auto i = headers.find("content-type"); return i != headers.end() && IsXmlContentType(i->second.c_str()); @@ -297,8 +297,7 @@ } /* virtual methods from CurlResponseHandler */ - void OnHeaders(unsigned status, - std::multimap &&headers) final { + void OnHeaders(unsigned status, Curl::Headers &&headers) final { if (status != 207) throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"", status); diff -Nru mpd-0.23.6/src/tag/ApeLoader.cxx mpd-0.23.8/src/tag/ApeLoader.cxx --- mpd-0.23.6/src/tag/ApeLoader.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/tag/ApeLoader.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -73,10 +73,10 @@ unsigned n = FromLE32(footer.count); const char *p = buffer.get(); while (n-- && remaining > 10) { - size_t size = FromLE32(*(const uint32_t *)p); + size_t size = *(const PackedLE32 *)p; p += 4; remaining -= 4; - unsigned long flags = FromLE32(*(const uint32_t *)p); + unsigned long flags = *(const PackedLE32 *)p; p += 4; remaining -= 4; diff -Nru mpd-0.23.6/src/tag/Id3Picture.cxx mpd-0.23.8/src/tag/Id3Picture.cxx --- mpd-0.23.6/src/tag/Id3Picture.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/tag/Id3Picture.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -32,7 +32,7 @@ if (src.size < 4) return nullptr; - const size_t length = FromBE32(*(const uint32_t *)src.data); + const size_t length = *(const PackedBE32 *)src.data; src.skip_front(4); if (src.size < length) @@ -65,7 +65,7 @@ buffer.skip_front(16); - const size_t image_size = FromBE32(*(const uint32_t *)buffer.data); + const size_t image_size = *(const PackedBE32 *)buffer.data; buffer.skip_front(4); if (buffer.size < image_size) diff -Nru mpd-0.23.6/src/util/ByteOrder.hxx mpd-0.23.8/src/util/ByteOrder.hxx --- mpd-0.23.6/src/util/ByteOrder.hxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/src/util/ByteOrder.hxx 2022-07-08 23:05:38.000000000 +0000 @@ -298,6 +298,60 @@ static_assert(alignof(PackedBE16) == 1, "Wrong alignment"); /** + * A packed big-endian 32 bit integer. + */ +class PackedBE32 { + uint8_t a, b, c, d; + +public: + PackedBE32() = default; + + constexpr PackedBE32(uint32_t src) noexcept + :a(uint8_t(src >> 24)), + b(uint8_t(src >> 16)), + c(uint8_t(src >> 8)), + d(uint8_t(src)) {} + + /** + * Construct an instance from an integer which is already + * big-endian. + */ + static constexpr auto FromBE(uint32_t src) noexcept { + union { + uint32_t in; + PackedBE32 out; + } u{src}; + return u.out; + } + + constexpr operator uint32_t() const noexcept { + return (uint32_t(a) << 24) | (uint32_t(b) << 16) | + (uint32_t(c) << 8) | uint32_t(d); + } + + PackedBE32 &operator=(uint32_t new_value) noexcept { + d = uint8_t(new_value); + c = uint8_t(new_value >> 8); + b = uint8_t(new_value >> 16); + a = uint8_t(new_value >> 24); + return *this; + } + + /** + * Reads the raw, big-endian value. + */ + constexpr uint32_t raw() const noexcept { + uint32_t x = *this; + if (IsLittleEndian()) + x = ByteSwap32(x); + return x; + } +}; + +static_assert(sizeof(PackedBE32) == sizeof(uint32_t), "Wrong size"); +static_assert(alignof(PackedBE32) == 1, "Wrong alignment"); + +/** * A packed little-endian 16 bit integer. */ class PackedLE16 { diff -Nru mpd-0.23.6/subprojects/expat.wrap mpd-0.23.8/subprojects/expat.wrap --- mpd-0.23.6/subprojects/expat.wrap 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/subprojects/expat.wrap 2022-07-08 23:05:38.000000000 +0000 @@ -1,9 +1,12 @@ -[wrap-file] -directory = expat-2.2.9 -source_url = https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.xz -source_filename = expat-2.2.9.tar.bz2 -source_hash = 1ea6965b15c2106b6bbe883397271c80dfa0331cdf821b2c319591b55eadc0a4 -patch_url = https://wrapdb.mesonbuild.com/v1/projects/expat/2.2.9/3/get_zip -patch_filename = expat-2.2.9-3-wrap.zip -patch_hash = e9aaace62e9a158b5e96f5c38c9f81f369179206acd87697653d777c0d3975d3 - +[wrap-file] +directory = expat-2.4.8 +source_url = https://github.com/libexpat/libexpat/releases/download/R_2_4_8/expat-2.4.8.tar.xz +source_filename = expat-2.4.8.tar.bz2 +source_hash = f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25 +patch_filename = expat_2.4.8-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.4.8-1/get_patch +patch_hash = 9aec253a2c6d1c0feb852c5c6920298d14701eeec7acc6832bb402438b52112a + +[provide] +expat = expat_dep + diff -Nru mpd-0.23.6/subprojects/fmt.wrap mpd-0.23.8/subprojects/fmt.wrap --- mpd-0.23.6/subprojects/fmt.wrap 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/subprojects/fmt.wrap 2022-07-08 23:05:38.000000000 +0000 @@ -1,12 +1,12 @@ [wrap-file] -directory = fmt-7.1.3 -source_url = https://github.com/fmtlib/fmt/archive/7.1.3.tar.gz -source_filename = fmt-7.1.3.tar.gz -source_hash = 5cae7072042b3043e12d53d50ef404bbb76949dad1de368d7f993a15c8c05ecc -patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/7.1.3/1/get_zip -patch_filename = fmt-7.1.3-1-wrap.zip -patch_hash = 6eb951a51806fd6ffd596064825c39b844c1fe1799840ef507b61a53dba08213 +directory = fmt-8.1.1 +source_url = https://github.com/fmtlib/fmt/archive/8.1.1.tar.gz +source_filename = fmt-8.1.1.tar.gz +source_hash = 3d794d3cf67633b34b2771eb9f073bde87e846e0d395d254df7b211ef1ec7346 +patch_filename = fmt_8.1.1-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_8.1.1-2/get_patch +patch_hash = cd001046281330a8862591780a9ea71a1fa594edd0d015deb24e44680c9ea33b +wrapdb_version = 8.1.1-2 [provide] fmt = fmt_dep - diff -Nru mpd-0.23.6/subprojects/gtest.wrap mpd-0.23.8/subprojects/gtest.wrap --- mpd-0.23.6/subprojects/gtest.wrap 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/subprojects/gtest.wrap 2022-07-08 23:05:38.000000000 +0000 @@ -1,10 +1,15 @@ [wrap-file] -directory = googletest-release-1.10.0 +directory = googletest-release-1.11.0 +source_url = https://github.com/google/googletest/archive/release-1.11.0.tar.gz +source_filename = gtest-1.11.0.tar.gz +source_hash = b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5 +patch_filename = gtest_1.11.0-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.11.0-2/get_patch +patch_hash = 764530d812ac161c9eab02a8cfaec67c871fcfc5548e29fd3d488070913d4e94 -source_url = https://github.com/google/googletest/archive/release-1.10.0.zip -source_filename = gtest-1.10.0.zip -source_hash = 94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91 +[provide] +gtest = gtest_dep +gtest_main = gtest_main_dep +gmock = gmock_dep +gmock_main = gmock_main_dep -patch_url = https://wrapdb.mesonbuild.com/v1/projects/gtest/1.10.0/1/get_zip -patch_filename = gtest-1.10.0-1-wrap.zip -patch_hash = 04ff14e8880e4e465f6260221e9dfd56fea6bc7cce4c4aff0dc528e4a2c8f514 diff -Nru mpd-0.23.6/subprojects/sqlite3.wrap mpd-0.23.8/subprojects/sqlite3.wrap --- mpd-0.23.6/subprojects/sqlite3.wrap 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/subprojects/sqlite3.wrap 2022-07-08 23:05:38.000000000 +0000 @@ -1,11 +1,11 @@ [wrap-file] -directory = sqlite-amalgamation-3340100 -source_url = https://www.sqlite.org/2021/sqlite-amalgamation-3340100.zip -source_filename = sqlite-amalgamation-3340100.zip -source_hash = e0b1c0345fe4338b936e17da8e1bd88366cd210e576834546977f040c12a8f68 -patch_url = https://wrapdb.mesonbuild.com/v1/projects/sqlite3/3.34.1/1/get_zip -patch_filename = sqlite3-3.34.1-1-wrap.zip -patch_hash = cba9e47bdb4c02f88fadaae8deab357218d32562c6b86ce7ba0c72f107044360 +directory = sqlite-amalgamation-3380000 +source_url = https://sqlite.org/2022/sqlite-amalgamation-3380000.zip +source_filename = sqlite-amalgamation-3380000.zip +source_hash = e055f6054e97747a135c89e36520c0a423249e8a91c5fc445163f4a6adb20df6 +patch_filename = sqlite3_3.38.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.38.0-1/get_patch +patch_hash = 49e30bf010ff63ab772d5417885e6905379025ceac80382e292c6dbd3a9da744 [provide] sqlite3 = sqlite3_dep diff -Nru mpd-0.23.6/subprojects/vorbis.wrap mpd-0.23.8/subprojects/vorbis.wrap --- mpd-0.23.6/subprojects/vorbis.wrap 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/subprojects/vorbis.wrap 2022-07-08 23:05:38.000000000 +0000 @@ -1,11 +1,12 @@ [wrap-file] -directory = libvorbis-1.3.5 -source_url = http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz -source_filename = libvorbis-1.3.5.tar.xz -source_hash = 54f94a9527ff0a88477be0a71c0bab09a4c3febe0ed878b24824906cd4b0e1d1 -patch_url = https://wrapdb.mesonbuild.com/v1/projects/vorbis/1.3.5/7/get_zip -patch_filename = vorbis-1.3.5-7-wrap.zip -patch_hash = 7f4d3f9253925196461d52fd4553aad4468fd845560d1ff6c2eb6a012cf64fb0 +directory = libvorbis-1.3.7 +source_url = https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz +source_filename = libvorbis-1.3.7.tar.xz +source_hash = b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b +patch_filename = vorbis_1.3.7-3_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-3/get_patch +patch_hash = 6cb90a61ede8c64d3e8e379b96dcc800c9dd69e925122b3d73d8f59a563c3afa +wrapdb_version = 1.3.7-3 [provide] vorbis = vorbis_dep diff -Nru mpd-0.23.6/test/DumpOgg.cxx mpd-0.23.8/test/DumpOgg.cxx --- mpd-0.23.6/test/DumpOgg.cxx 1970-01-01 00:00:00.000000000 +0000 +++ mpd-0.23.8/test/DumpOgg.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -0,0 +1,68 @@ +/* + * Copyright 2003-2022 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. + */ + +#include "lib/xiph/OggSyncState.hxx" +#include "lib/xiph/OggStreamState.hxx" +#include "config/Data.hxx" +#include "input/Init.hxx" +#include "input/InputStream.hxx" +#include "input/Reader.hxx" +#include "event/Thread.hxx" +#include "util/PrintException.hxx" + +#include +#include + +int +main(int argc, char **argv) noexcept +try { + if (argc != 2) { + fprintf(stderr, "Usage: DumpOgg FILE\n"); + return EXIT_FAILURE; + } + + const char *path = argv[1]; + + EventThread io_thread; + io_thread.Start(); + + const ScopeInputPluginsInit input_plugins_init(ConfigData(), + io_thread.GetEventLoop()); + + Mutex mutex; + auto is = InputStream::OpenReady(path, mutex); + + InputStreamReader reader{*is}; + + OggSyncState sync{reader}; + + while (true) { + ogg_page page; + if (!sync.ExpectPage(page)) + break; + + printf("page offset=%" PRIu64 " serial=%d\n", + sync.GetStartOffset(), ogg_page_serialno(&page)); + } + + return EXIT_SUCCESS; +} catch (...) { + PrintException(std::current_exception()); + return EXIT_FAILURE; +} diff -Nru mpd-0.23.6/test/meson.build mpd-0.23.8/test/meson.build --- mpd-0.23.6/test/meson.build 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/test/meson.build 2022-07-08 23:05:38.000000000 +0000 @@ -490,6 +490,19 @@ ], ) endif + +if ogg_dep.found() + executable( + 'DumpOgg', + 'DumpOgg.cxx', + include_directories: inc, + dependencies: [ + ogg_dep, + input_glue_dep, + archive_glue_dep, + ], + ) +endif # # Filter @@ -559,7 +572,7 @@ # Encoder # -if encoder_glue_dep.found() +if need_encoder executable( 'run_encoder', 'run_encoder.cxx', diff -Nru mpd-0.23.6/test/RunCurl.cxx mpd-0.23.8/test/RunCurl.cxx --- mpd-0.23.6/test/RunCurl.cxx 2022-03-14 17:55:47.000000000 +0000 +++ mpd-0.23.8/test/RunCurl.cxx 2022-07-08 23:05:38.000000000 +0000 @@ -41,8 +41,7 @@ } /* virtual methods from CurlResponseHandler */ - void OnHeaders(unsigned status, - std::multimap &&headers) override { + void OnHeaders(unsigned status, Curl::Headers &&headers) override { fprintf(stderr, "status: %u\n", status); for (const auto &i : headers) fprintf(stderr, "%s: %s\n",