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
This commit is contained in:
parent
5fdb804a50
commit
d63e2c2641
2
NEWS
2
NEWS
@ -12,6 +12,8 @@ ver 0.22 (not yet released)
|
|||||||
- ffmpeg: allow partial reads
|
- ffmpeg: allow partial reads
|
||||||
* archive
|
* archive
|
||||||
- iso9660: support seeking
|
- iso9660: support seeking
|
||||||
|
* playlist
|
||||||
|
- cue: integrate contents in database
|
||||||
* decoder
|
* decoder
|
||||||
- mad: remove option "gapless", always do gapless
|
- mad: remove option "gapless", always do gapless
|
||||||
- sidplay: add option "default_genre"
|
- sidplay: add option "default_genre"
|
||||||
|
@ -44,6 +44,12 @@ static constexpr unsigned DEVICE_INARCHIVE = -1;
|
|||||||
*/
|
*/
|
||||||
static constexpr unsigned DEVICE_CONTAINER = -2;
|
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;
|
class SongFilter;
|
||||||
|
|
||||||
struct Directory {
|
struct Directory {
|
||||||
@ -117,6 +123,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool IsReallyAFile() const noexcept {
|
bool IsReallyAFile() const noexcept {
|
||||||
return device == DEVICE_INARCHIVE ||
|
return device == DEVICE_INARCHIVE ||
|
||||||
|
device == DEVICE_PLAYLIST ||
|
||||||
device == DEVICE_CONTAINER;
|
device == DEVICE_CONTAINER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ DeviceToTypeString(unsigned device) noexcept
|
|||||||
case DEVICE_CONTAINER:
|
case DEVICE_CONTAINER:
|
||||||
return "container";
|
return "container";
|
||||||
|
|
||||||
|
case DEVICE_PLAYLIST:
|
||||||
|
return "playlist";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -63,6 +66,8 @@ ParseTypeString(const char *type) noexcept
|
|||||||
return DEVICE_INARCHIVE;
|
return DEVICE_INARCHIVE;
|
||||||
else if (StringIsEqual(type, "container"))
|
else if (StringIsEqual(type, "container"))
|
||||||
return DEVICE_CONTAINER;
|
return DEVICE_CONTAINER;
|
||||||
|
else if (StringIsEqual(type, "playlist"))
|
||||||
|
return DEVICE_PLAYLIST;
|
||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,91 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Walk.hxx"
|
#include "Walk.hxx"
|
||||||
|
#include "UpdateDomain.hxx"
|
||||||
#include "db/DatabaseLock.hxx"
|
#include "db/DatabaseLock.hxx"
|
||||||
#include "db/PlaylistVector.hxx"
|
#include "db/PlaylistVector.hxx"
|
||||||
#include "db/plugins/simple/Directory.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/PlaylistRegistry.hxx"
|
||||||
|
#include "playlist/PlaylistStream.hxx"
|
||||||
|
#include "playlist/SongEnumerator.hxx"
|
||||||
#include "storage/FileInfo.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<Song>(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
|
bool
|
||||||
UpdateWalk::UpdatePlaylistFile(Directory &directory,
|
UpdateWalk::UpdatePlaylistFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const StorageFileInfo &info) noexcept
|
const StorageFileInfo &info) noexcept
|
||||||
{
|
{
|
||||||
if (!playlist_suffix_supported(suffix))
|
const auto *const plugin = FindPlaylistPluginBySuffix(suffix);
|
||||||
|
if (plugin == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (plugin->as_folder)
|
||||||
|
UpdatePlaylistFile(directory, name, info, *plugin);
|
||||||
|
|
||||||
PlaylistInfo pi(name, info.mtime);
|
PlaylistInfo pi(name, info.mtime);
|
||||||
|
|
||||||
const ScopeDatabaseLock protect;
|
const ScopeDatabaseLock protect;
|
||||||
if (directory.playlists.UpdateOrInsert(std::move(pi)))
|
if (directory.playlists.UpdateOrInsert(std::move(pi)))
|
||||||
modified = true;
|
modified = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
struct StorageFileInfo;
|
struct StorageFileInfo;
|
||||||
struct Directory;
|
struct Directory;
|
||||||
struct ArchivePlugin;
|
struct ArchivePlugin;
|
||||||
|
struct PlaylistPlugin;
|
||||||
class ArchiveFile;
|
class ArchiveFile;
|
||||||
class Storage;
|
class Storage;
|
||||||
class ExcludeList;
|
class ExcludeList;
|
||||||
@ -118,6 +119,10 @@ private:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void UpdatePlaylistFile(Directory &parent, const char *name,
|
||||||
|
const StorageFileInfo &info,
|
||||||
|
const PlaylistPlugin &plugin) noexcept;
|
||||||
|
|
||||||
bool UpdatePlaylistFile(Directory &directory,
|
bool UpdatePlaylistFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const StorageFileInfo &info) noexcept;
|
const StorageFileInfo &info) noexcept;
|
||||||
|
@ -69,6 +69,12 @@ struct PlaylistPlugin {
|
|||||||
const char *const*suffixes = nullptr;
|
const char *const*suffixes = nullptr;
|
||||||
const char *const*mime_types = 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,
|
constexpr PlaylistPlugin(const char *_name,
|
||||||
std::unique_ptr<SongEnumerator> (*_open_uri)(const char *uri,
|
std::unique_ptr<SongEnumerator> (*_open_uri)(const char *uri,
|
||||||
Mutex &mutex)) noexcept
|
Mutex &mutex)) noexcept
|
||||||
@ -104,6 +110,12 @@ struct PlaylistPlugin {
|
|||||||
return copy;
|
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?
|
* Does the plugin announce the specified URI scheme?
|
||||||
*/
|
*/
|
||||||
|
@ -72,5 +72,6 @@ static const char *const cue_playlist_mime_types[] = {
|
|||||||
|
|
||||||
const PlaylistPlugin cue_playlist_plugin =
|
const PlaylistPlugin cue_playlist_plugin =
|
||||||
PlaylistPlugin("cue", cue_playlist_open_stream)
|
PlaylistPlugin("cue", cue_playlist_open_stream)
|
||||||
|
.WithAsFolder()
|
||||||
.WithSuffixes(cue_playlist_suffixes)
|
.WithSuffixes(cue_playlist_suffixes)
|
||||||
.WithMimeTypes(cue_playlist_mime_types);
|
.WithMimeTypes(cue_playlist_mime_types);
|
||||||
|
Loading…
Reference in New Issue
Block a user