You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
4.4 KiB
C++
169 lines
4.4 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// Copyright The Music Player Daemon Project
|
|
|
|
#include "Walk.hxx"
|
|
#include "UpdateDomain.hxx"
|
|
#include "CueValidator.hxx"
|
|
#include "db/DatabaseLock.hxx"
|
|
#include "db/PlaylistVector.hxx"
|
|
#include "db/plugins/simple/Directory.hxx"
|
|
#include "db/plugins/simple/Song.hxx"
|
|
#include "lib/fmt/ExceptionFormatter.hxx"
|
|
#include "song/DetachedSong.hxx"
|
|
#include "input/InputStream.hxx"
|
|
#include "input/WaitReady.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 "fs/Traits.hxx"
|
|
#include "util/StringCompare.hxx"
|
|
#include "Log.hxx"
|
|
|
|
#include <fmt/core.h>
|
|
|
|
inline void
|
|
UpdateWalk::UpdatePlaylistFile(Directory &directory,
|
|
SongEnumerator &contents) noexcept
|
|
{
|
|
unsigned track = 0;
|
|
|
|
while (true) {
|
|
auto song = contents.NextSong();
|
|
if (!song)
|
|
break;
|
|
|
|
auto db_song = std::make_unique<Song>(std::move(*song),
|
|
directory);
|
|
const bool is_absolute =
|
|
PathTraitsUTF8::IsAbsoluteOrHasScheme(db_song->filename.c_str());
|
|
db_song->target = is_absolute
|
|
? db_song->filename
|
|
/* prepend "../" to relative paths to go from
|
|
the virtual directory (DEVICE_PLAYLIST) to
|
|
the containing directory */
|
|
: "../" + db_song->filename;
|
|
db_song->filename = fmt::format("track{:04}", ++track);
|
|
|
|
{
|
|
const ScopeDatabaseLock protect;
|
|
directory.AddSong(std::move(db_song));
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void
|
|
UpdateWalk::UpdatePlaylistFile(Directory &parent, std::string_view 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 char *const path = directory->GetPath();
|
|
|
|
FmtDebug(update_domain, "scanning playlist {:?}", path);
|
|
|
|
try {
|
|
Mutex mutex;
|
|
auto is = storage.OpenFile(path, mutex);
|
|
LockWaitReady(*is);
|
|
|
|
auto e = plugin.open_stream(std::move(is));
|
|
if (!e) {
|
|
/* unsupported URI? roll back.. */
|
|
editor.LockDeleteDirectory(directory);
|
|
return;
|
|
}
|
|
|
|
UpdatePlaylistFile(*directory, *e);
|
|
|
|
if (directory->IsEmpty())
|
|
editor.LockDeleteDirectory(directory);
|
|
} catch (...) {
|
|
FmtError(update_domain,
|
|
"Failed to scan playlist {:?}: {}",
|
|
path, std::current_exception());
|
|
editor.LockDeleteDirectory(directory);
|
|
}
|
|
}
|
|
|
|
bool
|
|
UpdateWalk::UpdatePlaylistFile(Directory &directory,
|
|
std::string_view name, std::string_view suffix,
|
|
const StorageFileInfo &info) noexcept
|
|
{
|
|
const auto *const plugin = FindPlaylistPluginBySuffix(suffix);
|
|
if (plugin == nullptr)
|
|
return false;
|
|
|
|
// Check if this is a CUE file that should be ignored
|
|
if (StringIsEqualIgnoreCase(suffix, "cue")) {
|
|
if (ShouldIgnoreCueFile(storage, directory.GetPath(),
|
|
std::string(name).c_str())) {
|
|
FmtDebug(update_domain,
|
|
"Ignoring CUE file {}/{} based on validation rules",
|
|
directory.GetPath(), name);
|
|
return false; // Treat as if not a playlist file
|
|
}
|
|
}
|
|
|
|
// Also ignore .pls and .m3u files as requested
|
|
if (StringIsEqualIgnoreCase(suffix, "pls") ||
|
|
StringIsEqualIgnoreCase(suffix, "m3u")) {
|
|
FmtDebug(update_domain,
|
|
"Ignoring playlist file {}/{}",
|
|
directory.GetPath(), name);
|
|
return false;
|
|
}
|
|
|
|
if (GetPlaylistPluginAsFolder(*plugin))
|
|
UpdatePlaylistFile(directory, name, info, *plugin);
|
|
|
|
PlaylistInfo pi(name, info.mtime);
|
|
|
|
const ScopeDatabaseLock protect;
|
|
if (directory.playlists.UpdateOrInsert(std::move(pi)))
|
|
modified = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
UpdateWalk::PurgeDanglingFromPlaylists(Directory &directory) noexcept
|
|
{
|
|
/* recurse */
|
|
for (Directory &child : directory.children)
|
|
PurgeDanglingFromPlaylists(child);
|
|
|
|
if (!directory.IsPlaylist())
|
|
/* this check is only for virtual directories
|
|
representing a playlist file */
|
|
return;
|
|
|
|
directory.ForEachSongSafe([&](Song &song){
|
|
if (!song.target.empty() &&
|
|
!PathTraitsUTF8::IsAbsoluteOrHasScheme(song.target.c_str())) {
|
|
Song *target = directory.LookupTargetSong(song.target.c_str());
|
|
if (target == nullptr) {
|
|
/* the target does not exist: remove
|
|
the virtual song */
|
|
editor.DeleteSong(directory, &song);
|
|
modified = true;
|
|
} else {
|
|
/* the target exists: mark it (for
|
|
option "hide_playlist_targets") */
|
|
target->in_playlist = true;
|
|
}
|
|
}
|
|
});
|
|
}
|