From d63e2c26416de3c65e4d6b71c2ef859b60372bfa Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 2 Sep 2019 20:01:30 +0200 Subject: [PATCH] db/update: scan CUE playlist contents This commit adds a PlaylistPlugin attribute "as_folder" which for now is only enabled in the "CUE" playlist plugin (which handles separate "*.cue" files). If a playlist with this flag set is being scanned during database update, it will be parsed and its contents will be added to the database. This allows clients to inspect them like directories and its contents will be searchable. Closes https://github.com/MusicPlayerDaemon/MPD/issues/39 --- NEWS | 2 + src/db/plugins/simple/Directory.hxx | 7 +++ src/db/plugins/simple/DirectorySave.cxx | 5 ++ src/db/update/Playlist.cxx | 69 +++++++++++++++++++++- src/db/update/Walk.hxx | 5 ++ src/playlist/PlaylistPlugin.hxx | 12 ++++ src/playlist/plugins/CuePlaylistPlugin.cxx | 1 + 7 files changed, 100 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 6ae634e73..0caf58e5d 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ ver 0.22 (not yet released) - ffmpeg: allow partial reads * archive - iso9660: support seeking +* playlist + - cue: integrate contents in database * decoder - mad: remove option "gapless", always do gapless - sidplay: add option "default_genre" diff --git a/src/db/plugins/simple/Directory.hxx b/src/db/plugins/simple/Directory.hxx index 32c20c061..1647dbfad 100644 --- a/src/db/plugins/simple/Directory.hxx +++ b/src/db/plugins/simple/Directory.hxx @@ -44,6 +44,12 @@ static constexpr unsigned DEVICE_INARCHIVE = -1; */ static constexpr unsigned DEVICE_CONTAINER = -2; +/** + * Virtual directory that is really a playlist file (special value for + * Directory::device). + */ +static constexpr unsigned DEVICE_PLAYLIST = -3; + class SongFilter; struct Directory { @@ -117,6 +123,7 @@ public: */ bool IsReallyAFile() const noexcept { return device == DEVICE_INARCHIVE || + device == DEVICE_PLAYLIST || device == DEVICE_CONTAINER; } diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx index 1dedba063..ade511e97 100644 --- a/src/db/plugins/simple/DirectorySave.cxx +++ b/src/db/plugins/simple/DirectorySave.cxx @@ -50,6 +50,9 @@ DeviceToTypeString(unsigned device) noexcept case DEVICE_CONTAINER: return "container"; + case DEVICE_PLAYLIST: + return "playlist"; + default: return nullptr; } @@ -63,6 +66,8 @@ ParseTypeString(const char *type) noexcept return DEVICE_INARCHIVE; else if (StringIsEqual(type, "container")) return DEVICE_CONTAINER; + else if (StringIsEqual(type, "playlist")) + return DEVICE_PLAYLIST; else return 0; } diff --git a/src/db/update/Playlist.cxx b/src/db/update/Playlist.cxx index 93275dc91..4d4a9bb1d 100644 --- a/src/db/update/Playlist.cxx +++ b/src/db/update/Playlist.cxx @@ -18,24 +18,91 @@ */ #include "Walk.hxx" +#include "UpdateDomain.hxx" #include "db/DatabaseLock.hxx" #include "db/PlaylistVector.hxx" #include "db/plugins/simple/Directory.hxx" +#include "song/DetachedSong.hxx" +#include "input/InputStream.hxx" +#include "playlist/PlaylistPlugin.hxx" #include "playlist/PlaylistRegistry.hxx" +#include "playlist/PlaylistStream.hxx" +#include "playlist/SongEnumerator.hxx" #include "storage/FileInfo.hxx" +#include "storage/StorageInterface.hxx" +#include "util/StringFormat.hxx" +#include "Log.hxx" + +void +UpdateWalk::UpdatePlaylistFile(Directory &parent, const char *name, + const StorageFileInfo &info, + const PlaylistPlugin &plugin) noexcept +{ + assert(plugin.open_stream); + + Directory *directory = + LockMakeVirtualDirectoryIfModified(parent, name, info, + DEVICE_PLAYLIST); + if (directory == nullptr) + /* not modified */ + return; + + const auto uri_utf8 = storage.MapUTF8(directory->GetPath()); + + FormatDebug(update_domain, "scanning playlist '%s'", uri_utf8.c_str()); + + try { + Mutex mutex; + auto e = plugin.open_stream(InputStream::OpenReady(uri_utf8.c_str(), + mutex)); + if (!e) { + /* unsupported URI? roll back.. */ + editor.LockDeleteDirectory(directory); + return; + } + + unsigned track = 0; + + while (true) { + auto song = e->NextSong(); + if (!song) + break; + + auto db_song = std::make_unique(std::move(*song), + *directory); + db_song->target = "../" + db_song->filename; + db_song->filename = StringFormat<64>("track%04u", + ++track); + + { + const ScopeDatabaseLock protect; + directory->AddSong(std::move(db_song)); + } + } + } catch (...) { + FormatError(std::current_exception(), + "Failed to scan playlist '%s'", uri_utf8.c_str()); + editor.LockDeleteDirectory(directory); + } +} bool UpdateWalk::UpdatePlaylistFile(Directory &directory, const char *name, const char *suffix, const StorageFileInfo &info) noexcept { - if (!playlist_suffix_supported(suffix)) + const auto *const plugin = FindPlaylistPluginBySuffix(suffix); + if (plugin == nullptr) return false; + if (plugin->as_folder) + UpdatePlaylistFile(directory, name, info, *plugin); + PlaylistInfo pi(name, info.mtime); const ScopeDatabaseLock protect; if (directory.playlists.UpdateOrInsert(std::move(pi))) modified = true; + return true; } diff --git a/src/db/update/Walk.hxx b/src/db/update/Walk.hxx index e54e398b9..72b0e7822 100644 --- a/src/db/update/Walk.hxx +++ b/src/db/update/Walk.hxx @@ -30,6 +30,7 @@ struct StorageFileInfo; struct Directory; struct ArchivePlugin; +struct PlaylistPlugin; class ArchiveFile; class Storage; class ExcludeList; @@ -118,6 +119,10 @@ private: } #endif + void UpdatePlaylistFile(Directory &parent, const char *name, + const StorageFileInfo &info, + const PlaylistPlugin &plugin) noexcept; + bool UpdatePlaylistFile(Directory &directory, const char *name, const char *suffix, const StorageFileInfo &info) noexcept; diff --git a/src/playlist/PlaylistPlugin.hxx b/src/playlist/PlaylistPlugin.hxx index 4661ed967..449435ccb 100644 --- a/src/playlist/PlaylistPlugin.hxx +++ b/src/playlist/PlaylistPlugin.hxx @@ -69,6 +69,12 @@ struct PlaylistPlugin { const char *const*suffixes = nullptr; const char *const*mime_types = nullptr; + /** + * If true, then playlists of this type are shown in the + * database as folders. + */ + bool as_folder = false; + constexpr PlaylistPlugin(const char *_name, std::unique_ptr (*_open_uri)(const char *uri, Mutex &mutex)) noexcept @@ -104,6 +110,12 @@ struct PlaylistPlugin { return copy; } + constexpr auto WithAsFolder(bool value=true) noexcept { + auto copy = *this; + copy.as_folder = value; + return copy; + } + /** * Does the plugin announce the specified URI scheme? */ diff --git a/src/playlist/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx index 137eb4e90..b8d645c94 100644 --- a/src/playlist/plugins/CuePlaylistPlugin.cxx +++ b/src/playlist/plugins/CuePlaylistPlugin.cxx @@ -72,5 +72,6 @@ static const char *const cue_playlist_mime_types[] = { const PlaylistPlugin cue_playlist_plugin = PlaylistPlugin("cue", cue_playlist_open_stream) + .WithAsFolder() .WithSuffixes(cue_playlist_suffixes) .WithMimeTypes(cue_playlist_mime_types);