Skip to content

Commit

Permalink
db/update: scan CUE playlist contents
Browse files Browse the repository at this point in the history
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 #39
  • Loading branch information
MaxKellermann committed Sep 7, 2019
1 parent 5fdb804 commit d63e2c2
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 1 deletion.
2 changes: 2 additions & 0 deletions NEWS
Expand Up @@ -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"
Expand Down
7 changes: 7 additions & 0 deletions src/db/plugins/simple/Directory.hxx
Expand Up @@ -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 {
Expand Down Expand Up @@ -117,6 +123,7 @@ public:
*/
bool IsReallyAFile() const noexcept {
return device == DEVICE_INARCHIVE ||
device == DEVICE_PLAYLIST ||
device == DEVICE_CONTAINER;
}

Expand Down
5 changes: 5 additions & 0 deletions src/db/plugins/simple/DirectorySave.cxx
Expand Up @@ -50,6 +50,9 @@ DeviceToTypeString(unsigned device) noexcept
case DEVICE_CONTAINER:
return "container";

case DEVICE_PLAYLIST:
return "playlist";

default:
return nullptr;
}
Expand All @@ -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;
}
Expand Down
69 changes: 68 additions & 1 deletion src/db/update/Playlist.cxx
Expand Up @@ -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<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
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;
}
5 changes: 5 additions & 0 deletions src/db/update/Walk.hxx
Expand Up @@ -30,6 +30,7 @@
struct StorageFileInfo;
struct Directory;
struct ArchivePlugin;
struct PlaylistPlugin;
class ArchiveFile;
class Storage;
class ExcludeList;
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/playlist/PlaylistPlugin.hxx
Expand Up @@ -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<SongEnumerator> (*_open_uri)(const char *uri,
Mutex &mutex)) noexcept
Expand Down Expand Up @@ -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?
*/
Expand Down
1 change: 1 addition & 0 deletions src/playlist/plugins/CuePlaylistPlugin.cxx
Expand Up @@ -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);

0 comments on commit d63e2c2

Please sign in to comment.