From 9ae7f186bc43749383594807b1d751b5389161e7 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 29 Jan 2014 18:14:57 +0100 Subject: LocalStorage: new API abstracting filesystem walk Prepare to make this a new plugin API, for example to use a SMB share for the music_directory. --- src/Mapper.cxx | 13 +++- src/Mapper.hxx | 22 ------ src/db/update/Archive.cxx | 18 +++-- src/db/update/Container.cxx | 27 ++++--- src/db/update/UpdateIO.cxx | 91 ++++++++++------------ src/db/update/UpdateIO.hxx | 28 ++++--- src/db/update/UpdateSong.cxx | 15 ++-- src/db/update/Walk.cxx | 179 ++++++++++++++++++++++++------------------- src/db/update/Walk.hxx | 27 ++++--- src/storage/FileInfo.hxx | 62 +++++++++++++++ src/storage/LocalStorage.cxx | 154 +++++++++++++++++++++++++++++++++++++ src/storage/LocalStorage.hxx | 89 +++++++++++++++++++++ 12 files changed, 527 insertions(+), 198 deletions(-) create mode 100644 src/storage/FileInfo.hxx create mode 100644 src/storage/LocalStorage.cxx create mode 100644 src/storage/LocalStorage.hxx (limited to 'src') diff --git a/src/Mapper.cxx b/src/Mapper.cxx index 60928dbb9..786634ebb 100644 --- a/src/Mapper.cxx +++ b/src/Mapper.cxx @@ -193,7 +193,8 @@ map_uri_fs(const char *uri) return AllocatedPath::Build(music_dir_fs, uri_fs); } -AllocatedPath +gcc_pure +static AllocatedPath map_directory_fs(const Directory &directory) { assert(!music_dir_fs.IsNull()); @@ -204,7 +205,15 @@ map_directory_fs(const Directory &directory) return map_uri_fs(directory.GetPath()); } -AllocatedPath +/** + * Determines the file system path of a directory's child (may be a + * sub directory or a song). + * + * @param directory the parent directory object + * @param name the child's name in UTF-8 + * @return the path in file system encoding, or nullptr if mapping failed + */ +static AllocatedPath map_directory_child_fs(const Directory &directory, const char *name) { assert(!music_dir_fs.IsNull()); diff --git a/src/Mapper.hxx b/src/Mapper.hxx index 33d3b94f5..a94a4fbb5 100644 --- a/src/Mapper.hxx +++ b/src/Mapper.hxx @@ -91,28 +91,6 @@ gcc_pure AllocatedPath map_uri_fs(const char *uri); -/** - * Determines the file system path of a directory object. - * - * @param directory the directory object - * @return the path in file system encoding, or nullptr if mapping failed - */ -gcc_pure -AllocatedPath -map_directory_fs(const Directory &directory); - -/** - * Determines the file system path of a directory's child (may be a - * sub directory or a song). - * - * @param directory the parent directory object - * @param name the child's name in UTF-8 - * @return the path in file system encoding, or nullptr if mapping failed - */ -gcc_pure -AllocatedPath -map_directory_child_fs(const Directory &directory, const char *name); - /** * "Detach" the #Song object, i.e. convert it to a #DetachedSong * instance. diff --git a/src/db/update/Archive.cxx b/src/db/update/Archive.cxx index 0d7127685..9874eb1f2 100644 --- a/src/db/update/Archive.cxx +++ b/src/db/update/Archive.cxx @@ -23,8 +23,8 @@ #include "db/DatabaseLock.hxx" #include "db/Directory.hxx" #include "db/Song.hxx" -#include "Mapper.hxx" #include "fs/AllocatedPath.hxx" +#include "storage/FileInfo.hxx" #include "archive/ArchiveList.hxx" #include "archive/ArchivePlugin.hxx" #include "archive/ArchiveFile.hxx" @@ -103,20 +103,24 @@ class UpdateArchiveVisitor final : public ArchiveVisitor { */ void UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name, - const struct stat *st, + const FileInfo &info, const archive_plugin &plugin) { db_lock(); Directory *directory = parent.FindChild(name); db_unlock(); - if (directory != nullptr && directory->mtime == st->st_mtime && + if (directory != nullptr && directory->mtime == info.mtime && !walk_discard) /* MPD has already scanned the archive, and it hasn't changed since - don't consider updating it */ return; - const auto path_fs = map_directory_child_fs(parent, name); + const auto path_fs = storage.MapChildFS(parent.GetPath(), name); + if (path_fs.IsNull()) + /* not a local file: skip, because the archive API + supports only local files */ + return; /* open archive */ Error error; @@ -141,7 +145,7 @@ UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name, db_unlock(); } - directory->mtime = st->st_mtime; + directory->mtime = info.mtime; UpdateArchiveVisitor visitor(*this, directory); file->Visit(visitor); @@ -151,13 +155,13 @@ UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name, bool UpdateWalk::UpdateArchiveFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st) + const FileInfo &info) { const struct archive_plugin *plugin = archive_plugin_from_suffix(suffix); if (plugin == nullptr) return false; - UpdateArchiveFile(directory, name, st, *plugin); + UpdateArchiveFile(directory, name, info, *plugin); return true; } diff --git a/src/db/update/Container.cxx b/src/db/update/Container.cxx index 33e29953d..956db7209 100644 --- a/src/db/update/Container.cxx +++ b/src/db/update/Container.cxx @@ -25,8 +25,8 @@ #include "db/Song.hxx" #include "decoder/DecoderPlugin.hxx" #include "decoder/DecoderList.hxx" -#include "Mapper.hxx" #include "fs/AllocatedPath.hxx" +#include "storage/FileInfo.hxx" #include "tag/TagHandler.hxx" #include "tag/TagBuilder.hxx" #include "Log.hxx" @@ -37,13 +37,13 @@ Directory * UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name, - const struct stat *st) + const FileInfo &info) { Directory *directory = parent.FindChild(name); // directory exists already if (directory != nullptr) { - if (directory->mtime == st->st_mtime && !walk_discard) { + if (directory->mtime == info.mtime && !walk_discard) { /* not modified */ return nullptr; } @@ -53,7 +53,7 @@ UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name, } directory = parent.MakeChild(name); - directory->mtime = st->st_mtime; + directory->mtime = info.mtime; return directory; } @@ -67,7 +67,7 @@ SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix) bool UpdateWalk::UpdateContainerFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st) + const FileInfo &info) { const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){ return SupportsContainerSuffix(plugin, suffix); @@ -77,7 +77,7 @@ UpdateWalk::UpdateContainerFile(Directory &directory, const DecoderPlugin &plugin = *_plugin; db_lock(); - Directory *contdir = MakeDirectoryIfModified(directory, name, st); + Directory *contdir = MakeDirectoryIfModified(directory, name, info); if (contdir == nullptr) { /* not modified */ db_unlock(); @@ -87,7 +87,13 @@ UpdateWalk::UpdateContainerFile(Directory &directory, contdir->device = DEVICE_CONTAINER; db_unlock(); - const auto pathname = map_directory_child_fs(directory, name); + const auto pathname = storage.MapFS(contdir->GetPath()); + if (pathname.IsNull()) { + /* not a local file: skip, because the container API + supports only local files */ + editor.LockDeleteDirectory(contdir); + return false; + } char *vtrack; unsigned int tnum = 0; @@ -96,11 +102,10 @@ UpdateWalk::UpdateContainerFile(Directory &directory, Song *song = Song::NewFile(vtrack, *contdir); // shouldn't be necessary but it's there.. - song->mtime = st->st_mtime; - - const auto child_path_fs = - map_directory_child_fs(*contdir, vtrack); + song->mtime = info.mtime; + const auto child_path_fs = AllocatedPath::Build(pathname, + vtrack); plugin.ScanFile(child_path_fs.c_str(), add_tag_handler, &tag_builder); diff --git a/src/db/update/UpdateIO.cxx b/src/db/update/UpdateIO.cxx index f91caf359..58b1fe296 100644 --- a/src/db/update/UpdateIO.cxx +++ b/src/db/update/UpdateIO.cxx @@ -21,91 +21,84 @@ #include "UpdateIO.hxx" #include "UpdateDomain.hxx" #include "db/Directory.hxx" -#include "Mapper.hxx" -#include "fs/AllocatedPath.hxx" +#include "storage/FileInfo.hxx" +#include "storage/LocalStorage.hxx" +#include "fs/Traits.hxx" #include "fs/FileSystem.hxx" +#include "util/Error.hxx" #include "Log.hxx" #include #include -int -stat_directory(const Directory &directory, struct stat *st) +bool +GetInfo(LocalStorage &storage, const char *uri_utf8, FileInfo &info) { - const auto path_fs = map_directory_fs(directory); - if (path_fs.IsNull()) - return -1; - - if (!StatFile(path_fs, *st)) { - int error = errno; - const std::string path_utf8 = path_fs.ToUTF8(); - FormatErrno(update_domain, error, - "Failed to stat %s", path_utf8.c_str()); - return -1; - } - - return 0; + Error error; + bool success = storage.GetInfo(uri_utf8, true, info, error); + if (!success) + LogError(error); + return success; } -int -stat_directory_child(const Directory &parent, const char *name, - struct stat *st) +bool +GetInfo(LocalDirectoryReader &reader, FileInfo &info) { - const auto path_fs = map_directory_child_fs(parent, name); - if (path_fs.IsNull()) - return -1; - - if (!StatFile(path_fs, *st)) { - int error = errno; - const std::string path_utf8 = path_fs.ToUTF8(); - FormatErrno(update_domain, error, - "Failed to stat %s", path_utf8.c_str()); - return -1; - } - - return 0; + Error error; + bool success = reader.GetInfo(true, info, error); + if (!success) + LogError(error); + return success; } bool -directory_exists(const Directory &directory) +DirectoryExists(LocalStorage &storage, const Directory &directory) { - const auto path_fs = map_directory_fs(directory); - if (path_fs.IsNull()) - /* invalid path: cannot exist */ + FileInfo info; + if (!storage.GetInfo(directory.GetPath(), true, info, IgnoreError())) return false; return directory.device == DEVICE_INARCHIVE || directory.device == DEVICE_CONTAINER - ? FileExists(path_fs) - : DirectoryExists(path_fs); + ? info.IsRegular() + : info.IsDirectory(); +} + +static bool +GetDirectoryChildInfo(LocalStorage &storage, const Directory &directory, + const char *name_utf8, FileInfo &info, Error &error) +{ + const auto uri_utf8 = PathTraitsUTF8::Build(directory.GetPath(), + name_utf8); + return storage.GetInfo(uri_utf8.c_str(), true, info, error); } bool -directory_child_is_regular(const Directory &directory, +directory_child_is_regular(LocalStorage &storage, const Directory &directory, const char *name_utf8) { - const auto path_fs = map_directory_child_fs(directory, name_utf8); - if (path_fs.IsNull()) - return false; - - return FileExists(path_fs); + FileInfo info; + return GetDirectoryChildInfo(storage, directory, name_utf8, info, + IgnoreError()) && + info.IsRegular(); } bool -directory_child_access(const Directory &directory, +directory_child_access(LocalStorage &storage, const Directory &directory, const char *name, int mode) { #ifdef WIN32 /* CheckAccess() is useless on WIN32 */ + (void)storage; (void)directory; (void)name; (void)mode; return true; #else - const auto path = map_directory_child_fs(directory, name); + const auto path = storage.MapChildFS(directory.GetPath(), name); if (path.IsNull()) - /* something went wrong, but that isn't a permission - problem */ + /* does not point to local file: silently ignore the + check */ return true; return CheckAccess(path, mode) || errno != EACCES; diff --git a/src/db/update/UpdateIO.hxx b/src/db/update/UpdateIO.hxx index c33b79dc0..d5cbb2a5b 100644 --- a/src/db/update/UpdateIO.hxx +++ b/src/db/update/UpdateIO.hxx @@ -23,24 +23,32 @@ #include "check.h" #include "Compiler.h" -#include - struct Directory; +struct FileInfo; +class LocalStorage; +class LocalDirectoryReader; -int -stat_directory(const Directory &directory, struct stat *st); +/** + * Wrapper for LocalStorage::GetInfo() that logs errors instead of + * returning them. + */ +bool +GetInfo(LocalStorage &storage, const char *uri_utf8, FileInfo &info); -int -stat_directory_child(const Directory &parent, const char *name, - struct stat *st); +/** + * Wrapper for LocalDirectoryReader::GetInfo() that logs errors + * instead of returning them. + */ +bool +GetInfo(LocalDirectoryReader &reader, FileInfo &info); gcc_pure bool -directory_exists(const Directory &directory); +DirectoryExists(LocalStorage &storage, const Directory &directory); gcc_pure bool -directory_child_is_regular(const Directory &directory, +directory_child_is_regular(LocalStorage &storage, const Directory &directory, const char *name_utf8); /** @@ -48,7 +56,7 @@ directory_child_is_regular(const Directory &directory, */ gcc_pure bool -directory_child_access(const Directory &directory, +directory_child_access(LocalStorage &storage, const Directory &directory, const char *name, int mode); #endif diff --git a/src/db/update/UpdateSong.cxx b/src/db/update/UpdateSong.cxx index 751d8bfe9..2868249e7 100644 --- a/src/db/update/UpdateSong.cxx +++ b/src/db/update/UpdateSong.cxx @@ -25,6 +25,7 @@ #include "db/Directory.hxx" #include "db/Song.hxx" #include "decoder/DecoderList.hxx" +#include "storage/FileInfo.hxx" #include "Log.hxx" #include @@ -32,13 +33,13 @@ inline void UpdateWalk::UpdateSongFile2(Directory &directory, const char *name, const char *suffix, - const struct stat *st) + const FileInfo &info) { db_lock(); Song *song = directory.FindSong(name); db_unlock(); - if (!directory_child_access(directory, name, R_OK)) { + if (!directory_child_access(storage, directory, name, R_OK)) { FormatError(update_domain, "no read permissions on %s/%s", directory.GetPath(), name); @@ -48,9 +49,9 @@ UpdateWalk::UpdateSongFile2(Directory &directory, return; } - if (!(song != nullptr && st->st_mtime == song->mtime && + if (!(song != nullptr && info.mtime == song->mtime && !walk_discard) && - UpdateContainerFile(directory, name, suffix, st)) { + UpdateContainerFile(directory, name, suffix, info)) { if (song != nullptr) editor.LockDeleteSong(directory, song); @@ -75,7 +76,7 @@ UpdateWalk::UpdateSongFile2(Directory &directory, modified = true; FormatDefault(update_domain, "added %s/%s", directory.GetPath(), name); - } else if (st->st_mtime != song->mtime || walk_discard) { + } else if (info.mtime != song->mtime || walk_discard) { FormatDefault(update_domain, "updating %s/%s", directory.GetPath(), name); if (!song->UpdateFile()) { @@ -92,11 +93,11 @@ UpdateWalk::UpdateSongFile2(Directory &directory, bool UpdateWalk::UpdateSongFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st) + const FileInfo &info) { if (!decoder_plugins_supports_suffix(suffix)) return false; - UpdateSongFile2(directory, name, suffix, st); + UpdateSongFile2(directory, name, suffix, info); return true; } diff --git a/src/db/update/Walk.cxx b/src/db/update/Walk.cxx index c54d5a8d7..e6bfdcfd9 100644 --- a/src/db/update/Walk.cxx +++ b/src/db/update/Walk.cxx @@ -35,9 +35,10 @@ #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" -#include "fs/DirectoryReader.hxx" +#include "storage/FileInfo.hxx" #include "util/Alloc.hxx" #include "util/UriUtil.hxx" +#include "util/Error.hxx" #include "Log.hxx" #include @@ -47,7 +48,9 @@ #include UpdateWalk::UpdateWalk(EventLoop &_loop, DatabaseListener &_listener) - :editor(_loop, _listener) + :storage(mapper_get_music_directory_utf8(), + mapper_get_music_directory_fs()), + editor(_loop, _listener) { #ifndef WIN32 follow_inside_symlinks = @@ -61,10 +64,10 @@ UpdateWalk::UpdateWalk(EventLoop &_loop, DatabaseListener &_listener) } static void -directory_set_stat(Directory &dir, const struct stat *st) +directory_set_stat(Directory &dir, const FileInfo &info) { - dir.inode = st->st_ino; - dir.device = st->st_dev; + dir.inode = info.inode; + dir.device = info.device; dir.have_stat = true; } @@ -103,7 +106,7 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) { Directory *child, *n; directory_for_each_child_safe(child, n, directory) { - if (directory_exists(*child)) + if (DirectoryExists(storage, *child)) continue; editor.LockDeleteDirectory(child); @@ -113,8 +116,8 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) Song *song, *ns; directory_for_each_song_safe(song, ns, directory) { - const auto path = map_song_fs(*song); - if (path.IsNull() || !FileExists(path)) { + if (!directory_child_is_regular(storage, directory, + song->uri)) { editor.LockDeleteSong(directory, song); modified = true; @@ -124,7 +127,8 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) for (auto i = directory.playlists.begin(), end = directory.playlists.end(); i != end;) { - if (!directory_child_is_regular(directory, i->name.c_str())) { + if (!directory_child_is_regular(storage, directory, + i->name.c_str())) { db_lock(); i = directory.playlists.erase(i); db_unlock(); @@ -134,24 +138,26 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory) } #ifndef WIN32 -static int -update_directory_stat(Directory &directory) +static bool +update_directory_stat(LocalStorage &storage, Directory &directory) { - struct stat st; - if (stat_directory(directory, &st) < 0) - return -1; + FileInfo info; + if (!GetInfo(storage, directory.GetPath(), info)) + return false; - directory_set_stat(directory, &st); - return 0; + directory_set_stat(directory, info); + return true; } #endif static int -find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) +find_inode_ancestor(LocalStorage &storage, Directory *parent, + unsigned inode, unsigned device) { #ifndef WIN32 while (parent) { - if (!parent->have_stat && update_directory_stat(*parent) < 0) + if (!parent->have_stat && + !update_directory_stat(storage, *parent)) return -1; if (parent->inode == inode && parent->device == device) { @@ -162,6 +168,7 @@ find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) parent = parent->parent; } #else + (void)storage; (void)parent; (void)inode; (void)device; @@ -173,12 +180,12 @@ find_inode_ancestor(Directory *parent, ino_t inode, dev_t device) inline bool UpdateWalk::UpdatePlaylistFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st) + const FileInfo &info) { if (!playlist_suffix_supported(suffix)) return false; - PlaylistInfo pi(name, st->st_mtime); + PlaylistInfo pi(name, info.mtime); db_lock(); if (directory.playlists.UpdateOrInsert(std::move(pi))) @@ -189,27 +196,28 @@ UpdateWalk::UpdatePlaylistFile(Directory &directory, inline bool UpdateWalk::UpdateRegularFile(Directory &directory, - const char *name, const struct stat *st) + const char *name, const FileInfo &info) { const char *suffix = uri_get_suffix(name); if (suffix == nullptr) return false; - return UpdateSongFile(directory, name, suffix, st) || - UpdateArchiveFile(directory, name, suffix, st) || - UpdatePlaylistFile(directory, name, suffix, st); + return UpdateSongFile(directory, name, suffix, info) || + UpdateArchiveFile(directory, name, suffix, info) || + UpdatePlaylistFile(directory, name, suffix, info); } void UpdateWalk::UpdateDirectoryChild(Directory &directory, - const char *name, const struct stat *st) + const char *name, const FileInfo &info) { assert(strchr(name, '/') == nullptr); - if (S_ISREG(st->st_mode)) { - UpdateRegularFile(directory, name, st); - } else if (S_ISDIR(st->st_mode)) { - if (find_inode_ancestor(&directory, st->st_ino, st->st_dev)) + if (info.IsRegular()) { + UpdateRegularFile(directory, name, info); + } else if (info.IsDirectory()) { + if (find_inode_ancestor(storage, &directory, + info.inode, info.device)) return; db_lock(); @@ -218,7 +226,7 @@ UpdateWalk::UpdateDirectoryChild(Directory &directory, assert(&directory == subdir->parent); - if (!UpdateDirectory(*subdir, st)) + if (!UpdateDirectory(*subdir, info)) editor.LockDeleteDirectory(subdir); } else { FormatDebug(update_domain, @@ -228,12 +236,10 @@ UpdateWalk::UpdateDirectoryChild(Directory &directory, /* we don't look at "." / ".." nor files with newlines in their name */ gcc_pure -static bool skip_path(Path path_fs) +static bool +skip_path(const char *name_utf8) { - const char *path = path_fs.c_str(); - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != nullptr; + return strchr(name_utf8, '\n') != nullptr; } gcc_pure @@ -242,9 +248,11 @@ UpdateWalk::SkipSymlink(const Directory *directory, const char *utf8_name) const { #ifndef WIN32 - const auto path_fs = map_directory_child_fs(*directory, utf8_name); + const auto path_fs = storage.MapChildFS(directory->GetPath(), + utf8_name); if (path_fs.IsNull()) - return true; + /* not a local file: don't skip */ + return false; const auto target = ReadLink(path_fs); if (target.IsNull()) @@ -304,63 +312,68 @@ UpdateWalk::SkipSymlink(const Directory *directory, } bool -UpdateWalk::UpdateDirectory(Directory &directory, const struct stat *st) +UpdateWalk::UpdateDirectory(Directory &directory, const FileInfo &info) { - assert(S_ISDIR(st->st_mode)); + assert(info.IsDirectory()); - directory_set_stat(directory, st); + directory_set_stat(directory, info); - const auto path_fs = map_directory_fs(directory); - if (path_fs.IsNull()) - return false; - - DirectoryReader reader(path_fs); - if (reader.HasFailed()) { - int error = errno; - const auto path_utf8 = path_fs.ToUTF8(); - FormatErrno(update_domain, error, - "Failed to open directory %s", - path_utf8.c_str()); + Error error; + LocalDirectoryReader *const reader = + storage.OpenDirectory(directory.GetPath(), error); + if (reader == nullptr) { + LogError(error); return false; } ExcludeList exclude_list; - exclude_list.LoadFile(AllocatedPath::Build(path_fs, ".mpdignore")); + + { + const auto exclude_path_fs = + storage.MapChildFS(directory.GetPath(), ".mpdignore"); + if (!exclude_path_fs.IsNull()) + exclude_list.LoadFile(exclude_path_fs); + } if (!exclude_list.IsEmpty()) RemoveExcludedFromDirectory(directory, exclude_list); PurgeDeletedFromDirectory(directory); - while (reader.ReadEntry()) { - const auto entry = reader.GetEntry(); - - if (skip_path(entry) || exclude_list.Check(entry)) + const char *name_utf8; + while ((name_utf8 = reader->Read()) != nullptr) { + if (skip_path(name_utf8)) continue; - const std::string utf8 = entry.ToUTF8(); - if (utf8.empty()) + { + const auto name_fs = AllocatedPath::FromUTF8(name_utf8); + if (name_fs.IsNull() || exclude_list.Check(name_fs)) + continue; + } + + if (SkipSymlink(&directory, name_utf8)) { + modified |= editor.DeleteNameIn(directory, name_utf8); continue; + } - if (SkipSymlink(&directory, utf8.c_str())) { - modified |= editor.DeleteNameIn(directory, utf8.c_str()); + FileInfo info2; + if (!GetInfo(*reader, info2)) { + modified |= editor.DeleteNameIn(directory, name_utf8); continue; } - struct stat st2; - if (stat_directory_child(directory, utf8.c_str(), &st2) == 0) - UpdateDirectoryChild(directory, utf8.c_str(), &st2); - else - modified |= editor.DeleteNameIn(directory, utf8.c_str()); + UpdateDirectoryChild(directory, name_utf8, info2); } - directory.mtime = st->st_mtime; + directory.mtime = info.mtime; return true; } inline Directory * -UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8) +UpdateWalk::DirectoryMakeChildChecked(Directory &parent, + const char *uri_utf8, + const char *name_utf8) { db_lock(); Directory *directory = parent.FindChild(name_utf8); @@ -369,9 +382,9 @@ UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8) if (directory != nullptr) return directory; - struct stat st; - if (stat_directory_child(parent, name_utf8, &st) < 0 || - find_inode_ancestor(&parent, st.st_ino, st.st_dev)) + FileInfo info; + if (!GetInfo(storage, uri_utf8, info) || + find_inode_ancestor(storage, &parent, info.inode, info.device)) return nullptr; if (SkipSymlink(&parent, name_utf8)) @@ -387,7 +400,7 @@ UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8) directory = parent.CreateChild(name_utf8); db_unlock(); - directory_set_stat(*directory, &st); + directory_set_stat(*directory, info); return directory; } @@ -405,6 +418,7 @@ UpdateWalk::DirectoryMakeUriParentChecked(Directory &root, const char *uri) continue; directory = DirectoryMakeChildChecked(*directory, + duplicated, name_utf8); if (directory == nullptr) break; @@ -425,12 +439,18 @@ UpdateWalk::UpdateUri(Directory &root, const char *uri) const char *name = PathTraitsUTF8::GetBase(uri); - struct stat st; - if (!SkipSymlink(parent, name) && - stat_directory_child(*parent, name, &st) == 0) - UpdateDirectoryChild(*parent, name, &st); - else + if (SkipSymlink(parent, name)) { modified |= editor.DeleteNameIn(*parent, name); + return; + } + + FileInfo info; + if (!GetInfo(storage, uri, info)) { + modified |= editor.DeleteNameIn(*parent, name); + return; + } + + UpdateDirectoryChild(*parent, name, info); } bool @@ -442,10 +462,11 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) if (path != nullptr && !isRootDirectory(path)) { UpdateUri(root, path); } else { - struct stat st; + FileInfo info; + if (!GetInfo(storage, "", info)) + return false; - if (stat_directory(root, &st) == 0) - UpdateDirectory(root, &st); + UpdateDirectory(root, info); } return modified; diff --git a/src/db/update/Walk.hxx b/src/db/update/Walk.hxx index aa4516917..c465ea7e1 100644 --- a/src/db/update/Walk.hxx +++ b/src/db/update/Walk.hxx @@ -22,10 +22,12 @@ #include "check.h" #include "Editor.hxx" +#include "storage/LocalStorage.hxx" #include struct stat; +struct FileInfo; struct Directory; struct archive_plugin; class ExcludeList; @@ -46,6 +48,8 @@ class UpdateWalk final { bool walk_discard; bool modified; + LocalStorage storage; + DatabaseEditor editor; public: @@ -68,15 +72,15 @@ private: void UpdateSongFile2(Directory &directory, const char *name, const char *suffix, - const struct stat *st); + const FileInfo &info); bool UpdateSongFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st); + const FileInfo &info); bool UpdateContainerFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st); + const FileInfo &info); #ifdef ENABLE_ARCHIVE @@ -84,10 +88,10 @@ private: bool UpdateArchiveFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st); + const FileInfo &info); void UpdateArchiveFile(Directory &directory, const char *name, - const struct stat *st, + const FileInfo &info, const archive_plugin &plugin); @@ -95,22 +99,22 @@ private: bool UpdateArchiveFile(gcc_unused Directory &directory, gcc_unused const char *name, gcc_unused const char *suffix, - gcc_unused const struct stat *st) { + gcc_unused const FileInfo &info) { return false; } #endif bool UpdatePlaylistFile(Directory &directory, const char *name, const char *suffix, - const struct stat *st); + const FileInfo &info); bool UpdateRegularFile(Directory &directory, - const char *name, const struct stat *st); + const char *name, const FileInfo &info); void UpdateDirectoryChild(Directory &directory, - const char *name, const struct stat *st); + const char *name, const FileInfo &info); - bool UpdateDirectory(Directory &directory, const struct stat *st); + bool UpdateDirectory(Directory &directory, const FileInfo &info); /** * Create the specified directory object if it does not exist @@ -121,9 +125,10 @@ private: * The caller must lock the database. */ Directory *MakeDirectoryIfModified(Directory &parent, const char *name, - const struct stat *st); + const FileInfo &info); Directory *DirectoryMakeChildChecked(Directory &parent, + const char *uri_utf8, const char *name_utf8); Directory *DirectoryMakeUriParentChecked(Directory &root, diff --git a/src/storage/FileInfo.hxx b/src/storage/FileInfo.hxx new file mode 100644 index 000000000..8dd152c0a --- /dev/null +++ b/src/storage/FileInfo.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STORAGE_FILE_INFO_HXX +#define MPD_STORAGE_FILE_INFO_HXX + +#include "check.h" + +#include +#include + +struct FileInfo { + enum class Type : uint8_t { + OTHER, + REGULAR, + DIRECTORY, + }; + + Type type; + + /** + * The file size in bytes. Only valid for #Type::REGULAR. + */ + uint64_t size; + + /** + * The modification time. 0 means unknown / not applicable. + */ + time_t mtime; + + /** + * Device id and inode number. 0 means unknown / not + * applicable. + */ + unsigned device, inode; + + bool IsRegular() const { + return type == Type::REGULAR; + } + + bool IsDirectory() const { + return type == Type::DIRECTORY; + } +}; + +#endif diff --git a/src/storage/LocalStorage.cxx b/src/storage/LocalStorage.cxx new file mode 100644 index 000000000..a229b3fe7 --- /dev/null +++ b/src/storage/LocalStorage.cxx @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LocalStorage.hxx" +#include "FileInfo.hxx" +#include "util/Error.hxx" +#include "fs/FileSystem.hxx" + +static bool +Stat(Path path, bool follow, FileInfo &info, Error &error) +{ + struct stat st; + if (!StatFile(path, st, follow)) { + error.SetErrno(); + + const auto path_utf8 = path.ToUTF8(); + error.FormatPrefix("Failed to stat %s: ", path_utf8.c_str()); + return false; + } + + if (S_ISREG(st.st_mode)) + info.type = FileInfo::Type::REGULAR; + else if (S_ISDIR(st.st_mode)) + info.type = FileInfo::Type::DIRECTORY; + else + info.type = FileInfo::Type::OTHER; + + info.size = st.st_size; + info.mtime = st.st_mtime; + info.device = st.st_dev; + info.inode = st.st_ino; + return true; +} + +std::string +LocalStorage::MapUTF8(const char *uri_utf8) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base_utf8; + + return PathTraitsUTF8::Build(base_utf8.c_str(), uri_utf8); +} + +AllocatedPath +LocalStorage::MapFS(const char *uri_utf8, Error &error) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base_fs; + + AllocatedPath path_fs = AllocatedPath::FromUTF8(uri_utf8, error); + if (!path_fs.IsNull()) + path_fs = AllocatedPath::Build(base_fs, path_fs); + + return path_fs; +} + +AllocatedPath +LocalStorage::MapFS(const char *uri_utf8) const +{ + return MapFS(uri_utf8, IgnoreError()); +} + +AllocatedPath +LocalStorage::MapChildFS(const char *uri_utf8, + const char *child_utf8) const +{ + const auto uri2 = PathTraitsUTF8::Build(uri_utf8, child_utf8); + return MapFS(uri2.c_str()); +} + +bool +LocalStorage::GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) +{ + AllocatedPath path_fs = MapFS(uri_utf8, error); + if (path_fs.IsNull()) + return false; + + return Stat(path_fs, follow, info, error); +} + +LocalDirectoryReader * +LocalStorage::OpenDirectory(const char *uri_utf8, Error &error) +{ + AllocatedPath path_fs = MapFS(uri_utf8, error); + if (path_fs.IsNull()) + return nullptr; + + LocalDirectoryReader *reader = + new LocalDirectoryReader(std::move(path_fs)); + if (reader->HasFailed()) { + error.FormatErrno("Failed to open '%s'", uri_utf8); + delete reader; + return nullptr; + } + + return reader; +} + +gcc_pure +static bool +SkipNameFS(const char *name_fs) +{ + return name_fs[0] == '.' && + (name_fs[1] == 0 || + (name_fs[1] == '.' && name_fs[2] == 0)); +} + +const char * +LocalDirectoryReader::Read() +{ + while (reader.ReadEntry()) { + const Path name_fs = reader.GetEntry(); + if (SkipNameFS(name_fs.c_str())) + continue; + + name_utf8 = name_fs.ToUTF8(); + if (name_utf8.empty()) + continue; + + return name_utf8.c_str(); + } + + return nullptr; +} + +bool +LocalDirectoryReader::GetInfo(bool follow, FileInfo &info, Error &error) +{ + const AllocatedPath path_fs = + AllocatedPath::Build(base_fs, reader.GetEntry()); + return Stat(path_fs, follow, info, error); +} diff --git a/src/storage/LocalStorage.hxx b/src/storage/LocalStorage.hxx new file mode 100644 index 000000000..73c11dd80 --- /dev/null +++ b/src/storage/LocalStorage.hxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STORAGE_LOCAL_HXX +#define MPD_STORAGE_LOCAL_HXX + +#include "check.h" +#include "fs/AllocatedPath.hxx" +#include "fs/DirectoryReader.hxx" + +#include + +struct FileInfo; + +class LocalDirectoryReader { + AllocatedPath base_fs; + + DirectoryReader reader; + + std::string name_utf8; + +public: + LocalDirectoryReader(AllocatedPath &&_base_fs) + :base_fs(std::move(_base_fs)), reader(base_fs) {} + + bool HasFailed() { + return reader.HasFailed(); + } + + const char *Read(); + + bool GetInfo(bool follow, FileInfo &info, Error &error); +}; + +class LocalStorage { + const std::string base_utf8; + const AllocatedPath base_fs; + +public: + LocalStorage(const char *_base_utf8, Path _base_fs) + :base_utf8(_base_utf8), base_fs(_base_fs) {} + + LocalStorage(const LocalStorage &) = delete; + + bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error); + + LocalDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error); + + /** + * Map the given relative URI to an absolute URI. + */ + gcc_pure + std::string MapUTF8(const char *uri_utf8) const; + + /** + * Map the given relative URI to a local file path. Returns + * AllocatedPath::Null() on error or if this storage does not + * support local files. + */ + gcc_pure + AllocatedPath MapFS(const char *uri_utf8) const; + + gcc_pure + AllocatedPath MapChildFS(const char *uri_utf8, + const char *child_utf8) const; + +private: + AllocatedPath MapFS(const char *uri_utf8, Error &error) const; +}; + +#endif -- cgit v1.2.3