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.
This commit is contained in:
parent
f8d114be42
commit
9ae7f186bc
15
Makefile.am
15
Makefile.am
@ -31,6 +31,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
|
|||||||
src_mpd_LDADD = \
|
src_mpd_LDADD = \
|
||||||
$(NEIGHBOR_LIBS) \
|
$(NEIGHBOR_LIBS) \
|
||||||
$(DB_LIBS) \
|
$(DB_LIBS) \
|
||||||
|
$(STORAGE_LIBS) \
|
||||||
$(PLAYLIST_LIBS) \
|
$(PLAYLIST_LIBS) \
|
||||||
$(AVAHI_LIBS) \
|
$(AVAHI_LIBS) \
|
||||||
$(LIBWRAP_LDFLAGS) \
|
$(LIBWRAP_LDFLAGS) \
|
||||||
@ -410,6 +411,20 @@ libfs_a_SOURCES = \
|
|||||||
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
|
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
|
||||||
src/fs/DirectoryReader.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
|
# neighbor plugins
|
||||||
|
|
||||||
if ENABLE_NEIGHBOR_PLUGINS
|
if ENABLE_NEIGHBOR_PLUGINS
|
||||||
|
@ -193,7 +193,8 @@ map_uri_fs(const char *uri)
|
|||||||
return AllocatedPath::Build(music_dir_fs, uri_fs);
|
return AllocatedPath::Build(music_dir_fs, uri_fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
AllocatedPath
|
gcc_pure
|
||||||
|
static AllocatedPath
|
||||||
map_directory_fs(const Directory &directory)
|
map_directory_fs(const Directory &directory)
|
||||||
{
|
{
|
||||||
assert(!music_dir_fs.IsNull());
|
assert(!music_dir_fs.IsNull());
|
||||||
@ -204,7 +205,15 @@ map_directory_fs(const Directory &directory)
|
|||||||
return map_uri_fs(directory.GetPath());
|
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)
|
map_directory_child_fs(const Directory &directory, const char *name)
|
||||||
{
|
{
|
||||||
assert(!music_dir_fs.IsNull());
|
assert(!music_dir_fs.IsNull());
|
||||||
|
@ -91,28 +91,6 @@ gcc_pure
|
|||||||
AllocatedPath
|
AllocatedPath
|
||||||
map_uri_fs(const char *uri);
|
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
|
* "Detach" the #Song object, i.e. convert it to a #DetachedSong
|
||||||
* instance.
|
* instance.
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
#include "db/DatabaseLock.hxx"
|
#include "db/DatabaseLock.hxx"
|
||||||
#include "db/Directory.hxx"
|
#include "db/Directory.hxx"
|
||||||
#include "db/Song.hxx"
|
#include "db/Song.hxx"
|
||||||
#include "Mapper.hxx"
|
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
#include "storage/FileInfo.hxx"
|
||||||
#include "archive/ArchiveList.hxx"
|
#include "archive/ArchiveList.hxx"
|
||||||
#include "archive/ArchivePlugin.hxx"
|
#include "archive/ArchivePlugin.hxx"
|
||||||
#include "archive/ArchiveFile.hxx"
|
#include "archive/ArchiveFile.hxx"
|
||||||
@ -103,20 +103,24 @@ class UpdateArchiveVisitor final : public ArchiveVisitor {
|
|||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name,
|
UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name,
|
||||||
const struct stat *st,
|
const FileInfo &info,
|
||||||
const archive_plugin &plugin)
|
const archive_plugin &plugin)
|
||||||
{
|
{
|
||||||
db_lock();
|
db_lock();
|
||||||
Directory *directory = parent.FindChild(name);
|
Directory *directory = parent.FindChild(name);
|
||||||
db_unlock();
|
db_unlock();
|
||||||
|
|
||||||
if (directory != nullptr && directory->mtime == st->st_mtime &&
|
if (directory != nullptr && directory->mtime == info.mtime &&
|
||||||
!walk_discard)
|
!walk_discard)
|
||||||
/* MPD has already scanned the archive, and it hasn't
|
/* MPD has already scanned the archive, and it hasn't
|
||||||
changed since - don't consider updating it */
|
changed since - don't consider updating it */
|
||||||
return;
|
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 */
|
/* open archive */
|
||||||
Error error;
|
Error error;
|
||||||
@ -141,7 +145,7 @@ UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name,
|
|||||||
db_unlock();
|
db_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
directory->mtime = st->st_mtime;
|
directory->mtime = info.mtime;
|
||||||
|
|
||||||
UpdateArchiveVisitor visitor(*this, directory);
|
UpdateArchiveVisitor visitor(*this, directory);
|
||||||
file->Visit(visitor);
|
file->Visit(visitor);
|
||||||
@ -151,13 +155,13 @@ UpdateWalk::UpdateArchiveFile(Directory &parent, const char *name,
|
|||||||
bool
|
bool
|
||||||
UpdateWalk::UpdateArchiveFile(Directory &directory,
|
UpdateWalk::UpdateArchiveFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st)
|
const FileInfo &info)
|
||||||
{
|
{
|
||||||
const struct archive_plugin *plugin =
|
const struct archive_plugin *plugin =
|
||||||
archive_plugin_from_suffix(suffix);
|
archive_plugin_from_suffix(suffix);
|
||||||
if (plugin == nullptr)
|
if (plugin == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
UpdateArchiveFile(directory, name, st, *plugin);
|
UpdateArchiveFile(directory, name, info, *plugin);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
#include "db/Song.hxx"
|
#include "db/Song.hxx"
|
||||||
#include "decoder/DecoderPlugin.hxx"
|
#include "decoder/DecoderPlugin.hxx"
|
||||||
#include "decoder/DecoderList.hxx"
|
#include "decoder/DecoderList.hxx"
|
||||||
#include "Mapper.hxx"
|
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
#include "storage/FileInfo.hxx"
|
||||||
#include "tag/TagHandler.hxx"
|
#include "tag/TagHandler.hxx"
|
||||||
#include "tag/TagBuilder.hxx"
|
#include "tag/TagBuilder.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@ -37,13 +37,13 @@
|
|||||||
|
|
||||||
Directory *
|
Directory *
|
||||||
UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name,
|
UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name,
|
||||||
const struct stat *st)
|
const FileInfo &info)
|
||||||
{
|
{
|
||||||
Directory *directory = parent.FindChild(name);
|
Directory *directory = parent.FindChild(name);
|
||||||
|
|
||||||
// directory exists already
|
// directory exists already
|
||||||
if (directory != nullptr) {
|
if (directory != nullptr) {
|
||||||
if (directory->mtime == st->st_mtime && !walk_discard) {
|
if (directory->mtime == info.mtime && !walk_discard) {
|
||||||
/* not modified */
|
/* not modified */
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ UpdateWalk::MakeDirectoryIfModified(Directory &parent, const char *name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
directory = parent.MakeChild(name);
|
directory = parent.MakeChild(name);
|
||||||
directory->mtime = st->st_mtime;
|
directory->mtime = info.mtime;
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix)
|
|||||||
bool
|
bool
|
||||||
UpdateWalk::UpdateContainerFile(Directory &directory,
|
UpdateWalk::UpdateContainerFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st)
|
const FileInfo &info)
|
||||||
{
|
{
|
||||||
const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){
|
const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){
|
||||||
return SupportsContainerSuffix(plugin, suffix);
|
return SupportsContainerSuffix(plugin, suffix);
|
||||||
@ -77,7 +77,7 @@ UpdateWalk::UpdateContainerFile(Directory &directory,
|
|||||||
const DecoderPlugin &plugin = *_plugin;
|
const DecoderPlugin &plugin = *_plugin;
|
||||||
|
|
||||||
db_lock();
|
db_lock();
|
||||||
Directory *contdir = MakeDirectoryIfModified(directory, name, st);
|
Directory *contdir = MakeDirectoryIfModified(directory, name, info);
|
||||||
if (contdir == nullptr) {
|
if (contdir == nullptr) {
|
||||||
/* not modified */
|
/* not modified */
|
||||||
db_unlock();
|
db_unlock();
|
||||||
@ -87,7 +87,13 @@ UpdateWalk::UpdateContainerFile(Directory &directory,
|
|||||||
contdir->device = DEVICE_CONTAINER;
|
contdir->device = DEVICE_CONTAINER;
|
||||||
db_unlock();
|
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;
|
char *vtrack;
|
||||||
unsigned int tnum = 0;
|
unsigned int tnum = 0;
|
||||||
@ -96,11 +102,10 @@ UpdateWalk::UpdateContainerFile(Directory &directory,
|
|||||||
Song *song = Song::NewFile(vtrack, *contdir);
|
Song *song = Song::NewFile(vtrack, *contdir);
|
||||||
|
|
||||||
// shouldn't be necessary but it's there..
|
// shouldn't be necessary but it's there..
|
||||||
song->mtime = st->st_mtime;
|
song->mtime = info.mtime;
|
||||||
|
|
||||||
const auto child_path_fs =
|
|
||||||
map_directory_child_fs(*contdir, vtrack);
|
|
||||||
|
|
||||||
|
const auto child_path_fs = AllocatedPath::Build(pathname,
|
||||||
|
vtrack);
|
||||||
plugin.ScanFile(child_path_fs.c_str(),
|
plugin.ScanFile(child_path_fs.c_str(),
|
||||||
add_tag_handler, &tag_builder);
|
add_tag_handler, &tag_builder);
|
||||||
|
|
||||||
|
@ -21,91 +21,84 @@
|
|||||||
#include "UpdateIO.hxx"
|
#include "UpdateIO.hxx"
|
||||||
#include "UpdateDomain.hxx"
|
#include "UpdateDomain.hxx"
|
||||||
#include "db/Directory.hxx"
|
#include "db/Directory.hxx"
|
||||||
#include "Mapper.hxx"
|
#include "storage/FileInfo.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "storage/LocalStorage.hxx"
|
||||||
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
|
#include "util/Error.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
int
|
bool
|
||||||
stat_directory(const Directory &directory, struct stat *st)
|
GetInfo(LocalStorage &storage, const char *uri_utf8, FileInfo &info)
|
||||||
{
|
{
|
||||||
const auto path_fs = map_directory_fs(directory);
|
Error error;
|
||||||
if (path_fs.IsNull())
|
bool success = storage.GetInfo(uri_utf8, true, info, error);
|
||||||
return -1;
|
if (!success)
|
||||||
|
LogError(error);
|
||||||
if (!StatFile(path_fs, *st)) {
|
return success;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
stat_directory_child(const Directory &parent, const char *name,
|
|
||||||
struct stat *st)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
directory_exists(const Directory &directory)
|
GetInfo(LocalDirectoryReader &reader, FileInfo &info)
|
||||||
{
|
{
|
||||||
const auto path_fs = map_directory_fs(directory);
|
Error error;
|
||||||
if (path_fs.IsNull())
|
bool success = reader.GetInfo(true, info, error);
|
||||||
/* invalid path: cannot exist */
|
if (!success)
|
||||||
|
LogError(error);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
DirectoryExists(LocalStorage &storage, const Directory &directory)
|
||||||
|
{
|
||||||
|
FileInfo info;
|
||||||
|
if (!storage.GetInfo(directory.GetPath(), true, info, IgnoreError()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return directory.device == DEVICE_INARCHIVE ||
|
return directory.device == DEVICE_INARCHIVE ||
|
||||||
directory.device == DEVICE_CONTAINER
|
directory.device == DEVICE_CONTAINER
|
||||||
? FileExists(path_fs)
|
? info.IsRegular()
|
||||||
: DirectoryExists(path_fs);
|
: 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
|
bool
|
||||||
directory_child_is_regular(const Directory &directory,
|
directory_child_is_regular(LocalStorage &storage, const Directory &directory,
|
||||||
const char *name_utf8)
|
const char *name_utf8)
|
||||||
{
|
{
|
||||||
const auto path_fs = map_directory_child_fs(directory, name_utf8);
|
FileInfo info;
|
||||||
if (path_fs.IsNull())
|
return GetDirectoryChildInfo(storage, directory, name_utf8, info,
|
||||||
return false;
|
IgnoreError()) &&
|
||||||
|
info.IsRegular();
|
||||||
return FileExists(path_fs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
directory_child_access(const Directory &directory,
|
directory_child_access(LocalStorage &storage, const Directory &directory,
|
||||||
const char *name, int mode)
|
const char *name, int mode)
|
||||||
{
|
{
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
/* CheckAccess() is useless on WIN32 */
|
/* CheckAccess() is useless on WIN32 */
|
||||||
|
(void)storage;
|
||||||
(void)directory;
|
(void)directory;
|
||||||
(void)name;
|
(void)name;
|
||||||
(void)mode;
|
(void)mode;
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
const auto path = map_directory_child_fs(directory, name);
|
const auto path = storage.MapChildFS(directory.GetPath(), name);
|
||||||
if (path.IsNull())
|
if (path.IsNull())
|
||||||
/* something went wrong, but that isn't a permission
|
/* does not point to local file: silently ignore the
|
||||||
problem */
|
check */
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return CheckAccess(path, mode) || errno != EACCES;
|
return CheckAccess(path, mode) || errno != EACCES;
|
||||||
|
@ -23,24 +23,32 @@
|
|||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
struct Directory;
|
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,
|
* Wrapper for LocalDirectoryReader::GetInfo() that logs errors
|
||||||
struct stat *st);
|
* instead of returning them.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
GetInfo(LocalDirectoryReader &reader, FileInfo &info);
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool
|
bool
|
||||||
directory_exists(const Directory &directory);
|
DirectoryExists(LocalStorage &storage, const Directory &directory);
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool
|
bool
|
||||||
directory_child_is_regular(const Directory &directory,
|
directory_child_is_regular(LocalStorage &storage, const Directory &directory,
|
||||||
const char *name_utf8);
|
const char *name_utf8);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,7 +56,7 @@ directory_child_is_regular(const Directory &directory,
|
|||||||
*/
|
*/
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool
|
bool
|
||||||
directory_child_access(const Directory &directory,
|
directory_child_access(LocalStorage &storage, const Directory &directory,
|
||||||
const char *name, int mode);
|
const char *name, int mode);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "db/Directory.hxx"
|
#include "db/Directory.hxx"
|
||||||
#include "db/Song.hxx"
|
#include "db/Song.hxx"
|
||||||
#include "decoder/DecoderList.hxx"
|
#include "decoder/DecoderList.hxx"
|
||||||
|
#include "storage/FileInfo.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -32,13 +33,13 @@
|
|||||||
inline void
|
inline void
|
||||||
UpdateWalk::UpdateSongFile2(Directory &directory,
|
UpdateWalk::UpdateSongFile2(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st)
|
const FileInfo &info)
|
||||||
{
|
{
|
||||||
db_lock();
|
db_lock();
|
||||||
Song *song = directory.FindSong(name);
|
Song *song = directory.FindSong(name);
|
||||||
db_unlock();
|
db_unlock();
|
||||||
|
|
||||||
if (!directory_child_access(directory, name, R_OK)) {
|
if (!directory_child_access(storage, directory, name, R_OK)) {
|
||||||
FormatError(update_domain,
|
FormatError(update_domain,
|
||||||
"no read permissions on %s/%s",
|
"no read permissions on %s/%s",
|
||||||
directory.GetPath(), name);
|
directory.GetPath(), name);
|
||||||
@ -48,9 +49,9 @@ UpdateWalk::UpdateSongFile2(Directory &directory,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(song != nullptr && st->st_mtime == song->mtime &&
|
if (!(song != nullptr && info.mtime == song->mtime &&
|
||||||
!walk_discard) &&
|
!walk_discard) &&
|
||||||
UpdateContainerFile(directory, name, suffix, st)) {
|
UpdateContainerFile(directory, name, suffix, info)) {
|
||||||
if (song != nullptr)
|
if (song != nullptr)
|
||||||
editor.LockDeleteSong(directory, song);
|
editor.LockDeleteSong(directory, song);
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ UpdateWalk::UpdateSongFile2(Directory &directory,
|
|||||||
modified = true;
|
modified = true;
|
||||||
FormatDefault(update_domain, "added %s/%s",
|
FormatDefault(update_domain, "added %s/%s",
|
||||||
directory.GetPath(), name);
|
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",
|
FormatDefault(update_domain, "updating %s/%s",
|
||||||
directory.GetPath(), name);
|
directory.GetPath(), name);
|
||||||
if (!song->UpdateFile()) {
|
if (!song->UpdateFile()) {
|
||||||
@ -92,11 +93,11 @@ UpdateWalk::UpdateSongFile2(Directory &directory,
|
|||||||
bool
|
bool
|
||||||
UpdateWalk::UpdateSongFile(Directory &directory,
|
UpdateWalk::UpdateSongFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st)
|
const FileInfo &info)
|
||||||
{
|
{
|
||||||
if (!decoder_plugins_supports_suffix(suffix))
|
if (!decoder_plugins_supports_suffix(suffix))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
UpdateSongFile2(directory, name, suffix, st);
|
UpdateSongFile2(directory, name, suffix, info);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,10 @@
|
|||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "fs/DirectoryReader.hxx"
|
#include "storage/FileInfo.hxx"
|
||||||
#include "util/Alloc.hxx"
|
#include "util/Alloc.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
|
#include "util/Error.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -47,7 +48,9 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
UpdateWalk::UpdateWalk(EventLoop &_loop, DatabaseListener &_listener)
|
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
|
#ifndef WIN32
|
||||||
follow_inside_symlinks =
|
follow_inside_symlinks =
|
||||||
@ -61,10 +64,10 @@ UpdateWalk::UpdateWalk(EventLoop &_loop, DatabaseListener &_listener)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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.inode = info.inode;
|
||||||
dir.device = st->st_dev;
|
dir.device = info.device;
|
||||||
dir.have_stat = true;
|
dir.have_stat = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +106,7 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory)
|
|||||||
{
|
{
|
||||||
Directory *child, *n;
|
Directory *child, *n;
|
||||||
directory_for_each_child_safe(child, n, directory) {
|
directory_for_each_child_safe(child, n, directory) {
|
||||||
if (directory_exists(*child))
|
if (DirectoryExists(storage, *child))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
editor.LockDeleteDirectory(child);
|
editor.LockDeleteDirectory(child);
|
||||||
@ -113,8 +116,8 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory)
|
|||||||
|
|
||||||
Song *song, *ns;
|
Song *song, *ns;
|
||||||
directory_for_each_song_safe(song, ns, directory) {
|
directory_for_each_song_safe(song, ns, directory) {
|
||||||
const auto path = map_song_fs(*song);
|
if (!directory_child_is_regular(storage, directory,
|
||||||
if (path.IsNull() || !FileExists(path)) {
|
song->uri)) {
|
||||||
editor.LockDeleteSong(directory, song);
|
editor.LockDeleteSong(directory, song);
|
||||||
|
|
||||||
modified = true;
|
modified = true;
|
||||||
@ -124,7 +127,8 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory)
|
|||||||
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(directory, i->name.c_str())) {
|
if (!directory_child_is_regular(storage, directory,
|
||||||
|
i->name.c_str())) {
|
||||||
db_lock();
|
db_lock();
|
||||||
i = directory.playlists.erase(i);
|
i = directory.playlists.erase(i);
|
||||||
db_unlock();
|
db_unlock();
|
||||||
@ -134,24 +138,26 @@ UpdateWalk::PurgeDeletedFromDirectory(Directory &directory)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
static int
|
static bool
|
||||||
update_directory_stat(Directory &directory)
|
update_directory_stat(LocalStorage &storage, Directory &directory)
|
||||||
{
|
{
|
||||||
struct stat st;
|
FileInfo info;
|
||||||
if (stat_directory(directory, &st) < 0)
|
if (!GetInfo(storage, directory.GetPath(), info))
|
||||||
return -1;
|
return false;
|
||||||
|
|
||||||
directory_set_stat(directory, &st);
|
directory_set_stat(directory, info);
|
||||||
return 0;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int
|
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
|
#ifndef WIN32
|
||||||
while (parent) {
|
while (parent) {
|
||||||
if (!parent->have_stat && update_directory_stat(*parent) < 0)
|
if (!parent->have_stat &&
|
||||||
|
!update_directory_stat(storage, *parent))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (parent->inode == inode && parent->device == device) {
|
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;
|
parent = parent->parent;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
(void)storage;
|
||||||
(void)parent;
|
(void)parent;
|
||||||
(void)inode;
|
(void)inode;
|
||||||
(void)device;
|
(void)device;
|
||||||
@ -173,12 +180,12 @@ find_inode_ancestor(Directory *parent, ino_t inode, dev_t device)
|
|||||||
inline bool
|
inline bool
|
||||||
UpdateWalk::UpdatePlaylistFile(Directory &directory,
|
UpdateWalk::UpdatePlaylistFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st)
|
const FileInfo &info)
|
||||||
{
|
{
|
||||||
if (!playlist_suffix_supported(suffix))
|
if (!playlist_suffix_supported(suffix))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
PlaylistInfo pi(name, st->st_mtime);
|
PlaylistInfo pi(name, info.mtime);
|
||||||
|
|
||||||
db_lock();
|
db_lock();
|
||||||
if (directory.playlists.UpdateOrInsert(std::move(pi)))
|
if (directory.playlists.UpdateOrInsert(std::move(pi)))
|
||||||
@ -189,27 +196,28 @@ UpdateWalk::UpdatePlaylistFile(Directory &directory,
|
|||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
UpdateWalk::UpdateRegularFile(Directory &directory,
|
UpdateWalk::UpdateRegularFile(Directory &directory,
|
||||||
const char *name, const struct stat *st)
|
const char *name, const FileInfo &info)
|
||||||
{
|
{
|
||||||
const char *suffix = uri_get_suffix(name);
|
const char *suffix = uri_get_suffix(name);
|
||||||
if (suffix == nullptr)
|
if (suffix == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return UpdateSongFile(directory, name, suffix, st) ||
|
return UpdateSongFile(directory, name, suffix, info) ||
|
||||||
UpdateArchiveFile(directory, name, suffix, st) ||
|
UpdateArchiveFile(directory, name, suffix, info) ||
|
||||||
UpdatePlaylistFile(directory, name, suffix, st);
|
UpdatePlaylistFile(directory, name, suffix, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
UpdateWalk::UpdateDirectoryChild(Directory &directory,
|
UpdateWalk::UpdateDirectoryChild(Directory &directory,
|
||||||
const char *name, const struct stat *st)
|
const char *name, const FileInfo &info)
|
||||||
{
|
{
|
||||||
assert(strchr(name, '/') == nullptr);
|
assert(strchr(name, '/') == nullptr);
|
||||||
|
|
||||||
if (S_ISREG(st->st_mode)) {
|
if (info.IsRegular()) {
|
||||||
UpdateRegularFile(directory, name, st);
|
UpdateRegularFile(directory, name, info);
|
||||||
} else if (S_ISDIR(st->st_mode)) {
|
} else if (info.IsDirectory()) {
|
||||||
if (find_inode_ancestor(&directory, st->st_ino, st->st_dev))
|
if (find_inode_ancestor(storage, &directory,
|
||||||
|
info.inode, info.device))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
db_lock();
|
db_lock();
|
||||||
@ -218,7 +226,7 @@ UpdateWalk::UpdateDirectoryChild(Directory &directory,
|
|||||||
|
|
||||||
assert(&directory == subdir->parent);
|
assert(&directory == subdir->parent);
|
||||||
|
|
||||||
if (!UpdateDirectory(*subdir, st))
|
if (!UpdateDirectory(*subdir, info))
|
||||||
editor.LockDeleteDirectory(subdir);
|
editor.LockDeleteDirectory(subdir);
|
||||||
} else {
|
} else {
|
||||||
FormatDebug(update_domain,
|
FormatDebug(update_domain,
|
||||||
@ -228,12 +236,10 @@ UpdateWalk::UpdateDirectoryChild(Directory &directory,
|
|||||||
|
|
||||||
/* we don't look at "." / ".." nor files with newlines in their name */
|
/* we don't look at "." / ".." nor files with newlines in their name */
|
||||||
gcc_pure
|
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 strchr(name_utf8, '\n') != nullptr;
|
||||||
return (path[0] == '.' && path[1] == 0) ||
|
|
||||||
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
|
|
||||||
strchr(path, '\n') != nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
@ -242,9 +248,11 @@ UpdateWalk::SkipSymlink(const Directory *directory,
|
|||||||
const char *utf8_name) const
|
const char *utf8_name) const
|
||||||
{
|
{
|
||||||
#ifndef WIN32
|
#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())
|
if (path_fs.IsNull())
|
||||||
return true;
|
/* not a local file: don't skip */
|
||||||
|
return false;
|
||||||
|
|
||||||
const auto target = ReadLink(path_fs);
|
const auto target = ReadLink(path_fs);
|
||||||
if (target.IsNull())
|
if (target.IsNull())
|
||||||
@ -304,63 +312,68 @@ UpdateWalk::SkipSymlink(const Directory *directory,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
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);
|
Error error;
|
||||||
if (path_fs.IsNull())
|
LocalDirectoryReader *const reader =
|
||||||
return false;
|
storage.OpenDirectory(directory.GetPath(), error);
|
||||||
|
if (reader == nullptr) {
|
||||||
DirectoryReader reader(path_fs);
|
LogError(error);
|
||||||
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());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExcludeList exclude_list;
|
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())
|
if (!exclude_list.IsEmpty())
|
||||||
RemoveExcludedFromDirectory(directory, exclude_list);
|
RemoveExcludedFromDirectory(directory, exclude_list);
|
||||||
|
|
||||||
PurgeDeletedFromDirectory(directory);
|
PurgeDeletedFromDirectory(directory);
|
||||||
|
|
||||||
while (reader.ReadEntry()) {
|
const char *name_utf8;
|
||||||
const auto entry = reader.GetEntry();
|
while ((name_utf8 = reader->Read()) != nullptr) {
|
||||||
|
if (skip_path(name_utf8))
|
||||||
if (skip_path(entry) || exclude_list.Check(entry))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const std::string utf8 = entry.ToUTF8();
|
{
|
||||||
if (utf8.empty())
|
const auto name_fs = AllocatedPath::FromUTF8(name_utf8);
|
||||||
continue;
|
if (name_fs.IsNull() || exclude_list.Check(name_fs))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (SkipSymlink(&directory, utf8.c_str())) {
|
if (SkipSymlink(&directory, name_utf8)) {
|
||||||
modified |= editor.DeleteNameIn(directory, utf8.c_str());
|
modified |= editor.DeleteNameIn(directory, name_utf8);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stat st2;
|
FileInfo info2;
|
||||||
if (stat_directory_child(directory, utf8.c_str(), &st2) == 0)
|
if (!GetInfo(*reader, info2)) {
|
||||||
UpdateDirectoryChild(directory, utf8.c_str(), &st2);
|
modified |= editor.DeleteNameIn(directory, name_utf8);
|
||||||
else
|
continue;
|
||||||
modified |= editor.DeleteNameIn(directory, utf8.c_str());
|
}
|
||||||
|
|
||||||
|
UpdateDirectoryChild(directory, name_utf8, info2);
|
||||||
}
|
}
|
||||||
|
|
||||||
directory.mtime = st->st_mtime;
|
directory.mtime = info.mtime;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Directory *
|
inline Directory *
|
||||||
UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8)
|
UpdateWalk::DirectoryMakeChildChecked(Directory &parent,
|
||||||
|
const char *uri_utf8,
|
||||||
|
const char *name_utf8)
|
||||||
{
|
{
|
||||||
db_lock();
|
db_lock();
|
||||||
Directory *directory = parent.FindChild(name_utf8);
|
Directory *directory = parent.FindChild(name_utf8);
|
||||||
@ -369,9 +382,9 @@ UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8)
|
|||||||
if (directory != nullptr)
|
if (directory != nullptr)
|
||||||
return directory;
|
return directory;
|
||||||
|
|
||||||
struct stat st;
|
FileInfo info;
|
||||||
if (stat_directory_child(parent, name_utf8, &st) < 0 ||
|
if (!GetInfo(storage, uri_utf8, info) ||
|
||||||
find_inode_ancestor(&parent, st.st_ino, st.st_dev))
|
find_inode_ancestor(storage, &parent, info.inode, info.device))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (SkipSymlink(&parent, name_utf8))
|
if (SkipSymlink(&parent, name_utf8))
|
||||||
@ -387,7 +400,7 @@ UpdateWalk::DirectoryMakeChildChecked(Directory &parent, const char *name_utf8)
|
|||||||
directory = parent.CreateChild(name_utf8);
|
directory = parent.CreateChild(name_utf8);
|
||||||
db_unlock();
|
db_unlock();
|
||||||
|
|
||||||
directory_set_stat(*directory, &st);
|
directory_set_stat(*directory, info);
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,6 +418,7 @@ UpdateWalk::DirectoryMakeUriParentChecked(Directory &root, const char *uri)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
directory = DirectoryMakeChildChecked(*directory,
|
directory = DirectoryMakeChildChecked(*directory,
|
||||||
|
duplicated,
|
||||||
name_utf8);
|
name_utf8);
|
||||||
if (directory == nullptr)
|
if (directory == nullptr)
|
||||||
break;
|
break;
|
||||||
@ -425,12 +439,18 @@ UpdateWalk::UpdateUri(Directory &root, const char *uri)
|
|||||||
|
|
||||||
const char *name = PathTraitsUTF8::GetBase(uri);
|
const char *name = PathTraitsUTF8::GetBase(uri);
|
||||||
|
|
||||||
struct stat st;
|
if (SkipSymlink(parent, name)) {
|
||||||
if (!SkipSymlink(parent, name) &&
|
|
||||||
stat_directory_child(*parent, name, &st) == 0)
|
|
||||||
UpdateDirectoryChild(*parent, name, &st);
|
|
||||||
else
|
|
||||||
modified |= editor.DeleteNameIn(*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
|
bool
|
||||||
@ -442,10 +462,11 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard)
|
|||||||
if (path != nullptr && !isRootDirectory(path)) {
|
if (path != nullptr && !isRootDirectory(path)) {
|
||||||
UpdateUri(root, path);
|
UpdateUri(root, path);
|
||||||
} else {
|
} else {
|
||||||
struct stat st;
|
FileInfo info;
|
||||||
|
if (!GetInfo(storage, "", info))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (stat_directory(root, &st) == 0)
|
UpdateDirectory(root, info);
|
||||||
UpdateDirectory(root, &st);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return modified;
|
return modified;
|
||||||
|
@ -22,10 +22,12 @@
|
|||||||
|
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "Editor.hxx"
|
#include "Editor.hxx"
|
||||||
|
#include "storage/LocalStorage.hxx"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
struct stat;
|
struct stat;
|
||||||
|
struct FileInfo;
|
||||||
struct Directory;
|
struct Directory;
|
||||||
struct archive_plugin;
|
struct archive_plugin;
|
||||||
class ExcludeList;
|
class ExcludeList;
|
||||||
@ -46,6 +48,8 @@ class UpdateWalk final {
|
|||||||
bool walk_discard;
|
bool walk_discard;
|
||||||
bool modified;
|
bool modified;
|
||||||
|
|
||||||
|
LocalStorage storage;
|
||||||
|
|
||||||
DatabaseEditor editor;
|
DatabaseEditor editor;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -68,15 +72,15 @@ private:
|
|||||||
|
|
||||||
void UpdateSongFile2(Directory &directory,
|
void UpdateSongFile2(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st);
|
const FileInfo &info);
|
||||||
|
|
||||||
bool UpdateSongFile(Directory &directory,
|
bool UpdateSongFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st);
|
const FileInfo &info);
|
||||||
|
|
||||||
bool UpdateContainerFile(Directory &directory,
|
bool UpdateContainerFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st);
|
const FileInfo &info);
|
||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_ARCHIVE
|
#ifdef ENABLE_ARCHIVE
|
||||||
@ -84,10 +88,10 @@ private:
|
|||||||
|
|
||||||
bool UpdateArchiveFile(Directory &directory,
|
bool UpdateArchiveFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st);
|
const FileInfo &info);
|
||||||
|
|
||||||
void UpdateArchiveFile(Directory &directory, const char *name,
|
void UpdateArchiveFile(Directory &directory, const char *name,
|
||||||
const struct stat *st,
|
const FileInfo &info,
|
||||||
const archive_plugin &plugin);
|
const archive_plugin &plugin);
|
||||||
|
|
||||||
|
|
||||||
@ -95,22 +99,22 @@ private:
|
|||||||
bool UpdateArchiveFile(gcc_unused Directory &directory,
|
bool UpdateArchiveFile(gcc_unused Directory &directory,
|
||||||
gcc_unused const char *name,
|
gcc_unused const char *name,
|
||||||
gcc_unused const char *suffix,
|
gcc_unused const char *suffix,
|
||||||
gcc_unused const struct stat *st) {
|
gcc_unused const FileInfo &info) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool UpdatePlaylistFile(Directory &directory,
|
bool UpdatePlaylistFile(Directory &directory,
|
||||||
const char *name, const char *suffix,
|
const char *name, const char *suffix,
|
||||||
const struct stat *st);
|
const FileInfo &info);
|
||||||
|
|
||||||
bool UpdateRegularFile(Directory &directory,
|
bool UpdateRegularFile(Directory &directory,
|
||||||
const char *name, const struct stat *st);
|
const char *name, const FileInfo &info);
|
||||||
|
|
||||||
void UpdateDirectoryChild(Directory &directory,
|
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
|
* Create the specified directory object if it does not exist
|
||||||
@ -121,9 +125,10 @@ private:
|
|||||||
* The caller must lock the database.
|
* The caller must lock the database.
|
||||||
*/
|
*/
|
||||||
Directory *MakeDirectoryIfModified(Directory &parent, const char *name,
|
Directory *MakeDirectoryIfModified(Directory &parent, const char *name,
|
||||||
const struct stat *st);
|
const FileInfo &info);
|
||||||
|
|
||||||
Directory *DirectoryMakeChildChecked(Directory &parent,
|
Directory *DirectoryMakeChildChecked(Directory &parent,
|
||||||
|
const char *uri_utf8,
|
||||||
const char *name_utf8);
|
const char *name_utf8);
|
||||||
|
|
||||||
Directory *DirectoryMakeUriParentChecked(Directory &root,
|
Directory *DirectoryMakeUriParentChecked(Directory &root,
|
||||||
|
62
src/storage/FileInfo.hxx
Normal file
62
src/storage/FileInfo.hxx
Normal file
@ -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
|
154
src/storage/LocalStorage.cxx
Normal file
154
src/storage/LocalStorage.cxx
Normal file
@ -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);
|
||||||
|
}
|
89
src/storage/LocalStorage.hxx
Normal file
89
src/storage/LocalStorage.hxx
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user