db/update/Walk: use marker to remove deleted items

This eliminates all `Storage::GetInfo()` calls from
`UpdateWalk::PurgeDeletedFromDirectory()` and instead uses a "marker"
field to mark items that have been visited; later, all unmarked items
can be deleted.

This eliminates a lot of redundant I/O which is noticable with the
`curl` storage plugin (i.e. WebDAV).
This commit is contained in:
Max Kellermann 2023-05-22 19:45:02 +02:00
parent 9027e5c5bb
commit 7bb251dad8
9 changed files with 56 additions and 18 deletions

2
NEWS
View File

@ -14,6 +14,8 @@ ver 0.24 (not yet released)
- proxy: require libmpdclient 2.15 or later - proxy: require libmpdclient 2.15 or later
* archive * archive
- add option to disable archive plugins in mpd.conf - add option to disable archive plugins in mpd.conf
* storage
- curl: optimize database update
* input * input
- curl: add "connect_timeout" configuration - curl: add "connect_timeout" configuration
* decoder * decoder

View File

@ -24,6 +24,12 @@ struct PlaylistInfo {
std::chrono::system_clock::time_point mtime = std::chrono::system_clock::time_point mtime =
std::chrono::system_clock::time_point::min(); std::chrono::system_clock::time_point::min();
/**
* This field is used by the database update to check whether
* an item has disappeared.
*/
bool mark;
class CompareName { class CompareName {
const std::string_view name; const std::string_view name;

View File

@ -27,8 +27,11 @@ PlaylistVector::UpdateOrInsert(PlaylistInfo &&pi) noexcept
return false; return false;
i->mtime = pi.mtime; i->mtime = pi.mtime;
} else i->mark = true;
} else {
pi.mark = true;
push_back(std::move(pi)); push_back(std::move(pi));
}
return true; return true;
} }

View File

@ -75,6 +75,12 @@ struct Directory : IntrusiveListHook<> {
*/ */
DatabasePtr mounted_database; DatabasePtr mounted_database;
/**
* This field is used by the database update to check whether
* an item has disappeared.
*/
bool mark;
public: public:
Directory(std::string &&_path_utf8, Directory *_parent) noexcept; Directory(std::string &&_path_utf8, Directory *_parent) noexcept;
~Directory() noexcept; ~Directory() noexcept;

View File

@ -79,6 +79,12 @@ struct Song : IntrusiveListHook<> {
*/ */
bool in_playlist = false; bool in_playlist = false;
/**
* This field is used by the database update to check whether
* an item has disappeared.
*/
bool mark;
template<typename F> template<typename F>
Song(F &&_filename, Directory &_parent) noexcept Song(F &&_filename, Directory &_parent) noexcept
:parent(_parent), filename(std::forward<F>(_filename)) {} :parent(_parent), filename(std::forward<F>(_filename)) {}

View File

@ -137,7 +137,6 @@ UpdateWalk::UpdateArchiveFile(Directory &parent, std::string_view name,
file = archive_file_open(&plugin, path_fs); file = archive_file_open(&plugin, path_fs);
} catch (...) { } catch (...) {
LogError(std::current_exception()); LogError(std::current_exception());
editor.LockDeleteDirectory(directory);
return; return;
} }
@ -145,6 +144,8 @@ UpdateWalk::UpdateArchiveFile(Directory &parent, std::string_view name,
UpdateArchiveVisitor visitor(*this, *file, *directory); UpdateArchiveVisitor visitor(*this, *file, *directory);
file->Visit(visitor); file->Visit(visitor);
directory->mark = true;
} }
bool bool

View File

@ -29,17 +29,11 @@ try {
FmtError(update_domain, FmtError(update_domain,
"no read permissions on {}/{}", "no read permissions on {}/{}",
directory.GetPath(), name); directory.GetPath(), name);
if (song != nullptr)
editor.LockDeleteSong(directory, song);
return; return;
} }
if (!(song != nullptr && info.mtime == song->mtime && !walk_discard) && if (!(song != nullptr && info.mtime == song->mtime && !walk_discard) &&
UpdateContainerFile(directory, name, suffix, info)) { UpdateContainerFile(directory, name, suffix, info)) {
if (song != nullptr)
editor.LockDeleteSong(directory, song);
return; return;
} }
@ -55,6 +49,8 @@ try {
return; return;
} }
new_song->mark = true;
{ {
const ScopeDatabaseLock protect; const ScopeDatabaseLock protect;
directory.AddSong(std::move(new_song)); directory.AddSong(std::move(new_song));
@ -66,14 +62,17 @@ try {
} else if (info.mtime != song->mtime || walk_discard) { } else if (info.mtime != song->mtime || walk_discard) {
FmtNotice(update_domain, "updating {}/{}", FmtNotice(update_domain, "updating {}/{}",
directory.GetPath(), name); directory.GetPath(), name);
if (!song->UpdateFile(storage)) { if (song->UpdateFile(storage))
song->mark = true;
else
FmtDebug(update_domain, FmtDebug(update_domain,
"deleting unrecognized file {}/{}", "deleting unrecognized file {}/{}",
directory.GetPath(), name); directory.GetPath(), name);
editor.LockDeleteSong(directory, song);
}
modified = true; modified = true;
} else {
/* not modified */
song->mark = true;
} }
} catch (...) { } catch (...) {
FmtError(update_domain, FmtError(update_domain,

View File

@ -22,6 +22,7 @@ UpdateWalk::MakeVirtualDirectoryIfModified(Directory &parent, std::string_view n
directory->device == virtual_device && directory->device == virtual_device &&
!walk_discard) { !walk_discard) {
/* not modified */ /* not modified */
directory->mark = true;
return nullptr; return nullptr;
} }
@ -32,6 +33,7 @@ UpdateWalk::MakeVirtualDirectoryIfModified(Directory &parent, std::string_view n
directory = parent.MakeChild(name); directory = parent.MakeChild(name);
directory->mtime = info.mtime; directory->mtime = info.mtime;
directory->device = virtual_device; directory->device = virtual_device;
directory->mark = true;
return directory; return directory;
} }

View File

@ -73,6 +73,19 @@ UpdateWalk::RemoveExcludedFromDirectory(Directory &directory,
}); });
} }
static void
UnmarkAllIn(Directory &directory) noexcept
{
for (auto &i : directory.children)
i.mark = false;
for (auto &i : directory.songs)
i.mark = false;
for (auto &i : directory.playlists)
i.mark = false;
}
inline void inline void
UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept
{ {
@ -81,8 +94,7 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept
/* mount points are always preserved */ /* mount points are always preserved */
return; return;
if (DirectoryExists(storage, child) && if (child.mark)
child.IsPluginAvailable())
return; return;
/* the directory was deleted (or the plugin which /* the directory was deleted (or the plugin which
@ -94,9 +106,7 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept
}); });
directory.ForEachSongSafe([&](Song &song){ directory.ForEachSongSafe([&](Song &song){
if (!directory_child_is_regular(storage, directory, if (!song.mark) {
song.filename) ||
!song.IsPluginAvailable()) {
/* the song file was deleted (or the decoder /* the song file was deleted (or the decoder
plugin is unavailable) */ plugin is unavailable) */
@ -109,7 +119,7 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) noexcept
for (auto i = directory.playlists.begin(), for (auto i = directory.playlists.begin(),
end = directory.playlists.end(); end = directory.playlists.end();
i != end;) { i != end;) {
if (!directory_child_is_regular(storage, directory, i->name)) { if (!i->mark) {
const ScopeDatabaseLock protect; const ScopeDatabaseLock protect;
i = directory.playlists.erase(i); i = directory.playlists.erase(i);
} else } else
@ -343,7 +353,7 @@ UpdateWalk::UpdateDirectory(Directory &directory,
if (!child_exclude_list.IsEmpty()) if (!child_exclude_list.IsEmpty())
RemoveExcludedFromDirectory(directory, child_exclude_list); RemoveExcludedFromDirectory(directory, child_exclude_list);
PurgeDeletedFromDirectory(directory); UnmarkAllIn(directory);
const char *name_utf8; const char *name_utf8;
while (!cancel && (name_utf8 = reader->Read()) != nullptr) { while (!cancel && (name_utf8 = reader->Read()) != nullptr) {
@ -370,7 +380,10 @@ UpdateWalk::UpdateDirectory(Directory &directory,
UpdateDirectoryChild(directory, child_exclude_list, name_utf8, info2); UpdateDirectoryChild(directory, child_exclude_list, name_utf8, info2);
} }
PurgeDeletedFromDirectory(directory);
directory.mtime = info.mtime; directory.mtime = info.mtime;
directory.mark = true;
return true; return true;
} }