db/simple: prune CUE entries from database for non-existent songs

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1019
This commit is contained in:
Max Kellermann 2021-08-05 18:57:11 +02:00
parent 8e0d39ae94
commit 1985786ed2
5 changed files with 59 additions and 1 deletions

1
NEWS
View File

@ -4,6 +4,7 @@ ver 0.22.10 (not yet released)
* database * database
- simple: fix crash bug - simple: fix crash bug
- simple: fix absolute paths in CUE "as_directory" entries - simple: fix absolute paths in CUE "as_directory" entries
- simple: prune CUE entries from database for non-existent songs
* input * input
- curl: fix crash bug after stream with Icy metadata was closed by peer - curl: fix crash bug after stream with Icy metadata was closed by peer
- tidal: remove defunct unmaintained plugin - tidal: remove defunct unmaintained plugin

View File

@ -109,6 +109,23 @@ Directory::FindChild(std::string_view name) const noexcept
return nullptr; return nullptr;
} }
bool
Directory::TargetExists(std::string_view _target) const noexcept
{
StringView target{_target};
if (target.SkipPrefix("../")) {
if (parent == nullptr)
return false;
return parent->TargetExists(target);
}
/* sorry for the const_cast ... */
const auto lr = const_cast<Directory *>(this)->LookupDirectory(target);
return lr.directory->FindSong(lr.rest) != nullptr;
}
void void
Directory::PruneEmpty() noexcept Directory::PruneEmpty() noexcept
{ {

View File

@ -118,13 +118,17 @@ public:
return new Directory(std::string(), nullptr); return new Directory(std::string(), nullptr);
} }
bool IsPlaylist() const noexcept {
return device == DEVICE_PLAYLIST;
}
/** /**
* Is this really a regular file which is being treated like a * Is this really a regular file which is being treated like a
* directory? * directory?
*/ */
bool IsReallyAFile() const noexcept { bool IsReallyAFile() const noexcept {
return device == DEVICE_INARCHIVE || return device == DEVICE_INARCHIVE ||
device == DEVICE_PLAYLIST || IsPlaylist() ||
device == DEVICE_CONTAINER; device == DEVICE_CONTAINER;
} }
@ -210,6 +214,9 @@ public:
gcc_pure gcc_pure
LookupResult LookupDirectory(std::string_view uri) noexcept; LookupResult LookupDirectory(std::string_view uri) noexcept;
[[gnu::pure]]
bool TargetExists(std::string_view target) const noexcept;
gcc_pure gcc_pure
bool IsEmpty() const noexcept { bool IsEmpty() const noexcept {
return children.empty() && return children.empty() &&

View File

@ -133,6 +133,28 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept
} }
} }
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()) &&
!directory.TargetExists(song.target)) {
editor.DeleteSong(directory, &song);
modified = true;
}
});
}
#ifndef _WIN32 #ifndef _WIN32
static bool static bool
update_directory_stat(Storage &storage, Directory &directory) noexcept update_directory_stat(Storage &storage, Directory &directory) noexcept
@ -530,5 +552,10 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
UpdateDirectory(root, exclude_list, info); UpdateDirectory(root, exclude_list, info);
} }
{
const ScopeDatabaseLock protect;
PurgeDanglingFromPlaylists(root);
}
return modified; return modified;
} }

View File

@ -85,6 +85,12 @@ private:
void PurgeDeletedFromDirectory(Directory &directory) noexcept; void PurgeDeletedFromDirectory(Directory &directory) noexcept;
/**
* Remove all virtual songs inside playlists whose "target"
* field points to a non-existing song file.
*/
void PurgeDanglingFromPlaylists(Directory &directory) noexcept;
void UpdateSongFile2(Directory &directory, void UpdateSongFile2(Directory &directory,
const char *name, const char *suffix, const char *name, const char *suffix,
const StorageFileInfo &info) noexcept; const StorageFileInfo &info) noexcept;