From 2aed7378ccc9b9abd9efae3417e15c44478e6556 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 14 Mar 2022 17:44:39 +0100
Subject: [PATCH] TagAny: support CUE tracks

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1482
---
 NEWS           |  1 +
 src/TagAny.cxx | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+)

diff --git a/NEWS b/NEWS
index cdc114954..d1276ce49 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
 ver 0.23.6 (not yet released)
 * protocol
   - support filename "cover.webp" for "albumart" command
+  - support "readcomments" and "readpicture" on CUE tracks
 * decoder
   - ffmpeg: fix end-of-file check (update stuck at empty files)
   - opus: fix "readpicture" on Opus files
diff --git a/src/TagAny.cxx b/src/TagAny.cxx
index 6f875846e..2fd573fae 100644
--- a/src/TagAny.cxx
+++ b/src/TagAny.cxx
@@ -21,12 +21,16 @@
 #include "TagStream.hxx"
 #include "TagFile.hxx"
 #include "tag/Generic.hxx"
+#include "song/LightSong.hxx"
+#include "db/Interface.hxx"
 #include "storage/StorageInterface.hxx"
 #include "client/Client.hxx"
 #include "protocol/Ack.hxx"
 #include "fs/AllocatedPath.hxx"
 #include "input/InputStream.hxx"
 #include "util/Compiler.h"
+#include "util/ScopeExit.hxx"
+#include "util/StringCompare.hxx"
 #include "util/UriExtract.hxx"
 #include "LocateUri.hxx"
 
@@ -51,10 +55,67 @@ TagScanFile(const Path path_fs, TagHandler &handler)
 	ScanGenericTags(path_fs, handler);
 }
 
+#ifdef ENABLE_DATABASE
+
+/**
+ * Collapse "../" prefixes in a URI relative to the specified base
+ * URI.
+ */
+static std::string
+ResolveUri(std::string_view base, const char *relative)
+{
+	while (true) {
+		const char *rest = StringAfterPrefix(relative, "../");
+		if (rest == nullptr)
+			break;
+
+		if (base == ".")
+			throw ProtocolError(ACK_ERROR_NO_EXIST, "Bad real URI");
+
+		base = PathTraitsUTF8::GetParent(base);
+		relative = rest;
+	}
+
+	return PathTraitsUTF8::Build(base, relative);
+}
+
+/**
+ * Look up the specified song in the database and return its
+ * (resolved) "real" URI.
+ */
+static std::string
+GetRealSongUri(Client &client, std::string_view uri)
+{
+	const auto &db = client.GetDatabaseOrThrow();
+
+	const auto *song = db.GetSong(uri);
+	if (song == nullptr)
+		throw ProtocolError(ACK_ERROR_NO_EXIST, "No such song");
+
+	AtScopeExit(&db, song) { db.ReturnSong(song); };
+
+	if (song->real_uri == nullptr)
+		return {};
+
+	return ResolveUri(PathTraitsUTF8::GetParent(uri), song->real_uri);
+}
+
+#endif
+
 static void
 TagScanDatabase(Client &client, const char *uri, TagHandler &handler)
 {
 #ifdef ENABLE_DATABASE
+	const auto real_uri = GetRealSongUri(client, uri);
+
+	if (!real_uri.empty()) {
+		uri = real_uri.c_str();
+
+		// TODO: support absolute paths?
+		if (uri_has_scheme(uri))
+			return TagScanStream(uri, handler);
+	}
+
 	const Storage *storage = client.GetStorage();
 	if (storage == nullptr) {
 #else