diff --git a/NEWS b/NEWS index 4ccc3c7af..1c898d2c2 100644 --- a/NEWS +++ b/NEWS @@ -43,11 +43,14 @@ ver 0.24 (not yet released) * remove Boost dependency * require libfmt 7 or later -ver 0.23.13 (not yet released) +ver 0.23.13 (2023/05/22) * input - curl: fix busy loop after connection failed + - curl: hide "404" log messages for non-existent ".mpdignore" files * archive - zzip: fix crash bug +* database + - simple: reveal hidden songs after deleting containing CUE * decoder - ffmpeg: reorder to a lower priority than "gme" - gme: require GME 0.6 or later diff --git a/doc/user.rst b/doc/user.rst index 7eb891229..44d4285f4 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -670,6 +670,11 @@ If ReplayGain is enabled, then the setting ``replaygain_preamp`` is set to a value (in dB) between ``-15`` and ``15``. This is the gain applied to songs with ReplayGain tags. +On songs without ReplayGain tags, the setting +``replaygain_missing_preamp`` is used instead. If this setting is not +configured, then no ReplayGain is applied to such songs, and they will +appear too loud. + ReplayGain is usually implemented with a software volume filter (which prevents `Bit-perfect playback`_). To use a hardware mixer, set ``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx index e0b3bbcd5..7f58d1429 100644 --- a/src/db/plugins/simple/Directory.cxx +++ b/src/db/plugins/simple/Directory.cxx @@ -111,6 +111,18 @@ Directory::LookupTargetSong(std::string_view target) noexcept return lr.directory->FindSong(lr.rest); } +void +Directory::ClearInPlaylist() noexcept +{ + assert(holding_db_lock()); + + for (auto &child : children) + child.ClearInPlaylist(); + + for (auto &song : songs) + song.in_playlist = false; +} + void Directory::PruneEmpty() noexcept { diff --git a/src/db/plugins/simple/Directory.hxx b/src/db/plugins/simple/Directory.hxx index 5b787280b..852c792c6 100644 --- a/src/db/plugins/simple/Directory.hxx +++ b/src/db/plugins/simple/Directory.hxx @@ -257,6 +257,14 @@ public: */ SongPtr RemoveSong(Song *song) noexcept; + /** + * Recursively walk through the whole tree and set all + * `Song::in_playlist` fields to `false`. + * + * Caller must lock the #db_mutex. + */ + void ClearInPlaylist() noexcept; + /** * Caller must lock the #db_mutex. */ diff --git a/src/db/update/Walk.cxx b/src/db/update/Walk.cxx index 88420db06..dfa8fddd5 100644 --- a/src/db/update/Walk.cxx +++ b/src/db/update/Walk.cxx @@ -515,6 +515,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept { const ScopeDatabaseLock protect; + root.ClearInPlaylist(); PurgeDanglingFromPlaylists(root); } diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx index a5e54c98c..12b9fdbaa 100644 --- a/src/event/Loop.cxx +++ b/src/event/Loop.cxx @@ -275,10 +275,8 @@ EventLoop::Run() noexcept #endif assert(IsInside()); - assert(!quit); #ifdef HAVE_THREADED_EVENT_LOOP - assert(!quit_injected); - assert(alive); + assert(alive || quit_injected); assert(busy); wake_event.Schedule(SocketEvent::READ); @@ -303,7 +301,7 @@ EventLoop::Run() noexcept FlushClockCaches(); - do { + while (!quit) { again = false; /* invoke timers */ @@ -365,7 +363,7 @@ EventLoop::Run() noexcept socket_event.Dispatch(); } - } while (!quit); + } #ifdef HAVE_THREADED_EVENT_LOOP #ifndef NDEBUG diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index 7045bddc1..faac313c6 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -463,7 +463,6 @@ CurlInputStream::InitEasy() request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases); request->SetOption(CURLOPT_FOLLOWLOCATION, 1L); request->SetOption(CURLOPT_MAXREDIRS, 5L); - request->SetOption(CURLOPT_FAILONERROR, 1L); /* this option eliminates the probe request when username/password are specified */ diff --git a/src/lib/crypto/meson.build b/src/lib/crypto/meson.build index f3a99cb86..a10548ff5 100644 --- a/src/lib/crypto/meson.build +++ b/src/lib/crypto/meson.build @@ -18,13 +18,13 @@ endif conf.set('HAVE_MD5', crypto_md5_dep.found()) -if libavutil_dep.found() +if ffmpeg_util_dep.found() crypto_base64 = static_library( 'crypto_base64', 'Base64.cxx', include_directories: inc, dependencies: [ - libavutil_dep, + ffmpeg_util_dep, ], ) diff --git a/src/lib/ffmpeg/LogError.cxx b/src/lib/ffmpeg/LogError.cxx deleted file mode 100644 index 9abb37ed6..000000000 --- a/src/lib/ffmpeg/LogError.cxx +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -// Copyright The Music Player Daemon Project - -#include "LogError.hxx" -#include "Domain.hxx" -#include "Log.hxx" - -extern "C" { -#include -} - -void -LogFfmpegError(int errnum) -{ - char msg[256]; - av_strerror(errnum, msg, sizeof(msg)); - LogError(ffmpeg_domain, msg); -} - -void -LogFfmpegError(int errnum, const char *prefix) -{ - char msg[256]; - av_strerror(errnum, msg, sizeof(msg)); - FmtError(ffmpeg_domain, "{}: {}", prefix, msg); -} diff --git a/src/lib/ffmpeg/LogError.hxx b/src/lib/ffmpeg/LogError.hxx deleted file mode 100644 index 720ae1bfb..000000000 --- a/src/lib/ffmpeg/LogError.hxx +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -// Copyright The Music Player Daemon Project - -#ifndef MPD_FFMPEG_LOG_ERROR_HXX -#define MPD_FFMPEG_LOG_ERROR_HXX - -void -LogFfmpegError(int errnum); - -void -LogFfmpegError(int errnum, const char *prefix); - -#endif diff --git a/src/lib/ffmpeg/meson.build b/src/lib/ffmpeg/meson.build index 4cac26867..a1c75b202 100644 --- a/src/lib/ffmpeg/meson.build +++ b/src/lib/ffmpeg/meson.build @@ -13,6 +13,30 @@ else endif conf.set('HAVE_LIBAVFILTER', libavfilter_dep.found()) +if not libavutil_dep.found() + ffmpeg_util_dep = dependency('', required: false) + ffmpeg_dep = dependency('', required: false) + subdir_done() +endif + +ffmpeg_util = static_library( + 'ffmpeg_util', + 'Interleave.cxx', + 'Error.cxx', + include_directories: inc, + dependencies: [ + fmt_dep, + libavutil_dep, + ], +) + +ffmpeg_util_dep = declare_dependency( + link_with: ffmpeg_util, + dependencies: [ + libavutil_dep, + ], +) + if not enable_ffmpeg ffmpeg_dep = dependency('', required: false) subdir_done() @@ -30,17 +54,16 @@ ffmpeg = static_library( 'ffmpeg', 'Init.cxx', 'Interleave.cxx', - 'LogError.cxx', 'LogCallback.cxx', 'Error.cxx', 'Domain.cxx', ffmpeg_sources, include_directories: inc, dependencies: [ + ffmpeg_util_dep, libavformat_dep, libavcodec_dep, libavfilter_dep, - libavutil_dep, log_dep, ], ) @@ -48,9 +71,9 @@ ffmpeg = static_library( ffmpeg_dep = declare_dependency( link_with: ffmpeg, dependencies: [ + ffmpeg_util_dep, libavformat_dep, libavcodec_dep, libavfilter_dep, - libavutil_dep, ], ) diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx index 6ebb1bd1c..9c5a297ac 100644 --- a/src/storage/plugins/CurlStorage.cxx +++ b/src/storage/plugins/CurlStorage.cxx @@ -6,6 +6,7 @@ #include "storage/StorageInterface.hxx" #include "storage/FileInfo.hxx" #include "storage/MemoryDirectoryReader.hxx" +#include "lib/curl/HttpStatusError.hxx" #include "lib/curl/Init.hxx" #include "lib/curl/Global.hxx" #include "lib/curl/Slist.hxx" @@ -14,7 +15,6 @@ #include "lib/curl/Handler.hxx" #include "lib/curl/Escape.hxx" #include "lib/expat/ExpatParser.hxx" -#include "lib/fmt/RuntimeError.hxx" #include "lib/fmt/ToBuffer.hxx" #include "fs/Traits.hxx" #include "event/InjectEvent.hxx" @@ -286,8 +286,9 @@ private: /* virtual methods from CurlResponseHandler */ void OnHeaders(unsigned status, Curl::Headers &&headers) final { if (status != 207) - throw FmtRuntimeError("Status {} from WebDAV server; expected \"207 Multi-Status\"", - status); + throw HttpStatusError(status, + FmtBuffer<80>("Status {} from WebDAV server; expected \"207 Multi-Status\"", + status)); if (!IsXmlContentType(headers)) throw std::runtime_error("Unexpected Content-Type from WebDAV server"); diff --git a/src/util/ScopeExit.hxx b/src/util/ScopeExit.hxx index 5c6fc9a7e..c6278a977 100644 --- a/src/util/ScopeExit.hxx +++ b/src/util/ScopeExit.hxx @@ -14,14 +14,17 @@ class ScopeExitGuard : F { bool enabled = true; public: - explicit ScopeExitGuard(F &&f):F(std::forward(f)) {} + explicit ScopeExitGuard(F &&f) noexcept:F(std::forward(f)) {} - ScopeExitGuard(ScopeExitGuard &&src) - :F(std::move(src)), enabled(src.enabled) { - src.enabled = false; - } + ScopeExitGuard(ScopeExitGuard &&src) noexcept + :F(std::move(src)), + enabled(std::exchange(src.enabled, false)) {} - ~ScopeExitGuard() { + /* destructors are "noexcept" by default; this explicit + "noexcept" declaration allows the destructor to throw if + the function can throw; without this, a throwing function + would std::terminate() */ + ~ScopeExitGuard() noexcept(noexcept(std::declval()())) { if (enabled) F::operator()(); } @@ -38,7 +41,7 @@ struct ScopeExitTag { parantheses at the end of the expression AtScopeExit() call */ template - ScopeExitGuard operator+(F &&f) { + ScopeExitGuard operator+(F &&f) noexcept { return ScopeExitGuard(std::forward(f)); } };