From 6d9b452fde3c5b53994628374cda0e9d52da54f7 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 13:48:13 +0200
Subject: [PATCH 01/13] lib/ffmpeg/LogError: remove unused library

---
 src/lib/ffmpeg/LogError.cxx | 42 -------------------------------------
 src/lib/ffmpeg/LogError.hxx | 29 -------------------------
 src/lib/ffmpeg/meson.build  |  1 -
 3 files changed, 72 deletions(-)
 delete mode 100644 src/lib/ffmpeg/LogError.cxx
 delete mode 100644 src/lib/ffmpeg/LogError.hxx

diff --git a/src/lib/ffmpeg/LogError.cxx b/src/lib/ffmpeg/LogError.cxx
deleted file mode 100644
index 7cb02278c..000000000
--- a/src/lib/ffmpeg/LogError.cxx
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2003-2021 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "LogError.hxx"
-#include "Domain.hxx"
-#include "Log.hxx"
-
-extern "C" {
-#include <libavutil/error.h>
-}
-
-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 8951caba6..000000000
--- a/src/lib/ffmpeg/LogError.hxx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2003-2021 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef MPD_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 43ceef505..d570beb42 100644
--- a/src/lib/ffmpeg/meson.build
+++ b/src/lib/ffmpeg/meson.build
@@ -30,7 +30,6 @@ ffmpeg = static_library(
   'ffmpeg',
   'Init.cxx',
   'Interleave.cxx',
-  'LogError.cxx',
   'LogCallback.cxx',
   'Error.cxx',
   'Domain.cxx',

From 7c759ba8b0ba6ae7125d1baad1f9b9f096615b61 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 13:49:38 +0200
Subject: [PATCH 02/13] lib/ffmpeg/meson.build: move libavutil helpers into
 separate library

---
 src/lib/ffmpeg/meson.build | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/lib/ffmpeg/meson.build b/src/lib/ffmpeg/meson.build
index d570beb42..3e62881c0 100644
--- a/src/lib/ffmpeg/meson.build
+++ b/src/lib/ffmpeg/meson.build
@@ -13,6 +13,29 @@ 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: [
+    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()
@@ -36,10 +59,10 @@ ffmpeg = static_library(
   ffmpeg_sources,
   include_directories: inc,
   dependencies: [
+    ffmpeg_util_dep,
     libavformat_dep,
     libavcodec_dep,
     libavfilter_dep,
-    libavutil_dep,
     log_dep,
   ],
 )
@@ -47,9 +70,9 @@ ffmpeg = static_library(
 ffmpeg_dep = declare_dependency(
   link_with: ffmpeg,
   dependencies: [
+    ffmpeg_util_dep,
     libavformat_dep,
     libavcodec_dep,
     libavfilter_dep,
-    libavutil_dep,
   ],
 )

From 2fa8c7d2db8e2a00d74cce404a22cfd581883ba9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 14:04:01 +0200
Subject: [PATCH 03/13] lib/crypto/meson.build: link with `ffmpeg_util_dep`

This adds `MakeFfmpegError()` to the executable and fixes a linker
failure when `libavutil` is available, but `libavformat` and
`libavcodec` are not.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1786
---
 src/lib/crypto/meson.build | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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,
     ],
   )
 

From 2ab03a0914023d2150f38fa11c48ddedd90d0c94 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 14:42:02 +0200
Subject: [PATCH 04/13] util/ScopeExit: allow the function to throw

Fixes crash inside AtScopeExit() in the WASAPI output plugin.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1759
---
 src/util/ScopeExit.hxx | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/util/ScopeExit.hxx b/src/util/ScopeExit.hxx
index b64a35cf0..0fecf6c1e 100644
--- a/src/util/ScopeExit.hxx
+++ b/src/util/ScopeExit.hxx
@@ -47,7 +47,11 @@ public:
 		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(F::operator()())) {
 		if (enabled)
 			F::operator()();
 	}

From 70b451db7b457f505f4aa03c08cfd9302931d5f0 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 14:43:36 +0200
Subject: [PATCH 05/13] util/ScopeExit: add `noexcept`

---
 src/util/ScopeExit.hxx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/util/ScopeExit.hxx b/src/util/ScopeExit.hxx
index 0fecf6c1e..8dc0e2c70 100644
--- a/src/util/ScopeExit.hxx
+++ b/src/util/ScopeExit.hxx
@@ -40,9 +40,9 @@ class ScopeExitGuard : F {
 	bool enabled = true;
 
 public:
-	explicit ScopeExitGuard(F &&f):F(std::forward<F>(f)) {}
+	explicit ScopeExitGuard(F &&f) noexcept:F(std::forward<F>(f)) {}
 
-	ScopeExitGuard(ScopeExitGuard &&src)
+	ScopeExitGuard(ScopeExitGuard &&src) noexcept
 		:F(std::move(src)), enabled(src.enabled) {
 		src.enabled = false;
 	}
@@ -68,7 +68,7 @@ struct ScopeExitTag {
 	   parantheses at the end of the expression AtScopeExit()
 	   call */
 	template<typename F>
-	ScopeExitGuard<F> operator+(F &&f) {
+	ScopeExitGuard<F> operator+(F &&f) noexcept {
 		return ScopeExitGuard<F>(std::forward<F>(f));
 	}
 };

From 7a99a7008c0807fb6b9dff230d45847a584e6de9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 14:44:32 +0200
Subject: [PATCH 06/13] util/ScopeExit: use std::exchange()

---
 src/util/ScopeExit.hxx | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/util/ScopeExit.hxx b/src/util/ScopeExit.hxx
index 8dc0e2c70..a53282676 100644
--- a/src/util/ScopeExit.hxx
+++ b/src/util/ScopeExit.hxx
@@ -43,9 +43,8 @@ public:
 	explicit ScopeExitGuard(F &&f) noexcept:F(std::forward<F>(f)) {}
 
 	ScopeExitGuard(ScopeExitGuard &&src) noexcept
-		:F(std::move(src)), enabled(src.enabled) {
-		src.enabled = false;
-	}
+		:F(std::move(src)),
+		 enabled(std::exchange(src.enabled, false)) {}
 
 	/* destructors are "noexcept" by default; this explicit
 	   "noexcept" declaration allows the destructor to throw if

From dc127f39a787a2919a5aa90b75b85753ae64c012 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 15:01:00 +0200
Subject: [PATCH 07/13] util/ScopeExit: use std::declval()

Fixes GCC 10 error:

 error: cannot call member function `Foo` without object
---
 src/util/ScopeExit.hxx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/util/ScopeExit.hxx b/src/util/ScopeExit.hxx
index a53282676..b50468702 100644
--- a/src/util/ScopeExit.hxx
+++ b/src/util/ScopeExit.hxx
@@ -50,7 +50,7 @@ public:
 	   "noexcept" declaration allows the destructor to throw if
 	   the function can throw; without this, a throwing function
 	   would std::terminate() */
-	~ScopeExitGuard() noexcept(noexcept(F::operator()())) {
+	~ScopeExitGuard() noexcept(noexcept(std::declval<F>()())) {
 		if (enabled)
 			F::operator()();
 	}

From 068cd559e11dd5b95229d4e18710dffe27fa9ae9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 15:41:20 +0200
Subject: [PATCH 08/13] db/update/Walk: clear `Song::in_playlist`

Without clearing all `in_playlist` flags, the songs will never be
revealed again if they were hidden once by a CUE sheet, not even after
the CUE sheet gets deleted or modified.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1784
---
 NEWS                                |  2 ++
 src/db/plugins/simple/Directory.cxx | 12 ++++++++++++
 src/db/plugins/simple/Directory.hxx |  8 ++++++++
 src/db/update/Walk.cxx              |  1 +
 4 files changed, 23 insertions(+)

diff --git a/NEWS b/NEWS
index 69d88b777..cd9636d6f 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ ver 0.23.13 (not yet released)
   - curl: fix busy loop after connection failed
 * 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/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx
index 313709a31..f00215355 100644
--- a/src/db/plugins/simple/Directory.cxx
+++ b/src/db/plugins/simple/Directory.cxx
@@ -126,6 +126,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 e13b556ff..3b4b280fe 100644
--- a/src/db/plugins/simple/Directory.hxx
+++ b/src/db/plugins/simple/Directory.hxx
@@ -287,6 +287,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 be71e7195..4fdec28d9 100644
--- a/src/db/update/Walk.cxx
+++ b/src/db/update/Walk.cxx
@@ -531,6 +531,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
 
 	{
 		const ScopeDatabaseLock protect;
+		root.ClearInPlaylist();
 		PurgeDanglingFromPlaylists(root);
 	}
 

From ed890a273a9e579cbd471ef74193ec0ae25cd6fb Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 16:15:52 +0200
Subject: [PATCH 09/13] doc/user.rst: document the `replaygain_missing_preamp`
 setting

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1785
---
 doc/user.rst | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/doc/user.rst b/doc/user.rst
index 57918fad3..489df08ad 100644
--- a/doc/user.rst
+++ b/doc/user.rst
@@ -611,6 +611,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

From 740cbe9e028814b09cd720d3fe8a117ae2f233cb Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 18:12:54 +0200
Subject: [PATCH 10/13] event/Loop: remove failing assert()

The `assert(!quit)` can fail if the `EventThread` gets stopped before
it enters `EventLoop::Run()`. There is a similar problem with `alive`,
which gets reset by `EventThread::Stop()`.

If that happens, then `EventLoop::Run()` should return immediately
without handling any events.
---
 src/event/Loop.cxx | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx
index be0f1804b..1b99cd91b 100644
--- a/src/event/Loop.cxx
+++ b/src/event/Loop.cxx
@@ -272,9 +272,8 @@ EventLoop::Run() noexcept
 #endif
 
 	assert(IsInside());
-	assert(!quit);
 #ifdef HAVE_THREADED_EVENT_LOOP
-	assert(alive);
+	assert(alive || quit);
 	assert(busy);
 
 	wake_event.Schedule(SocketEvent::READ);
@@ -299,7 +298,7 @@ EventLoop::Run() noexcept
 
 	steady_clock_cache.flush();
 
-	do {
+	while (!quit) {
 		again = false;
 
 		/* invoke timers */
@@ -361,7 +360,7 @@ EventLoop::Run() noexcept
 
 			socket_event.Dispatch();
 		}
-	} while (!quit);
+	}
 
 #ifdef HAVE_THREADED_EVENT_LOOP
 #ifndef NDEBUG

From 5cd86e272f28b4a204bdf19d6573e3b7cd68e663 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 18:47:00 +0200
Subject: [PATCH 11/13] input/curl: disable `CURLOPT_FAILONERROR`

Let OnHeaders() check the status.

The status checking code was added by commit 4f021cbced19dfc6 in 2011,
but in 2008, commit a8e81326d0 enabled `CURLOPT_FAILONERROR`, which
means the status checking code never had any effect.

This allows `LoadExcludeListOrLog()` to hide boring "404 Not Found"
log messages via `IsFileNotFound()`.
---
 NEWS                                  | 1 +
 src/input/plugins/CurlInputPlugin.cxx | 1 -
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index cd9636d6f..5d57af4a6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
 ver 0.23.13 (not yet released)
 * input
   - curl: fix busy loop after connection failed
+  - curl: hide "404" log messages for non-existent ".mpdignore" files
 * archive
   - zzip: fix crash bug
 * database
diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx
index e90ce8daf..e7a766658 100644
--- a/src/input/plugins/CurlInputPlugin.cxx
+++ b/src/input/plugins/CurlInputPlugin.cxx
@@ -417,7 +417,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 */

From d5bf128ceef6cfdeb029f1ab2ce4a9a73618f5cc Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 18:56:32 +0200
Subject: [PATCH 12/13] storage/curl: throw HttpStatusError

---
 src/storage/plugins/CurlStorage.cxx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx
index 1a6578f68..62107b536 100644
--- a/src/storage/plugins/CurlStorage.cxx
+++ b/src/storage/plugins/CurlStorage.cxx
@@ -22,6 +22,7 @@
 #include "storage/StorageInterface.hxx"
 #include "storage/FileInfo.hxx"
 #include "storage/MemoryDirectoryReader.hxx"
+#include "lib/curl/Error.hxx"
 #include "lib/curl/Init.hxx"
 #include "lib/curl/Global.hxx"
 #include "lib/curl/Slist.hxx"
@@ -300,8 +301,9 @@ private:
 	/* virtual methods from CurlResponseHandler */
 	void OnHeaders(unsigned status, Curl::Headers &&headers) final {
 		if (status != 207)
-			throw FormatRuntimeError("Status %d from WebDAV server; expected \"207 Multi-Status\"",
-						 status);
+			throw HttpStatusError(status,
+					      StringFormat<80>("Status %u from WebDAV server; expected \"207 Multi-Status\"",
+							       status).c_str());
 
 		if (!IsXmlContentType(headers))
 			throw std::runtime_error("Unexpected Content-Type from WebDAV server");

From 8842650c330f0383192c5accdbe895e48f548d16 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Mon, 22 May 2023 19:46:38 +0200
Subject: [PATCH 13/13] release v0.23.13

---
 NEWS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 5d57af4a6..df45c188a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-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