aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am15
-rw-r--r--src/Mapper.cxx13
-rw-r--r--src/Mapper.hxx22
-rw-r--r--src/db/update/Archive.cxx18
-rw-r--r--src/db/update/Container.cxx27
-rw-r--r--src/db/update/UpdateIO.cxx91
-rw-r--r--src/db/update/UpdateIO.hxx28
-rw-r--r--src/db/update/UpdateSong.cxx15
-rw-r--r--src/db/update/Walk.cxx179
-rw-r--r--src/db/update/Walk.hxx27
-rw-r--r--src/storage/FileInfo.hxx62
-rw-r--r--src/storage/LocalStorage.cxx154
-rw-r--r--src/storage/LocalStorage.hxx89
13 files changed, 542 insertions, 198 deletions
diff --git a/Makefile.am b/Makefile.am
index 5e3f3b260..fe3932975 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,6 +31,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
src_mpd_LDADD = \
$(NEIGHBOR_LIBS) \
$(DB_LIBS) \
+ $(STORAGE_LIBS) \
$(PLAYLIST_LIBS) \
$(AVAHI_LIBS) \
$(LIBWRAP_LDFLAGS) \
@@ -410,6 +411,20 @@ libfs_a_SOURCES = \
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
src/fs/DirectoryReader.hxx
+# Storage library
+
+if ENABLE_DATABASE
+
+noinst_LIBRARIES += libstorage.a
+
+libstorage_a_SOURCES = \
+ src/storage/LocalStorage.cxx src/storage/LocalStorage.hxx \
+ src/storage/FileInfo.hxx
+
+STORAGE_LIBS = libstorage.a
+
+endif
+
# neighbor plugins
if ENABLE_NEIGHBOR_PLUGINS
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
@@ -92,28 +92,6 @@ 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 <errno.h>
#include <unistd.h>
-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 <sys/stat.h>
-
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 <unistd.h>
@@ -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 <assert.h>
@@ -47,7 +48,9 @@
#include <errno.h>
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 <sys/stat.h>
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 <time.h>
+#include <stdint.h>
+
+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 <string>
+
+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