From 9e2a31b156e3271b2ab545ac211a52799e078872 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sat, 24 Nov 2018 16:03:28 +0100 Subject: [PATCH 01/27] Fix comments in FileIterator --- source/cppfs/include/cppfs/FileIterator.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/cppfs/include/cppfs/FileIterator.h b/source/cppfs/include/cppfs/FileIterator.h index aa87985..7f2e9be 100644 --- a/source/cppfs/include/cppfs/FileIterator.h +++ b/source/cppfs/include/cppfs/FileIterator.h @@ -33,7 +33,7 @@ class CPPFS_API FileIterator * Constructor * * @param[in] backend - * Concrete file handle backend + * Concrete file iterator backend */ FileIterator(std::unique_ptr && backend); @@ -41,8 +41,8 @@ class CPPFS_API FileIterator * @brief * Copy constructor * - * @param[in] fileHandle - * Source handle + * @param[in] fileIterator + * Source iterator */ FileIterator(const FileIterator & fileIterator); @@ -50,8 +50,8 @@ class CPPFS_API FileIterator * @brief * Move constructor * - * @param[in] fileHandle - * Source handle + * @param[in] fileIterator + * Source iterator */ FileIterator(FileIterator && fileIterator); @@ -66,7 +66,7 @@ class CPPFS_API FileIterator * Copy operator * * @param[in] fileIterator - * Source handle + * Source iterator */ FileIterator & operator=(const FileIterator & fileIterator); From 0b917773a83c990bbfcf05ed5dddd8c3a025ed37 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sat, 24 Nov 2018 18:43:16 +0100 Subject: [PATCH 02/27] Make filesystem accessible through interfaces (FileHandle, FileIterator) --- source/cppfs/include/cppfs/FileHandle.h | 10 ++++++++++ source/cppfs/include/cppfs/FileIterator.h | 10 ++++++++++ source/cppfs/include/cppfs/fs.h | 12 +++++++++++- source/cppfs/source/FileHandle.cpp | 5 +++++ source/cppfs/source/FileIterator.cpp | 5 +++++ source/cppfs/source/fs.cpp | 13 ++++++++++--- 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/source/cppfs/include/cppfs/FileHandle.h b/source/cppfs/include/cppfs/FileHandle.h index 35ca416..5dbd6f1 100644 --- a/source/cppfs/include/cppfs/FileHandle.h +++ b/source/cppfs/include/cppfs/FileHandle.h @@ -16,6 +16,7 @@ namespace cppfs { +class AbstractFileSystem; class FileIterator; class FileVisitor; class Tree; @@ -104,6 +105,15 @@ class CPPFS_API FileHandle */ FileHandle & operator=(FileHandle && fileHandle); + /** + * @brief + * Get file system + * + * @return + * File system (can be null) + */ + AbstractFileSystem * fs() const; + /** * @brief * Get path diff --git a/source/cppfs/include/cppfs/FileIterator.h b/source/cppfs/include/cppfs/FileIterator.h index 7f2e9be..57d1daf 100644 --- a/source/cppfs/include/cppfs/FileIterator.h +++ b/source/cppfs/include/cppfs/FileIterator.h @@ -12,6 +12,7 @@ namespace cppfs { +class AbstractFileSystem; class AbstractFileIteratorBackend; @@ -109,6 +110,15 @@ class CPPFS_API FileIterator */ bool operator!=(const FileIterator & it) const; + /** + * @brief + * Get file system + * + * @return + * File system (can be null) + */ + AbstractFileSystem * fs() const; + protected: std::unique_ptr m_backend; diff --git a/source/cppfs/include/cppfs/fs.h b/source/cppfs/include/cppfs/fs.h index 092a69f..b4dc003 100644 --- a/source/cppfs/include/cppfs/fs.h +++ b/source/cppfs/include/cppfs/fs.h @@ -2,9 +2,10 @@ #pragma once +#include #include -#include +#include namespace cppfs @@ -23,6 +24,15 @@ namespace fs { +/** +* @brief +* Get local file system +* +* @return +* Pointer to local file system (never null) +*/ +CPPFS_API std::shared_ptr localFS(); + /** * @brief * Open a file or directory diff --git a/source/cppfs/source/FileHandle.cpp b/source/cppfs/source/FileHandle.cpp index 440d5a8..6c569f0 100644 --- a/source/cppfs/source/FileHandle.cpp +++ b/source/cppfs/source/FileHandle.cpp @@ -76,6 +76,11 @@ FileHandle & FileHandle::operator=(FileHandle && fileHandle) return *this; } +AbstractFileSystem * FileHandle::fs() const +{ + return m_backend ? m_backend->fs() : nullptr; +} + std::string FileHandle::path() const { return m_backend ? m_backend->path() : ""; diff --git a/source/cppfs/source/FileIterator.cpp b/source/cppfs/source/FileIterator.cpp index 6b6e2cb..2ddd826 100644 --- a/source/cppfs/source/FileIterator.cpp +++ b/source/cppfs/source/FileIterator.cpp @@ -107,5 +107,10 @@ bool FileIterator::operator!=(const FileIterator & it) const return !((*this) == it); } +AbstractFileSystem * FileIterator::fs() const +{ + return m_backend ? m_backend->fs() : nullptr; +} + } // namespace cppfs diff --git a/source/cppfs/source/fs.cpp b/source/cppfs/source/fs.cpp index d552c3a..dbb1470 100644 --- a/source/cppfs/source/fs.cpp +++ b/source/cppfs/source/fs.cpp @@ -37,6 +37,13 @@ namespace fs { +std::shared_ptr localFS() +{ + static std::shared_ptr fs(new LocalFileSystem); + + return fs; +} + FileHandle open(const std::string & path, const LoginCredentials * credentials) { // Parse url @@ -74,8 +81,8 @@ FileHandle open(const std::string & path, const LoginCredentials * credentials) // Open path return fs->open(localPath); -#else - return FileHandle{}; +#else + return FileHandle(); #endif } @@ -86,7 +93,7 @@ FileHandle open(const std::string & path, const LoginCredentials * credentials) std::string localPath = url.path(); // Open local file system - static std::shared_ptr fs(new LocalFileSystem); + auto fs = localFS(); // Open path return fs->open(localPath); From 299d797d137397370034d3893430052bd05dd8cf Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sat, 24 Nov 2018 20:27:30 +0100 Subject: [PATCH 03/27] Create structure for file system watchers and first implementation for linux --- source/cppfs/CMakeLists.txt | 10 + .../cppfs/include/cppfs/AbstractFileSystem.h | 15 ++ .../cppfs/AbstractFileWatcherBackend.h | 99 ++++++++++ source/cppfs/include/cppfs/FileWatcher.h | 173 ++++++++++++++++++ source/cppfs/include/cppfs/cppfs.h | 11 ++ .../include/cppfs/null/NullFileWatcher.h | 66 +++++++ .../include/cppfs/posix/LocalFileSystem.h | 1 + .../include/cppfs/posix/LocalFileWatcher.h | 56 ++++++ .../cppfs/include/cppfs/ssh/SshFileSystem.h | 1 + .../include/cppfs/windows/LocalFileSystem.h | 1 + .../include/cppfs/windows/LocalFileWatcher.h | 53 ++++++ source/cppfs/source/AbstractFileSystem.cpp | 2 + .../source/AbstractFileWatcherBackend.cpp | 26 +++ source/cppfs/source/FileWatcher.cpp | 96 ++++++++++ source/cppfs/source/null/NullFileWatcher.cpp | 43 +++++ source/cppfs/source/posix/LocalFileSystem.cpp | 9 + .../cppfs/source/posix/LocalFileWatcher.cpp | 106 +++++++++++ source/cppfs/source/ssh/SshFileSystem.cpp | 8 + .../cppfs/source/windows/LocalFileSystem.cpp | 8 + .../cppfs/source/windows/LocalFileWatcher.cpp | 42 +++++ source/examples/CMakeLists.txt | 1 + source/examples/cppfs-watch/CMakeLists.txt | 124 +++++++++++++ source/examples/cppfs-watch/main.cpp | 87 +++++++++ 23 files changed, 1038 insertions(+) create mode 100644 source/cppfs/include/cppfs/AbstractFileWatcherBackend.h create mode 100644 source/cppfs/include/cppfs/FileWatcher.h create mode 100644 source/cppfs/include/cppfs/null/NullFileWatcher.h create mode 100644 source/cppfs/include/cppfs/posix/LocalFileWatcher.h create mode 100644 source/cppfs/include/cppfs/windows/LocalFileWatcher.h create mode 100644 source/cppfs/source/AbstractFileWatcherBackend.cpp create mode 100644 source/cppfs/source/FileWatcher.cpp create mode 100644 source/cppfs/source/null/NullFileWatcher.cpp create mode 100644 source/cppfs/source/posix/LocalFileWatcher.cpp create mode 100644 source/cppfs/source/windows/LocalFileWatcher.cpp create mode 100644 source/examples/cppfs-watch/CMakeLists.txt create mode 100644 source/examples/cppfs-watch/main.cpp diff --git a/source/cppfs/CMakeLists.txt b/source/cppfs/CMakeLists.txt index aa37eda..8dd3ffd 100644 --- a/source/cppfs/CMakeLists.txt +++ b/source/cppfs/CMakeLists.txt @@ -55,10 +55,12 @@ set(headers ${include_path}/FileHandle.h ${include_path}/FileIterator.h ${include_path}/FileVisitor.h + ${include_path}/FileWatcher.h ${include_path}/FunctionalFileVisitor.h ${include_path}/AbstractFileSystem.h ${include_path}/AbstractFileHandleBackend.h ${include_path}/AbstractFileIteratorBackend.h + ${include_path}/AbstractFileWatcherBackend.h ${include_path}/InputStream.h ${include_path}/OutputStream.h ${include_path}/LoginCredentials.h @@ -69,9 +71,12 @@ set(headers ${include_path}/Change.h ${include_path}/units.h + ${include_path}/null/NullFileWatcher.h + ${include_path}/${localfs}/LocalFileSystem.h ${include_path}/${localfs}/LocalFileHandle.h ${include_path}/${localfs}/LocalFileIterator.h + ${include_path}/${localfs}/LocalFileWatcher.h ) set(sources @@ -80,10 +85,12 @@ set(sources ${source_path}/FileHandle.cpp ${source_path}/FileIterator.cpp ${source_path}/FileVisitor.cpp + ${source_path}/FileWatcher.cpp ${source_path}/FunctionalFileVisitor.cpp ${source_path}/AbstractFileSystem.cpp ${source_path}/AbstractFileHandleBackend.cpp ${source_path}/AbstractFileIteratorBackend.cpp + ${source_path}/AbstractFileWatcherBackend.cpp ${source_path}/InputStream.cpp ${source_path}/OutputStream.cpp ${source_path}/LoginCredentials.cpp @@ -93,9 +100,12 @@ set(sources ${source_path}/Diff.cpp ${source_path}/Change.cpp + ${source_path}/null/NullFileWatcher.cpp + ${source_path}/${localfs}/LocalFileSystem.cpp ${source_path}/${localfs}/LocalFileHandle.cpp ${source_path}/${localfs}/LocalFileIterator.cpp + ${source_path}/${localfs}/LocalFileWatcher.cpp ) if (OPTION_BUILD_SSH_BACKEND) diff --git a/source/cppfs/include/cppfs/AbstractFileSystem.h b/source/cppfs/include/cppfs/AbstractFileSystem.h index 1b8e684..e75914a 100644 --- a/source/cppfs/include/cppfs/AbstractFileSystem.h +++ b/source/cppfs/include/cppfs/AbstractFileSystem.h @@ -2,6 +2,7 @@ #pragma once +#include #include #include @@ -12,6 +13,8 @@ namespace cppfs class FileHandle; +class FileWatcher; +class AbstractFileWatcherBackend; /** @@ -50,6 +53,18 @@ class CPPFS_API AbstractFileSystem * Path to file or directory */ virtual FileHandle open(std::string && path) = 0; + + /** + * @brief + * Create a watcher for the file system + * + * @param[in] fileWatcher + * File watcher that owns the backend + * + * @return + * Watcher backend (must NOT be null!) + */ + virtual std::unique_ptr createFileWatcher(FileWatcher & fileWatcher) = 0; }; diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h new file mode 100644 index 0000000..6b2e5d7 --- /dev/null +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -0,0 +1,99 @@ + +#pragma once + + +#include + +#include + + +namespace cppfs +{ + + +class FileWatcher; +class AbstractFileSystem; + + +/** +* @brief +* Interface for file watchers +*/ +class CPPFS_API AbstractFileWatcherBackend +{ +public: + /** + * @brief + * Constructor + * + * @param[in] fileWatcher + * File watcher that owns the backend + */ + AbstractFileWatcherBackend(FileWatcher & fileWatcher); + + /** + * @brief + * Destructor + */ + virtual ~AbstractFileWatcherBackend(); + + /** + * @brief + * Create a copy of this watcher + * + * @return + * File watcher + */ + virtual std::unique_ptr clone() const = 0; + + /** + * @brief + * Get file system + * + * @return + * File system (must NOT be null) + */ + virtual AbstractFileSystem * fs() const = 0; + + /** + * @brief + * Watch file handle + * + * @param[in] path + * File path + * @param[in] mode + * Watch mode (combination of FileEvent values) + */ + virtual void add(const std::string & path, unsigned int mode) = 0; + + /** + * @brief + * Start watching files (blocking) + * + * @remarks + * The function is supposed to block until one or more file system + * events have occured. For each event, onFileEvent has to be called. + * After all events have been processed, the function shall return. + */ + virtual void watch() = 0; + + +protected: + /** + * @brief + * Called on each file event + * + * @param[in] path + * Path to file or directory + * @param[in] event + * Type of event that has occured + */ + void onFileEvent(const std::string & path, FileEvent event); + + +protected: + FileWatcher & m_fileWatcher; ///< File watcher that owns the backend +}; + + +} // namespace cppfs diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h new file mode 100644 index 0000000..2afdb34 --- /dev/null +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -0,0 +1,173 @@ + +#pragma once + + +#include +#include + +#include +#include + + +namespace cppfs +{ + + +class AbstractFileSystem; +class FileHandle; + + +/** +* @brief +* Watcher that reports on changes of files or directories +*/ +class CPPFS_API FileWatcher +{ + friend class AbstractFileWatcherBackend; + + +public: + /** + * @brief + * Callback function signature + */ + using CallbackFunc = std::function; + + +public: + /** + * @brief + * Constructor for the local file system + * + * @remarks + * Creates a file watcher for the local file system. + */ + FileWatcher(); + + /** + * @brief + * Constructor + * + * @param[in] fs + * Filesystem for which the watcher is created (can be null) + * + * @remarks + * If fs == nullptr, the watcher will not be usable with any + * file system handles. + */ + FileWatcher(AbstractFileSystem * fs); + + /** + * @brief + * Copy constructor (deleted) + * + * @param[in] fileWatcher + * Source watcher + * + * @remarks + * File watcher objects cannot be copied, as it would make little sense + * to duplicate all the watcher tasks and registered callbacks. + */ + FileWatcher(const FileWatcher & fileWatcher) = delete; + + /** + * @brief + * Move constructor + * + * @param[in] fileWatcher + * Source watcher + */ + FileWatcher(FileWatcher && fileWatcher); + + /** + * @brief + * Destructor + */ + virtual ~FileWatcher(); + + /** + * @brief + * Copy operator (deleted) + * + * @param[in] fileWatcher + * Source watcher + * + * @remarks + * File watcher objects cannot be copied, as it would make little sense + * to duplicate all the watcher tasks and registered callbacks. + */ + FileWatcher & operator=(const FileWatcher & fileWatcher) = delete; + + /** + * @brief + * Move operator + * + * @param[in] fileWatcher + * Source watcher + */ + FileWatcher & operator=(FileWatcher && fileWatcher); + + /** + * @brief + * Get file system + * + * @return + * File system (can be null) + */ + AbstractFileSystem * fs() const; + + /** + * @brief + * Watch file handle + * + * @param[in] fileHandle + * File handle + * @param[in] mode + * Watch mode (combination of FileEvent values) + * + * @remarks + * The file handle must belong to the same file system as the + * file watcher, otherwise it will be ignored. Therefore, one + * file watcher object can only be used to watch files on a + * single file system. Also note that file watching is not + * supported for remote file systems, such as SSH. + */ + void add(const FileHandle & fileHandle, unsigned int mode = FileCreated | FileRemoved | FileModified); + + /** + * @brief + * Start watching files (blocking) + * + * @remarks + * This function begins to watch the file system and blocks until + * one or more events have occured. On every event, onFileEvent() + * is called with the type of the event and a file handle to the + * file or directory. Afterwards, the function returns. + * To listen to more events, call watch() again. + */ + void watch(); + + +protected: + /** + * @brief + * Called on each file event + * + * @param[in] path + * Path to file or directory + * @param[in] event + * Type of event that has occured + * + * @remarks + * The default implementation checks the event and calls + * the registered callback functions. + */ + virtual void onFileEvent(const std::string & path, FileEvent event); + + +protected: + std::unique_ptr m_backend; ///< Backend implementation (can be null) +}; + + +} // namespace cppfs diff --git a/source/cppfs/include/cppfs/cppfs.h b/source/cppfs/include/cppfs/cppfs.h index 132ec81..314a108 100644 --- a/source/cppfs/include/cppfs/cppfs.h +++ b/source/cppfs/include/cppfs/cppfs.h @@ -29,5 +29,16 @@ enum FilePermissions Sticky = 01000 }; +/** +* @brief +* Type of event on the file system +*/ +enum FileEvent +{ + FileCreated = 0x01, ///< A file or directory has been created + FileRemoved = 0x02, ///< A file or directory has been removed + FileModified = 0x04 ///< A file or directory has been modified +}; + } // namespace cppfs diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h new file mode 100644 index 0000000..da62878 --- /dev/null +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -0,0 +1,66 @@ + +#pragma once + + +#include + +#include + + +namespace cppfs +{ + + +class AbstractFileSystem; + + +/** +* @brief +* Empty implementation of the file watcher interface +* +* @remarks +* This class is used for file systems that do not support +* file system watchers, such as remote file systems. +*/ +class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend +{ +public: + /** + * @brief + * Constructor + * + * @param[in] fileWatcher + * File watcher that owns the backend + */ + NullFileWatcher(FileWatcher & fileWatcher); + + /** + * @brief + * Constructor + * + * @param[in] fileWatcher + * File watcher that owns the backend + * @param[in] fs + * File system that created this watcher + */ + NullFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs); + + /** + * @brief + * Destructor + */ + virtual ~NullFileWatcher(); + + // Virtual AbstractFileWatcherBackend functions + virtual std::unique_ptr clone() const override; + virtual AbstractFileSystem * fs() const override; + virtual void add(const std::string & path, unsigned int mode) override; + virtual void watch() override; + + +protected: + std::shared_ptr m_fs; ///< File system that created this watcher +}; + + +} // namespace cppfs diff --git a/source/cppfs/include/cppfs/posix/LocalFileSystem.h b/source/cppfs/include/cppfs/posix/LocalFileSystem.h index df75723..9130b41 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileSystem.h +++ b/source/cppfs/include/cppfs/posix/LocalFileSystem.h @@ -33,6 +33,7 @@ class CPPFS_API LocalFileSystem : public AbstractFileSystem, public std::enable_ // Virtual AbstractFileSystem functions virtual FileHandle open(const std::string & path) override; virtual FileHandle open(std::string && path) override; + virtual std::unique_ptr createFileWatcher(FileWatcher & fileWatcher) override; }; diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h new file mode 100644 index 0000000..ce0c180 --- /dev/null +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -0,0 +1,56 @@ + +#pragma once + + +#include +#include + +#include + + +namespace cppfs +{ + + +class LocalFileSystem; + + +/** +* @brief +* File watcher for the local file system +*/ +class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend +{ +public: + /** + * @brief + * Constructor + * + * @param[in] fileWatcher + * File watcher that owns the backend + * @param[in] fs + * File system that created this watcher + */ + LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs); + + /** + * @brief + * Destructor + */ + virtual ~LocalFileWatcher(); + + // Virtual AbstractFileWatcherBackend functions + virtual std::unique_ptr clone() const override; + virtual AbstractFileSystem * fs() const override; + virtual void add(const std::string & path, unsigned int mode) override; + virtual void watch() override; + + +protected: + std::shared_ptr m_fs; ///< File system that created this watcher + int m_inotify; ///< File handle for the inotify instance + std::vector m_watchers; ///< List of watcher handles +}; + + +} // namespace cppfs diff --git a/source/cppfs/include/cppfs/ssh/SshFileSystem.h b/source/cppfs/include/cppfs/ssh/SshFileSystem.h index 7735efd..94ac741 100644 --- a/source/cppfs/include/cppfs/ssh/SshFileSystem.h +++ b/source/cppfs/include/cppfs/ssh/SshFileSystem.h @@ -86,6 +86,7 @@ class CPPFS_API SshFileSystem : public AbstractFileSystem, public std::enable_sh // Virtual AbstractFileSystem functions virtual FileHandle open(const std::string & path) override; virtual FileHandle open(std::string && path) override; + virtual std::unique_ptr createFileWatcher(FileWatcher & fileWatcher) override; protected: diff --git a/source/cppfs/include/cppfs/windows/LocalFileSystem.h b/source/cppfs/include/cppfs/windows/LocalFileSystem.h index df75723..9130b41 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileSystem.h +++ b/source/cppfs/include/cppfs/windows/LocalFileSystem.h @@ -33,6 +33,7 @@ class CPPFS_API LocalFileSystem : public AbstractFileSystem, public std::enable_ // Virtual AbstractFileSystem functions virtual FileHandle open(const std::string & path) override; virtual FileHandle open(std::string && path) override; + virtual std::unique_ptr createFileWatcher(FileWatcher & fileWatcher) override; }; diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h new file mode 100644 index 0000000..0e4731a --- /dev/null +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -0,0 +1,53 @@ + +#pragma once + + +#include + +#include + + +namespace cppfs +{ + + +class LocalFileSystem; + + +/** +* @brief +* File watcher for the local file system +*/ +class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend +{ +public: + /** + * @brief + * Constructor + * + * @param[in] fileWatcher + * File watcher that owns the backend + * @param[in] fs + * File system that created this watcher + */ + LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs); + + /** + * @brief + * Destructor + */ + virtual ~LocalFileWatcher(); + + // Virtual AbstractFileWatcherBackend functions + virtual std::unique_ptr clone() const override; + virtual AbstractFileSystem * fs() const override; + virtual void add(const std::string & path, unsigned int mode) override; + virtual void watch() override; + + +protected: + std::shared_ptr m_fs; ///< File system that created this watcher +}; + + +} // namespace cppfs diff --git a/source/cppfs/source/AbstractFileSystem.cpp b/source/cppfs/source/AbstractFileSystem.cpp index 42048e0..4c52970 100644 --- a/source/cppfs/source/AbstractFileSystem.cpp +++ b/source/cppfs/source/AbstractFileSystem.cpp @@ -1,6 +1,8 @@ #include +#include + namespace cppfs { diff --git a/source/cppfs/source/AbstractFileWatcherBackend.cpp b/source/cppfs/source/AbstractFileWatcherBackend.cpp new file mode 100644 index 0000000..11d4451 --- /dev/null +++ b/source/cppfs/source/AbstractFileWatcherBackend.cpp @@ -0,0 +1,26 @@ + +#include + +#include + + +namespace cppfs +{ + + +AbstractFileWatcherBackend::AbstractFileWatcherBackend(FileWatcher & fileWatcher) +: m_fileWatcher(fileWatcher) +{ +} + +AbstractFileWatcherBackend::~AbstractFileWatcherBackend() +{ +} + +void AbstractFileWatcherBackend::onFileEvent(const std::string & path, FileEvent event) +{ + m_fileWatcher.onFileEvent(path, event); +} + + +} // namespace cppfs diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp new file mode 100644 index 0000000..d9d45c3 --- /dev/null +++ b/source/cppfs/source/FileWatcher.cpp @@ -0,0 +1,96 @@ + +#include + +#include + +#include +#include +#include +#include + + +namespace cppfs +{ + + +FileWatcher::FileWatcher() +: m_backend(fs::localFS()->createFileWatcher(*this)) +{ +} + +FileWatcher::FileWatcher(AbstractFileSystem * fs) +: m_backend(fs ? fs->createFileWatcher(*this) : nullptr) +{ +} + +FileWatcher::FileWatcher(FileWatcher && fileWatcher) +: m_backend(std::move(fileWatcher.m_backend)) +{ +} + +FileWatcher::~FileWatcher() +{ +} + +FileWatcher & FileWatcher::operator=(FileWatcher && fileWatcher) +{ + m_backend = std::move(fileWatcher.m_backend); + + return *this; +} + +AbstractFileSystem * FileWatcher::fs() const +{ + return m_backend ? m_backend->fs() : nullptr; +} + +void FileWatcher::add(const FileHandle & fileHandle, unsigned int mode) +{ + // Check backend + if (!m_backend) { + return; + } + + // Check that file handle belongs to the same file system as the watcher + if (fileHandle.fs() != fs()) { + return; + } + + // Add file to watcher + m_backend->add(fileHandle.path(), mode); +} + +void FileWatcher::watch() +{ + // Check backend + if (!m_backend) { + return; + } + + // Watch files + m_backend->watch(); +} + +void FileWatcher::onFileEvent(const std::string & path, FileEvent event) +{ + // [TODO] Pass FileHandle instead of string + // [TODO] Make sure that path is fully qualified + // [TODO] Add methods to register callback functions + + // Open file + FileHandle fh = fs::open(path); + + // Get file type + std::string type = (fh.isDirectory() ? "Directory" : "File"); + + // Get operation + std::string operation = ( (event & FileCreated) ? "created" : + ( (event & FileRemoved) ? "removed" : + "modified" ) ); + + // Log event + std::cout << type << " '" << path << "' has been " << operation << "." << std::endl; +} + + +} // name cppfs diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp new file mode 100644 index 0000000..a87ce5d --- /dev/null +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -0,0 +1,43 @@ + +#include + + +namespace cppfs +{ + + +NullFileWatcher::NullFileWatcher(FileWatcher & fileWatcher) +: AbstractFileWatcherBackend(fileWatcher) +{ +} + +NullFileWatcher::NullFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs) +: AbstractFileWatcherBackend(fileWatcher) +, m_fs(fs) +{ +} + +NullFileWatcher::~NullFileWatcher() +{ +} + +std::unique_ptr NullFileWatcher::clone() const +{ + return std::unique_ptr(new NullFileWatcher(m_fileWatcher, m_fs)); +} + +AbstractFileSystem * NullFileWatcher::fs() const +{ + return m_fs.get(); +} + +void NullFileWatcher::add(const std::string &, unsigned int) +{ +} + +void NullFileWatcher::watch() +{ +} + + +} // namespace cppfs diff --git a/source/cppfs/source/posix/LocalFileSystem.cpp b/source/cppfs/source/posix/LocalFileSystem.cpp index 1a644c8..c4aff82 100644 --- a/source/cppfs/source/posix/LocalFileSystem.cpp +++ b/source/cppfs/source/posix/LocalFileSystem.cpp @@ -2,7 +2,9 @@ #include #include +#include #include +#include namespace cppfs @@ -31,5 +33,12 @@ FileHandle LocalFileSystem::open(std::string && path) ); } +std::unique_ptr LocalFileSystem::createFileWatcher(FileWatcher & fileWatcher) +{ + return std::unique_ptr( + new LocalFileWatcher(fileWatcher, shared_from_this()) + ); +} + } // namespace cppfs diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp new file mode 100644 index 0000000..6a38d3f --- /dev/null +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -0,0 +1,106 @@ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace cppfs +{ + + +LocalFileWatcher::LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs) +: AbstractFileWatcherBackend(fileWatcher) +, m_fs(fs) +, m_inotify(-1) +{ + // Create inotify instance + m_inotify = inotify_init(); +} + +LocalFileWatcher::~LocalFileWatcher() +{ + // Close watch handles + for (int handle : m_watchers) { + inotify_rm_watch(m_inotify, handle); + } + + // Close inotify instance + close(m_inotify); +} + +std::unique_ptr LocalFileWatcher::clone() const +{ + // Clone watcher + return std::unique_ptr(new LocalFileWatcher(m_fileWatcher, m_fs)); +} + +AbstractFileSystem * LocalFileWatcher::fs() const +{ + // Return file system + return static_cast(m_fs.get()); +} + +void LocalFileWatcher::add(const std::string & path, unsigned int mode) +{ + // Get watch mode + uint32_t flags = 0; + if (mode & FileCreated) flags |= IN_CREATE; + if (mode & FileRemoved) flags |= IN_DELETE; + if (mode & FileModified) flags |= IN_MODIFY; + + // Create watcher + int handle = inotify_add_watch(m_inotify, path.c_str(), flags); + if (handle < 0) { + return; + } + + // Add watcher handle + m_watchers.push_back(handle); +} + +void LocalFileWatcher::watch() +{ + // Create buffer for receiving events + size_t bufSize = 64 * (sizeof(inotify_event) + NAME_MAX); + std::vector buffer; + buffer.resize(bufSize); + + // Read events + int numEvents = read(m_inotify, buffer.data(), bufSize); + if (numEvents < 0) { + return; + } + + // Process all events + for (int i=0; i(&buffer.data()[i]); + if (event->len) { + // Get path + std::string path = event->name; + + // Get event + FileEvent eventType = (FileEvent)0; + if (event->mask & IN_CREATE) eventType = FileCreated; + else if (event->mask & IN_DELETE) eventType = FileRemoved; + else if (event->mask & IN_MODIFY) eventType = FileModified; + + // Invoke callback function + onFileEvent(path, eventType); + } + + // Next event + i += sizeof(inotify_event) + event->len; + } +} + + +} // namespace cppfs diff --git a/source/cppfs/source/ssh/SshFileSystem.cpp b/source/cppfs/source/ssh/SshFileSystem.cpp index d1832aa..cf89cf9 100644 --- a/source/cppfs/source/ssh/SshFileSystem.cpp +++ b/source/cppfs/source/ssh/SshFileSystem.cpp @@ -17,6 +17,7 @@ #include #include +#include namespace cppfs @@ -70,6 +71,13 @@ FileHandle SshFileSystem::open(std::string && path) ); } +std::unique_ptr SshFileSystem::createFileWatcher(FileWatcher & fileWatcher) +{ + return std::unique_ptr( + new NullFileWatcher(fileWatcher, shared_from_this()) + ); +} + void SshFileSystem::connect() { #ifdef WIN32 diff --git a/source/cppfs/source/windows/LocalFileSystem.cpp b/source/cppfs/source/windows/LocalFileSystem.cpp index b24ffc6..3ed0ab5 100644 --- a/source/cppfs/source/windows/LocalFileSystem.cpp +++ b/source/cppfs/source/windows/LocalFileSystem.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace cppfs @@ -31,5 +32,12 @@ FileHandle LocalFileSystem::open(std::string && path) ); } +std::unique_ptr LocalFileSystem::createFileWatcher(FileWatcher & fileWatcher) +{ + return std::unique_ptr( + new LocalFileWatcher(fileWatcher, shared_from_this()) + ); +} + } // namespace cppfs diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp new file mode 100644 index 0000000..8e933e1 --- /dev/null +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -0,0 +1,42 @@ + +#include + +#include + + +namespace cppfs +{ + + +LocalFileWatcher::LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs) +: AbstractFileWatcherBackend(fileWatcher) +, m_fs(fs) +{ +} + +LocalFileWatcher::~LocalFileWatcher() +{ +} + +std::unique_ptr LocalFileWatcher::clone() const +{ + return std::unique_ptr(new LocalFileWatcher(m_fileWatcher, m_fs)); +} + +AbstractFileSystem * LocalFileWatcher::fs() const +{ + return static_cast(m_fs.get()); +} + +void LocalFileWatcher::add(const std::string &, unsigned int) +{ + // [TODO] Implement for Windows +} + +void LocalFileWatcher::watch() +{ + // [TODO] Implement for Windows +} + + +} // namespace cppfs diff --git a/source/examples/CMakeLists.txt b/source/examples/CMakeLists.txt index 020b4d7..ad8733c 100644 --- a/source/examples/CMakeLists.txt +++ b/source/examples/CMakeLists.txt @@ -11,3 +11,4 @@ add_subdirectory(cppfs-cp) add_subdirectory(cppfs-ln) add_subdirectory(cppfs-tree) add_subdirectory(cppfs-sync) +add_subdirectory(cppfs-watch) diff --git a/source/examples/cppfs-watch/CMakeLists.txt b/source/examples/cppfs-watch/CMakeLists.txt new file mode 100644 index 0000000..793771d --- /dev/null +++ b/source/examples/cppfs-watch/CMakeLists.txt @@ -0,0 +1,124 @@ + +# +# External dependencies +# + +find_package(cppassist REQUIRED) + + +# +# Executable name and options +# + +# Target name +set(target cppfs-watch) + +# Exit here if required dependencies are not met +message(STATUS "Example ${target}") + + +# +# Sources +# + +set(sources + main.cpp +) + + +# +# Create executable +# + +# Build executable +add_executable(${target} + ${sources} +) + +# Create namespaced alias +add_executable(${META_PROJECT_NAME}::${target} ALIAS ${target}) + + +# +# Project options +# + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" +) + + +# +# Include directories +# + +target_include_directories(${target} + PRIVATE + ${DEFAULT_INCLUDE_DIRECTORIES} + ${PROJECT_BINARY_DIR}/source/include +) + + +# +# Libraries +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LIBRARIES} + cppassist::cppassist + ${META_PROJECT_NAME}::cppfs +) + + +# +# Compile definitions +# + +target_compile_definitions(${target} + PRIVATE + ${DEFAULT_COMPILE_DEFINITIONS} +) + + +# +# Compile options +# + +target_compile_options(${target} + PRIVATE + ${DEFAULT_COMPILE_OPTIONS} +) + + +# +# Linker options +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LINKER_OPTIONS} +) + + +# +# Target Health +# + +perform_health_checks( + ${target} + ${sources} +) + + +# +# Deployment +# + +# Executable +install(TARGETS ${target} + RUNTIME DESTINATION ${INSTALL_BIN} COMPONENT examples + BUNDLE DESTINATION ${INSTALL_BIN} COMPONENT examples +) diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp new file mode 100644 index 0000000..412a807 --- /dev/null +++ b/source/examples/cppfs-watch/main.cpp @@ -0,0 +1,87 @@ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +using namespace cppassist; +using namespace cppfs; + + +int main(int argc, char * argv[]) +{ + // Declare program + CommandLineProgram program( + "cppfs-watch", + "cppfs-watch " CPPFS_VERSION, + "Listen to events in the file system and print them to the console." + ); + + CommandLineAction action("watch", "Watch files in directory"); + program.add(&action); + + CommandLineSwitch swHelp("--help", "-h", "Print help text", CommandLineSwitch::Optional); + action.add(&swHelp); + + CommandLineOption opConfig("--config", "-c", "file", "Load configuration from file", CommandLineOption::Optional); + action.add(&opConfig); + + CommandLineParameter paramPath("path", CommandLineParameter::Optional); + action.add(¶mPath); + + // Parse command line + program.parse(argc, argv); + if (program.hasErrors() || swHelp.activated()) + { + // Print help and exit + program.print(program.help()); + return 0; + } + + // Get login credentials + LoginCredentials login; + + std::string configFile = opConfig.value(); + if (!configFile.empty()) + { + login.load(configFile); + } + + // Get path + std::string path = paramPath.value(); + if (path.empty()) path = "."; + + // Create file system watcher + FileWatcher watcher; + + // Open directory + FileHandle dir = fs::open(path, &login); + if (dir.isDirectory()) + { + // Watch directory + watcher.add(dir); + + // Begin watching and print events + while (true) { + watcher.watch(); + } + } + else + { + std::cout << "'" << path << "' is not a valid directory." << std::endl; + } + + // Done + return 0; +} From 9d08a58ea918f6108cda6b7e50be740133933147 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sat, 24 Nov 2018 23:22:56 +0100 Subject: [PATCH 04/27] Remove clone() from file watcher interface --- source/cppfs/include/cppfs/AbstractFileWatcherBackend.h | 9 --------- source/cppfs/include/cppfs/null/NullFileWatcher.h | 1 - source/cppfs/include/cppfs/posix/LocalFileWatcher.h | 1 - source/cppfs/include/cppfs/windows/LocalFileWatcher.h | 1 - source/cppfs/source/null/NullFileWatcher.cpp | 5 ----- source/cppfs/source/posix/LocalFileWatcher.cpp | 6 ------ source/cppfs/source/windows/LocalFileWatcher.cpp | 5 ----- 7 files changed, 28 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index 6b2e5d7..82def27 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -37,15 +37,6 @@ class CPPFS_API AbstractFileWatcherBackend */ virtual ~AbstractFileWatcherBackend(); - /** - * @brief - * Create a copy of this watcher - * - * @return - * File watcher - */ - virtual std::unique_ptr clone() const = 0; - /** * @brief * Get file system diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index da62878..e4fb7fd 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -52,7 +52,6 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend virtual ~NullFileWatcher(); // Virtual AbstractFileWatcherBackend functions - virtual std::unique_ptr clone() const override; virtual AbstractFileSystem * fs() const override; virtual void add(const std::string & path, unsigned int mode) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index ce0c180..cfb10e2 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -40,7 +40,6 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend virtual ~LocalFileWatcher(); // Virtual AbstractFileWatcherBackend functions - virtual std::unique_ptr clone() const override; virtual AbstractFileSystem * fs() const override; virtual void add(const std::string & path, unsigned int mode) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index 0e4731a..f0709b2 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -39,7 +39,6 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend virtual ~LocalFileWatcher(); // Virtual AbstractFileWatcherBackend functions - virtual std::unique_ptr clone() const override; virtual AbstractFileSystem * fs() const override; virtual void add(const std::string & path, unsigned int mode) override; virtual void watch() override; diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp index a87ce5d..50b7d42 100644 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -21,11 +21,6 @@ NullFileWatcher::~NullFileWatcher() { } -std::unique_ptr NullFileWatcher::clone() const -{ - return std::unique_ptr(new NullFileWatcher(m_fileWatcher, m_fs)); -} - AbstractFileSystem * NullFileWatcher::fs() const { return m_fs.get(); diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index 6a38d3f..e767357 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -36,12 +36,6 @@ LocalFileWatcher::~LocalFileWatcher() close(m_inotify); } -std::unique_ptr LocalFileWatcher::clone() const -{ - // Clone watcher - return std::unique_ptr(new LocalFileWatcher(m_fileWatcher, m_fs)); -} - AbstractFileSystem * LocalFileWatcher::fs() const { // Return file system diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index 8e933e1..688dae8 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -18,11 +18,6 @@ LocalFileWatcher::~LocalFileWatcher() { } -std::unique_ptr LocalFileWatcher::clone() const -{ - return std::unique_ptr(new LocalFileWatcher(m_fileWatcher, m_fs)); -} - AbstractFileSystem * LocalFileWatcher::fs() const { return static_cast(m_fs.get()); From 89e4dd8c75e7a6503c70d2580040a193761daa58 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 00:17:52 +0100 Subject: [PATCH 05/27] Create file event handlers (analog to FileVisitor) --- source/cppfs/CMakeLists.txt | 8 +- source/cppfs/include/cppfs/FileEventHandler.h | 82 +++++++++++++++++++ source/cppfs/include/cppfs/FileWatcher.h | 49 +++++++++-- .../cppfs/FunctionalFileEventHandler.h | 60 ++++++++++++++ source/cppfs/source/FileEventHandler.cpp | 43 ++++++++++ source/cppfs/source/FileWatcher.cpp | 51 +++++++++--- .../source/FunctionalFileEventHandler.cpp | 31 +++++++ source/examples/cppfs-watch/main.cpp | 16 +++- 8 files changed, 320 insertions(+), 20 deletions(-) create mode 100644 source/cppfs/include/cppfs/FileEventHandler.h create mode 100644 source/cppfs/include/cppfs/FunctionalFileEventHandler.h create mode 100644 source/cppfs/source/FileEventHandler.cpp create mode 100644 source/cppfs/source/FunctionalFileEventHandler.cpp diff --git a/source/cppfs/CMakeLists.txt b/source/cppfs/CMakeLists.txt index 8dd3ffd..522b0ae 100644 --- a/source/cppfs/CMakeLists.txt +++ b/source/cppfs/CMakeLists.txt @@ -55,8 +55,10 @@ set(headers ${include_path}/FileHandle.h ${include_path}/FileIterator.h ${include_path}/FileVisitor.h - ${include_path}/FileWatcher.h ${include_path}/FunctionalFileVisitor.h + ${include_path}/FileWatcher.h + ${include_path}/FileEventHandler.h + ${include_path}/FunctionalFileEventHandler.h ${include_path}/AbstractFileSystem.h ${include_path}/AbstractFileHandleBackend.h ${include_path}/AbstractFileIteratorBackend.h @@ -85,8 +87,10 @@ set(sources ${source_path}/FileHandle.cpp ${source_path}/FileIterator.cpp ${source_path}/FileVisitor.cpp - ${source_path}/FileWatcher.cpp ${source_path}/FunctionalFileVisitor.cpp + ${source_path}/FileWatcher.cpp + ${source_path}/FileEventHandler.cpp + ${source_path}/FunctionalFileEventHandler.cpp ${source_path}/AbstractFileSystem.cpp ${source_path}/AbstractFileHandleBackend.cpp ${source_path}/AbstractFileIteratorBackend.cpp diff --git a/source/cppfs/include/cppfs/FileEventHandler.h b/source/cppfs/include/cppfs/FileEventHandler.h new file mode 100644 index 0000000..fad467d --- /dev/null +++ b/source/cppfs/include/cppfs/FileEventHandler.h @@ -0,0 +1,82 @@ + +#pragma once + + +#include + + +namespace cppfs +{ + + +class FileHandle; + + +/** +* @brief +* Handler that is informed about file system events +* +* @remarks +* Use FileWatcher to register file event handlers. +*/ +class CPPFS_API FileEventHandler +{ + friend class FileWatcher; + + +public: + /** + * @brief + * Constructor + */ + FileEventHandler(); + + /** + * @brief + * Destructor + */ + virtual ~FileEventHandler(); + + +protected: + /** + * @brief + * Called on file event + * + * @param[in] fh + * Handle to file or directory + * @param[in] event + * Type of event that has occured + */ + virtual void onFileEvent(FileHandle & fh, FileEvent event); + + /** + * @brief + * Called when a file or directory has been created + * + * @param[in] fh + * Handle to file or directory + */ + virtual void onFileCreated(FileHandle & fh); + + /** + * @brief + * Called when a file or directory has been removed + * + * @param[in] fh + * Handle to file or directory + */ + virtual void onFileRemoved(FileHandle & fh); + + /** + * @brief + * Called when a file or directory has been modified + * + * @param[in] fh + * Handle to file or directory + */ + virtual void onFileModified(FileHandle & fh); +}; + + +} // namespace cppfs diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index 2afdb34..e3d1e16 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -2,11 +2,13 @@ #pragma once +#include #include #include -#include #include +#include +#include namespace cppfs @@ -29,9 +31,9 @@ class CPPFS_API FileWatcher public: /** * @brief - * Callback function signature + * Callback function for file system events */ - using CallbackFunc = std::function; + using EventFunc = std::function; public: @@ -134,6 +136,39 @@ class CPPFS_API FileWatcher */ void add(const FileHandle & fileHandle, unsigned int mode = FileCreated | FileRemoved | FileModified); + /** + * @brief + * Add event handler + * + * @param[in] eventHandler + * File event handler (must NOT be null!) + * + * @remarks + * Adds the event handler to be called on each file system event. + */ + void addHandler(FileEventHandler * eventHandler); + + /** + * @brief + * Add callback function as event handler + * + * @param[in] funcFileEvent + * Function that is call on each file system event + */ + void addHandler(EventFunc funcFileEvent); + + /** + * @brief + * Remove event handler + * + * @param[in] eventHandler + * File event handler (must NOT be null!) + * + * @remarks + * Removes the event handler from the watcher. + */ + void removeHandler(FileEventHandler * eventHandler); + /** * @brief * Start watching files (blocking) @@ -151,7 +186,7 @@ class CPPFS_API FileWatcher protected: /** * @brief - * Called on each file event + * Called on file event * * @param[in] path * Path to file or directory @@ -166,7 +201,11 @@ class CPPFS_API FileWatcher protected: - std::unique_ptr m_backend; ///< Backend implementation (can be null) + std::unique_ptr m_backend; ///< Backend implementation (can be null) + std::vector m_eventHandlers; ///< List of registered file event handlers + + /// Functional event handlers that are owned by the file watcher + std::vector< std::unique_ptr > m_ownEventHandlers; }; diff --git a/source/cppfs/include/cppfs/FunctionalFileEventHandler.h b/source/cppfs/include/cppfs/FunctionalFileEventHandler.h new file mode 100644 index 0000000..dc2a400 --- /dev/null +++ b/source/cppfs/include/cppfs/FunctionalFileEventHandler.h @@ -0,0 +1,60 @@ + +#pragma once + + +#include + +#include + + +namespace cppfs +{ + + +/** +* @brief +* File event handler that calls a function +*/ +class CPPFS_API FunctionalFileEventHandler : public FileEventHandler +{ +public: + /** + * @brief + * Callback function for file system events + */ + using EventFunc = std::function; + + +public: + /** + * @brief + * Constructor + */ + FunctionalFileEventHandler(); + + /** + * @brief + * Constructor + * + * @param[in] funcFileEvent + * Function that is call on each file system event + */ + FunctionalFileEventHandler(EventFunc funcFileEvent); + + /** + * @brief + * Destructor + */ + virtual ~FunctionalFileEventHandler(); + + +protected: + virtual void onFileEvent(FileHandle & fh, FileEvent event) override; + + +protected: + EventFunc m_funcFileEvent; +}; + + +} // namespace cppfs diff --git a/source/cppfs/source/FileEventHandler.cpp b/source/cppfs/source/FileEventHandler.cpp new file mode 100644 index 0000000..a77d258 --- /dev/null +++ b/source/cppfs/source/FileEventHandler.cpp @@ -0,0 +1,43 @@ + +#include + +#include + + +namespace cppfs +{ + + +FileEventHandler::FileEventHandler() +{ +} + +FileEventHandler::~FileEventHandler() +{ +} + +void FileEventHandler::onFileEvent(FileHandle & fh, FileEvent event) +{ + if (event & FileCreated) { + onFileCreated(fh); + } else if (event & FileRemoved) { + onFileRemoved(fh); + } else if (event & FileModified) { + onFileModified(fh); + } +} + +void FileEventHandler::onFileCreated(FileHandle &) +{ +} + +void FileEventHandler::onFileRemoved(FileHandle &) +{ +} + +void FileEventHandler::onFileModified(FileHandle &) +{ +} + + +} // namespace cppfs diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index d9d45c3..46feee9 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -1,10 +1,11 @@ #include -#include +#include #include #include +#include #include #include @@ -60,6 +61,39 @@ void FileWatcher::add(const FileHandle & fileHandle, unsigned int mode) m_backend->add(fileHandle.path(), mode); } +void FileWatcher::addHandler(FileEventHandler * eventHandler) +{ + // Check that event handler is valid and not already registered + if (!eventHandler || std::find(m_eventHandlers.begin(), m_eventHandlers.end(), eventHandler) != m_eventHandlers.end()) { + return; + } + + // Add event handler to list + m_eventHandlers.push_back(eventHandler); +} + +void FileWatcher::addHandler(EventFunc funcFileEvent) +{ + // Create event handler + auto ptr = std::unique_ptr(new FunctionalFileEventHandler(funcFileEvent)); + + // Register event handler + addHandler(ptr.get()); + + // Take ownership + m_ownEventHandlers.push_back(std::move(ptr)); +} + +void FileWatcher::removeHandler(FileEventHandler * eventHandler) +{ + // Check if event handler is registered + auto it = std::find(m_eventHandlers.begin(), m_eventHandlers.end(), eventHandler); + if (it != m_eventHandlers.end()) { + // Remove event handler + m_eventHandlers.erase(it); + } +} + void FileWatcher::watch() { // Check backend @@ -75,21 +109,14 @@ void FileWatcher::onFileEvent(const std::string & path, FileEvent event) { // [TODO] Pass FileHandle instead of string // [TODO] Make sure that path is fully qualified - // [TODO] Add methods to register callback functions // Open file FileHandle fh = fs::open(path); - // Get file type - std::string type = (fh.isDirectory() ? "Directory" : "File"); - - // Get operation - std::string operation = ( (event & FileCreated) ? "created" : - ( (event & FileRemoved) ? "removed" : - "modified" ) ); - - // Log event - std::cout << type << " '" << path << "' has been " << operation << "." << std::endl; + // Call file event handlers + for (auto * eventHandler : m_eventHandlers) { + eventHandler->onFileEvent(fh, event); + } } diff --git a/source/cppfs/source/FunctionalFileEventHandler.cpp b/source/cppfs/source/FunctionalFileEventHandler.cpp new file mode 100644 index 0000000..a639356 --- /dev/null +++ b/source/cppfs/source/FunctionalFileEventHandler.cpp @@ -0,0 +1,31 @@ + +#include + + +namespace cppfs +{ + + +FunctionalFileEventHandler::FunctionalFileEventHandler() +{ +} + +FunctionalFileEventHandler::FunctionalFileEventHandler(EventFunc funcFileEvent) +: m_funcFileEvent(funcFileEvent) +{ +} + +FunctionalFileEventHandler::~FunctionalFileEventHandler() +{ +} + +void FunctionalFileEventHandler::onFileEvent(FileHandle & fh, FileEvent event) +{ + if (m_funcFileEvent) + { + m_funcFileEvent(fh, event); + } +} + + +} // namespace cppfs diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index 412a807..d1a92f9 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -72,7 +72,21 @@ int main(int argc, char * argv[]) // Watch directory watcher.add(dir); - // Begin watching and print events + // Create file event handler + watcher.addHandler([] (FileHandle & fh, FileEvent event) { + // Get file type + std::string type = (fh.isDirectory() ? "directory" : "file"); + + // Get operation + std::string operation = ( (event & FileCreated) ? "created" : + ( (event & FileRemoved) ? "removed" : + "modified" ) ); + + // Log event + std::cout << "The " << type << " '" << fh.path() << "' was " << operation << "." << std::endl; + }); + + // Begin watching and printing events while (true) { watcher.watch(); } From a8b0d3d10ad8a7177152ffd82d1a88efeeaf2ea1 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 01:19:51 +0100 Subject: [PATCH 06/27] Finish file watcher interface --- .../cppfs/AbstractFileWatcherBackend.h | 13 ++++++----- source/cppfs/include/cppfs/FileWatcher.h | 6 ++--- .../include/cppfs/null/NullFileWatcher.h | 2 +- .../include/cppfs/posix/LocalFileWatcher.h | 6 ++--- .../include/cppfs/windows/LocalFileWatcher.h | 2 +- .../source/AbstractFileWatcherBackend.cpp | 4 ++-- source/cppfs/source/FileWatcher.cpp | 12 +++------- source/cppfs/source/null/NullFileWatcher.cpp | 2 +- .../cppfs/source/posix/LocalFileWatcher.cpp | 22 ++++++++++--------- .../cppfs/source/windows/LocalFileWatcher.cpp | 2 +- 10 files changed, 34 insertions(+), 37 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index 82def27..e54714b 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -11,6 +11,7 @@ namespace cppfs { +class FileHandle; class FileWatcher; class AbstractFileSystem; @@ -50,12 +51,12 @@ class CPPFS_API AbstractFileWatcherBackend * @brief * Watch file handle * - * @param[in] path - * File path + * @param[in] fileHandle + * File handle * @param[in] mode * Watch mode (combination of FileEvent values) */ - virtual void add(const std::string & path, unsigned int mode) = 0; + virtual void add(const FileHandle & fileHandle, unsigned int mode) = 0; /** * @brief @@ -74,12 +75,12 @@ class CPPFS_API AbstractFileWatcherBackend * @brief * Called on each file event * - * @param[in] path - * Path to file or directory + * @param[in] fileHandle + * File handle * @param[in] event * Type of event that has occured */ - void onFileEvent(const std::string & path, FileEvent event); + void onFileEvent(FileHandle & fileHandle, FileEvent event); protected: diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index e3d1e16..cc630b5 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -188,8 +188,8 @@ class CPPFS_API FileWatcher * @brief * Called on file event * - * @param[in] path - * Path to file or directory + * @param[in] fileHandle + * File handle * @param[in] event * Type of event that has occured * @@ -197,7 +197,7 @@ class CPPFS_API FileWatcher * The default implementation checks the event and calls * the registered callback functions. */ - virtual void onFileEvent(const std::string & path, FileEvent event); + virtual void onFileEvent(FileHandle & fileHandle, FileEvent event); protected: diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index e4fb7fd..4157374 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -53,7 +53,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const std::string & path, unsigned int mode) override; + virtual void add(const FileHandle & fileHandle, unsigned int mode) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index cfb10e2..240bc0d 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -3,7 +3,7 @@ #include -#include +#include #include @@ -41,14 +41,14 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const std::string & path, unsigned int mode) override; + virtual void add(const FileHandle & fileHandle, unsigned int mode) override; virtual void watch() override; protected: std::shared_ptr m_fs; ///< File system that created this watcher int m_inotify; ///< File handle for the inotify instance - std::vector m_watchers; ///< List of watcher handles + std::map m_watchers; ///< Map of watch handle -> file handle }; diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index f0709b2..04fd0cf 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -40,7 +40,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const std::string & path, unsigned int mode) override; + virtual void add(const FileHandle & fileHandle, unsigned int mode) override; virtual void watch() override; diff --git a/source/cppfs/source/AbstractFileWatcherBackend.cpp b/source/cppfs/source/AbstractFileWatcherBackend.cpp index 11d4451..07ce8ff 100644 --- a/source/cppfs/source/AbstractFileWatcherBackend.cpp +++ b/source/cppfs/source/AbstractFileWatcherBackend.cpp @@ -17,9 +17,9 @@ AbstractFileWatcherBackend::~AbstractFileWatcherBackend() { } -void AbstractFileWatcherBackend::onFileEvent(const std::string & path, FileEvent event) +void AbstractFileWatcherBackend::onFileEvent(FileHandle & fileHandle, FileEvent event) { - m_fileWatcher.onFileEvent(path, event); + m_fileWatcher.onFileEvent(fileHandle, event); } diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index 46feee9..1319803 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -58,7 +58,7 @@ void FileWatcher::add(const FileHandle & fileHandle, unsigned int mode) } // Add file to watcher - m_backend->add(fileHandle.path(), mode); + m_backend->add(fileHandle, mode); } void FileWatcher::addHandler(FileEventHandler * eventHandler) @@ -105,17 +105,11 @@ void FileWatcher::watch() m_backend->watch(); } -void FileWatcher::onFileEvent(const std::string & path, FileEvent event) +void FileWatcher::onFileEvent(FileHandle & fileHandle, FileEvent event) { - // [TODO] Pass FileHandle instead of string - // [TODO] Make sure that path is fully qualified - - // Open file - FileHandle fh = fs::open(path); - // Call file event handlers for (auto * eventHandler : m_eventHandlers) { - eventHandler->onFileEvent(fh, event); + eventHandler->onFileEvent(fileHandle, event); } } diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp index 50b7d42..b3b8ae9 100644 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -26,7 +26,7 @@ AbstractFileSystem * NullFileWatcher::fs() const return m_fs.get(); } -void NullFileWatcher::add(const std::string &, unsigned int) +void NullFileWatcher::add(const FileHandle &, unsigned int) { } diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index e767357..d730b67 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -28,8 +29,8 @@ LocalFileWatcher::LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr(m_fs.get()); } -void LocalFileWatcher::add(const std::string & path, unsigned int mode) +void LocalFileWatcher::add(const FileHandle & fileHandle, unsigned int mode) { // Get watch mode uint32_t flags = 0; @@ -51,13 +52,13 @@ void LocalFileWatcher::add(const std::string & path, unsigned int mode) if (mode & FileModified) flags |= IN_MODIFY; // Create watcher - int handle = inotify_add_watch(m_inotify, path.c_str(), flags); + int handle = inotify_add_watch(m_inotify, fileHandle.path().c_str(), flags); if (handle < 0) { return; } - // Add watcher handle - m_watchers.push_back(handle); + // Associate watcher handle with file handle + m_watchers[handle] = fileHandle; } void LocalFileWatcher::watch() @@ -78,17 +79,18 @@ void LocalFileWatcher::watch() // Get event auto * event = reinterpret_cast(&buffer.data()[i]); if (event->len) { - // Get path - std::string path = event->name; - // Get event FileEvent eventType = (FileEvent)0; if (event->mask & IN_CREATE) eventType = FileCreated; else if (event->mask & IN_DELETE) eventType = FileRemoved; else if (event->mask & IN_MODIFY) eventType = FileModified; + // Get file handle + std::string path = event->name; + FileHandle fh = m_watchers[event->wd].open(path); + // Invoke callback function - onFileEvent(path, eventType); + onFileEvent(fh, eventType); } // Next event diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index 688dae8..a73849d 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -23,7 +23,7 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(const std::string &, unsigned int) +void LocalFileWatcher::add(const FileHandle &, unsigned int) { // [TODO] Implement for Windows } From ea51205513d9f96d726499176c80d448d3f179ef Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 01:51:22 +0100 Subject: [PATCH 07/27] Add mode to watch files/directories recursively --- .../cppfs/AbstractFileWatcherBackend.h | 8 ++-- source/cppfs/include/cppfs/FileWatcher.h | 8 ++-- source/cppfs/include/cppfs/cppfs.h | 10 +++++ .../include/cppfs/null/NullFileWatcher.h | 2 +- .../include/cppfs/posix/LocalFileWatcher.h | 17 ++++++- .../include/cppfs/windows/LocalFileWatcher.h | 2 +- source/cppfs/source/FileWatcher.cpp | 4 +- source/cppfs/source/null/NullFileWatcher.cpp | 2 +- .../cppfs/source/posix/LocalFileWatcher.cpp | 44 +++++++++++++++---- .../cppfs/source/windows/LocalFileWatcher.cpp | 2 +- 10 files changed, 76 insertions(+), 23 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index e54714b..7739e4e 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -53,10 +53,12 @@ class CPPFS_API AbstractFileWatcherBackend * * @param[in] fileHandle * File handle - * @param[in] mode - * Watch mode (combination of FileEvent values) + * @param[in] events + * Events that are watched (combination of FileEvent values) + * @param[in] recursive + * Watch file system recursively? (only relevant if fileHandle points to a directory) */ - virtual void add(const FileHandle & fileHandle, unsigned int mode) = 0; + virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) = 0; /** * @brief diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index cc630b5..d325e32 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -124,8 +124,10 @@ class CPPFS_API FileWatcher * * @param[in] fileHandle * File handle - * @param[in] mode - * Watch mode (combination of FileEvent values) + * @param[in] events + * Events that are watched (combination of FileEvent values) + * @param[in] recursive + * Watch file system recursively? (only relevant if fileHandle points to a directory) * * @remarks * The file handle must belong to the same file system as the @@ -134,7 +136,7 @@ class CPPFS_API FileWatcher * single file system. Also note that file watching is not * supported for remote file systems, such as SSH. */ - void add(const FileHandle & fileHandle, unsigned int mode = FileCreated | FileRemoved | FileModified); + void add(const FileHandle & fileHandle, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); /** * @brief diff --git a/source/cppfs/include/cppfs/cppfs.h b/source/cppfs/include/cppfs/cppfs.h index 314a108..1ce814c 100644 --- a/source/cppfs/include/cppfs/cppfs.h +++ b/source/cppfs/include/cppfs/cppfs.h @@ -40,5 +40,15 @@ enum FileEvent FileModified = 0x04 ///< A file or directory has been modified }; +/** +* @brief +* Recursive mode for operation that can run recursively or non-recursively +*/ +enum RecursiveMode +{ + NonRecursive = 0, ///< Run recursively + Recursive ///< Run non-recursively +}; + } // namespace cppfs diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index 4157374..104c1f3 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -53,7 +53,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const FileHandle & fileHandle, unsigned int mode) override; + virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index 240bc0d..260f942 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -6,6 +6,7 @@ #include #include +#include namespace cppfs @@ -41,14 +42,26 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const FileHandle & fileHandle, unsigned int mode) override; + virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; +protected: + /** + * @brief + * Watcher entry + */ + struct Watcher { + FileHandle fileHandle; + unsigned int events; + RecursiveMode recursive; + }; + + protected: std::shared_ptr m_fs; ///< File system that created this watcher int m_inotify; ///< File handle for the inotify instance - std::map m_watchers; ///< Map of watch handle -> file handle + std::map m_watchers; ///< Map of watch handle -> file handle }; diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index 04fd0cf..c2a277d 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -40,7 +40,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const FileHandle & fileHandle, unsigned int mode) override; + virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index 1319803..fa4f776 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -45,7 +45,7 @@ AbstractFileSystem * FileWatcher::fs() const return m_backend ? m_backend->fs() : nullptr; } -void FileWatcher::add(const FileHandle & fileHandle, unsigned int mode) +void FileWatcher::add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) { // Check backend if (!m_backend) { @@ -58,7 +58,7 @@ void FileWatcher::add(const FileHandle & fileHandle, unsigned int mode) } // Add file to watcher - m_backend->add(fileHandle, mode); + m_backend->add(fileHandle, events, recursive); } void FileWatcher::addHandler(FileEventHandler * eventHandler) diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp index b3b8ae9..f68c010 100644 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -26,7 +26,7 @@ AbstractFileSystem * NullFileWatcher::fs() const return m_fs.get(); } -void NullFileWatcher::add(const FileHandle &, unsigned int) +void NullFileWatcher::add(const FileHandle &, unsigned int, RecursiveMode) { } diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index d730b67..e19637e 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -43,13 +44,13 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(const FileHandle & fileHandle, unsigned int mode) +void LocalFileWatcher::add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) { // Get watch mode uint32_t flags = 0; - if (mode & FileCreated) flags |= IN_CREATE; - if (mode & FileRemoved) flags |= IN_DELETE; - if (mode & FileModified) flags |= IN_MODIFY; + if (events & FileCreated) flags |= IN_CREATE; + if (events & FileRemoved) flags |= IN_DELETE; + if (events & FileModified) flags |= IN_MODIFY; // Create watcher int handle = inotify_add_watch(m_inotify, fileHandle.path().c_str(), flags); @@ -57,8 +58,24 @@ void LocalFileWatcher::add(const FileHandle & fileHandle, unsigned int mode) return; } + // Watch directories recursively + if (fileHandle.isDirectory() && recursive == Recursive) { + // List directory entries + for (auto it = fileHandle.begin(); it != fileHandle.end(); ++it) + { + // Check if entry is a directory + FileHandle fh = fileHandle.open(*it); + if (fh.isDirectory()) { + // Watch directory + add(fh, events, recursive); + } + } + } + // Associate watcher handle with file handle - m_watchers[handle] = fileHandle; + m_watchers[handle].fileHandle = fileHandle; + m_watchers[handle].events = events; + m_watchers[handle].recursive = recursive; } void LocalFileWatcher::watch() @@ -69,13 +86,14 @@ void LocalFileWatcher::watch() buffer.resize(bufSize); // Read events - int numEvents = read(m_inotify, buffer.data(), bufSize); - if (numEvents < 0) { + int size = read(m_inotify, buffer.data(), bufSize); + if (size < 0) { return; } // Process all events - for (int i=0; i(&buffer.data()[i]); if (event->len) { @@ -85,9 +103,17 @@ void LocalFileWatcher::watch() else if (event->mask & IN_DELETE) eventType = FileRemoved; else if (event->mask & IN_MODIFY) eventType = FileModified; + // Get watcher + auto & watcher = m_watchers[event->wd]; + // Get file handle std::string path = event->name; - FileHandle fh = m_watchers[event->wd].open(path); + FileHandle fh = watcher.fileHandle.open(path); + + // Watch new directories + if (fh.isDirectory() && eventType == FileCreated && watcher.recursive == Recursive) { + add(fh, watcher.events, watcher.recursive); + } // Invoke callback function onFileEvent(fh, eventType); diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index a73849d..3e13512 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -23,7 +23,7 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(const FileHandle &, unsigned int) +void LocalFileWatcher::add(const FileHandle &, unsigned int, RecursiveMode) { // [TODO] Implement for Windows } From 85a657566cac1a4c42c9f6fe9b92785271df6963 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 01:53:42 +0100 Subject: [PATCH 08/27] Clean up file watcher interface --- source/cppfs/include/cppfs/AbstractFileWatcherBackend.h | 2 +- source/cppfs/include/cppfs/FileWatcher.h | 2 +- source/cppfs/include/cppfs/null/NullFileWatcher.h | 2 +- source/cppfs/include/cppfs/posix/LocalFileWatcher.h | 2 +- source/cppfs/include/cppfs/windows/LocalFileWatcher.h | 2 +- source/cppfs/source/FileWatcher.cpp | 2 +- source/cppfs/source/null/NullFileWatcher.cpp | 2 +- source/cppfs/source/posix/LocalFileWatcher.cpp | 2 +- source/cppfs/source/windows/LocalFileWatcher.cpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index 7739e4e..e94777b 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -58,7 +58,7 @@ class CPPFS_API AbstractFileWatcherBackend * @param[in] recursive * Watch file system recursively? (only relevant if fileHandle points to a directory) */ - virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) = 0; + virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) = 0; /** * @brief diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index d325e32..437af4d 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -136,7 +136,7 @@ class CPPFS_API FileWatcher * single file system. Also note that file watching is not * supported for remote file systems, such as SSH. */ - void add(const FileHandle & fileHandle, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); + void add(FileHandle & fileHandle, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); /** * @brief diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index 104c1f3..5ae025a 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -53,7 +53,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index 260f942..9f93018 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -42,7 +42,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index c2a277d..291749b 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -40,7 +40,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index fa4f776..fd91ce9 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -45,7 +45,7 @@ AbstractFileSystem * FileWatcher::fs() const return m_backend ? m_backend->fs() : nullptr; } -void FileWatcher::add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) +void FileWatcher::add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) { // Check backend if (!m_backend) { diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp index f68c010..f33babe 100644 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -26,7 +26,7 @@ AbstractFileSystem * NullFileWatcher::fs() const return m_fs.get(); } -void NullFileWatcher::add(const FileHandle &, unsigned int, RecursiveMode) +void NullFileWatcher::add(FileHandle &, unsigned int, RecursiveMode) { } diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index e19637e..83ad252 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -44,7 +44,7 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(const FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) +void LocalFileWatcher::add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) { // Get watch mode uint32_t flags = 0; diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index 3e13512..1c11e15 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -23,7 +23,7 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(const FileHandle &, unsigned int, RecursiveMode) +void LocalFileWatcher::add(FileHandle &, unsigned int, RecursiveMode) { // [TODO] Implement for Windows } From 2f068b16c64900d7a5ada1eac44d819dec933285 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 02:00:44 +0100 Subject: [PATCH 09/27] Clean up file watcher interface --- .../include/cppfs/AbstractFileWatcherBackend.h | 8 ++++---- source/cppfs/include/cppfs/FileWatcher.h | 10 +++++----- .../cppfs/include/cppfs/null/NullFileWatcher.h | 2 +- .../cppfs/include/cppfs/posix/LocalFileWatcher.h | 2 +- .../include/cppfs/windows/LocalFileWatcher.h | 2 +- .../cppfs/source/AbstractFileWatcherBackend.cpp | 4 ++-- source/cppfs/source/FileWatcher.cpp | 10 +++++----- source/cppfs/source/posix/LocalFileWatcher.cpp | 16 ++++++++-------- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index e94777b..aed1b3e 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -51,14 +51,14 @@ class CPPFS_API AbstractFileWatcherBackend * @brief * Watch file handle * - * @param[in] fileHandle + * @param[in] fh * File handle * @param[in] events * Events that are watched (combination of FileEvent values) * @param[in] recursive * Watch file system recursively? (only relevant if fileHandle points to a directory) */ - virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) = 0; + virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) = 0; /** * @brief @@ -77,12 +77,12 @@ class CPPFS_API AbstractFileWatcherBackend * @brief * Called on each file event * - * @param[in] fileHandle + * @param[in] fh * File handle * @param[in] event * Type of event that has occured */ - void onFileEvent(FileHandle & fileHandle, FileEvent event); + void onFileEvent(FileHandle & fh, FileEvent event); protected: diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index 437af4d..361dc9f 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -122,12 +122,12 @@ class CPPFS_API FileWatcher * @brief * Watch file handle * - * @param[in] fileHandle + * @param[in] fh * File handle * @param[in] events * Events that are watched (combination of FileEvent values) * @param[in] recursive - * Watch file system recursively? (only relevant if fileHandle points to a directory) + * Watch file system recursively? (only relevant if fh points to a directory) * * @remarks * The file handle must belong to the same file system as the @@ -136,7 +136,7 @@ class CPPFS_API FileWatcher * single file system. Also note that file watching is not * supported for remote file systems, such as SSH. */ - void add(FileHandle & fileHandle, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); + void add(FileHandle & fh, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); /** * @brief @@ -190,7 +190,7 @@ class CPPFS_API FileWatcher * @brief * Called on file event * - * @param[in] fileHandle + * @param[in] fh * File handle * @param[in] event * Type of event that has occured @@ -199,7 +199,7 @@ class CPPFS_API FileWatcher * The default implementation checks the event and calls * the registered callback functions. */ - virtual void onFileEvent(FileHandle & fileHandle, FileEvent event); + virtual void onFileEvent(FileHandle & fh, FileEvent event); protected: diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index 5ae025a..8fd2049 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -53,7 +53,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index 9f93018..53bf609 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -42,7 +42,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index 291749b..3378b7e 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -40,7 +40,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; virtual void watch() override; diff --git a/source/cppfs/source/AbstractFileWatcherBackend.cpp b/source/cppfs/source/AbstractFileWatcherBackend.cpp index 07ce8ff..729181a 100644 --- a/source/cppfs/source/AbstractFileWatcherBackend.cpp +++ b/source/cppfs/source/AbstractFileWatcherBackend.cpp @@ -17,9 +17,9 @@ AbstractFileWatcherBackend::~AbstractFileWatcherBackend() { } -void AbstractFileWatcherBackend::onFileEvent(FileHandle & fileHandle, FileEvent event) +void AbstractFileWatcherBackend::onFileEvent(FileHandle & fh, FileEvent event) { - m_fileWatcher.onFileEvent(fileHandle, event); + m_fileWatcher.onFileEvent(fh, event); } diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index fd91ce9..aff42e5 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -45,7 +45,7 @@ AbstractFileSystem * FileWatcher::fs() const return m_backend ? m_backend->fs() : nullptr; } -void FileWatcher::add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) +void FileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode recursive) { // Check backend if (!m_backend) { @@ -53,12 +53,12 @@ void FileWatcher::add(FileHandle & fileHandle, unsigned int events, RecursiveMod } // Check that file handle belongs to the same file system as the watcher - if (fileHandle.fs() != fs()) { + if (fh.fs() != fs()) { return; } // Add file to watcher - m_backend->add(fileHandle, events, recursive); + m_backend->add(fh, events, recursive); } void FileWatcher::addHandler(FileEventHandler * eventHandler) @@ -105,11 +105,11 @@ void FileWatcher::watch() m_backend->watch(); } -void FileWatcher::onFileEvent(FileHandle & fileHandle, FileEvent event) +void FileWatcher::onFileEvent(FileHandle & fh, FileEvent event) { // Call file event handlers for (auto * eventHandler : m_eventHandlers) { - eventHandler->onFileEvent(fileHandle, event); + eventHandler->onFileEvent(fh, event); } } diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index 83ad252..3aab0d1 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -44,7 +44,7 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(FileHandle & fileHandle, unsigned int events, RecursiveMode recursive) +void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode recursive) { // Get watch mode uint32_t flags = 0; @@ -53,27 +53,27 @@ void LocalFileWatcher::add(FileHandle & fileHandle, unsigned int events, Recursi if (events & FileModified) flags |= IN_MODIFY; // Create watcher - int handle = inotify_add_watch(m_inotify, fileHandle.path().c_str(), flags); + int handle = inotify_add_watch(m_inotify, fh.path().c_str(), flags); if (handle < 0) { return; } // Watch directories recursively - if (fileHandle.isDirectory() && recursive == Recursive) { + if (fh.isDirectory() && recursive == Recursive) { // List directory entries - for (auto it = fileHandle.begin(); it != fileHandle.end(); ++it) + for (auto it = fh.begin(); it != fh.end(); ++it) { // Check if entry is a directory - FileHandle fh = fileHandle.open(*it); - if (fh.isDirectory()) { + FileHandle fh2 = fh.open(*it); + if (fh2.isDirectory()) { // Watch directory - add(fh, events, recursive); + add(fh2, events, recursive); } } } // Associate watcher handle with file handle - m_watchers[handle].fileHandle = fileHandle; + m_watchers[handle].fileHandle = fh; m_watchers[handle].events = events; m_watchers[handle].recursive = recursive; } From 79b88ecc2d36b600d758740adf5490c4ab3e5430 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 03:18:26 +0100 Subject: [PATCH 10/27] Create convenience function for creating a watcher on a single file/directory --- .../cppfs/AbstractFileWatcherBackend.h | 9 +++++--- source/cppfs/include/cppfs/FileHandle.h | 23 ++++++++++++++++++- .../include/cppfs/null/NullFileWatcher.h | 8 +++---- .../include/cppfs/posix/LocalFileWatcher.h | 4 ++-- .../include/cppfs/windows/LocalFileWatcher.h | 4 ++-- .../source/AbstractFileWatcherBackend.cpp | 4 ++-- source/cppfs/source/FileHandle.cpp | 11 +++++++++ source/cppfs/source/FileWatcher.cpp | 13 ++++++++++- source/cppfs/source/null/NullFileWatcher.cpp | 4 ++-- source/cppfs/source/posix/LocalFileSystem.cpp | 2 +- .../cppfs/source/posix/LocalFileWatcher.cpp | 2 +- source/cppfs/source/ssh/SshFileSystem.cpp | 2 +- .../cppfs/source/windows/LocalFileSystem.cpp | 2 +- .../cppfs/source/windows/LocalFileWatcher.cpp | 2 +- source/examples/cppfs-watch/main.cpp | 5 +--- 15 files changed, 69 insertions(+), 26 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index aed1b3e..7cddbde 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -22,15 +22,18 @@ class AbstractFileSystem; */ class CPPFS_API AbstractFileWatcherBackend { + friend class FileWatcher; + + public: /** * @brief * Constructor * * @param[in] fileWatcher - * File watcher that owns the backend + * File watcher that owns the backend (must NOT be null!) */ - AbstractFileWatcherBackend(FileWatcher & fileWatcher); + AbstractFileWatcherBackend(FileWatcher * fileWatcher); /** * @brief @@ -86,7 +89,7 @@ class CPPFS_API AbstractFileWatcherBackend protected: - FileWatcher & m_fileWatcher; ///< File watcher that owns the backend + FileWatcher * m_fileWatcher; ///< File watcher that owns the backend (never null) }; diff --git a/source/cppfs/include/cppfs/FileHandle.h b/source/cppfs/include/cppfs/FileHandle.h index 5dbd6f1..47f99f4 100644 --- a/source/cppfs/include/cppfs/FileHandle.h +++ b/source/cppfs/include/cppfs/FileHandle.h @@ -8,7 +8,7 @@ #include #include -#include +#include #include @@ -20,6 +20,7 @@ class AbstractFileSystem; class FileIterator; class FileVisitor; class Tree; +class FileWatcher; /** @@ -488,6 +489,26 @@ class CPPFS_API FileHandle */ bool remove(); + /** + * @brief + * Create file system watcher for this file or directory + * + * @param[in] events + * Events that are watched (combination of FileEvent values) + * @param[in] recursive + * Watch file system recursively? (only relevant if fh points to a directory) + * + * @return + * File watcher + * + * @remarks + * This is a shortcut for creating a FileWatcher and adding file handles to watch. + * You should use this only to watch a single file or directory. + * To watch more than one file at a time, use FileWatcher and add. + * Avoid creating more than one FileWatcher, as OS limits can be reached. + */ + FileWatcher watch(unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); + /** * @brief * Create input stream to read from the file diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index 8fd2049..61043b5 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -30,20 +30,20 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend * Constructor * * @param[in] fileWatcher - * File watcher that owns the backend + * File watcher that owns the backend (must NOT be null!) */ - NullFileWatcher(FileWatcher & fileWatcher); + NullFileWatcher(FileWatcher * fileWatcher); /** * @brief * Constructor * * @param[in] fileWatcher - * File watcher that owns the backend + * File watcher that owns the backend (must NOT be null!) * @param[in] fs * File system that created this watcher */ - NullFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs); + NullFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs); /** * @brief diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index 53bf609..3c22642 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -28,11 +28,11 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend * Constructor * * @param[in] fileWatcher - * File watcher that owns the backend + * File watcher that owns the backend (must NOT be null!) * @param[in] fs * File system that created this watcher */ - LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs); + LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs); /** * @brief diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index 3378b7e..8a15b9c 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -26,11 +26,11 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend * Constructor * * @param[in] fileWatcher - * File watcher that owns the backend + * File watcher that owns the backend (must NOT be null!) * @param[in] fs * File system that created this watcher */ - LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs); + LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs); /** * @brief diff --git a/source/cppfs/source/AbstractFileWatcherBackend.cpp b/source/cppfs/source/AbstractFileWatcherBackend.cpp index 729181a..162f3f3 100644 --- a/source/cppfs/source/AbstractFileWatcherBackend.cpp +++ b/source/cppfs/source/AbstractFileWatcherBackend.cpp @@ -8,7 +8,7 @@ namespace cppfs { -AbstractFileWatcherBackend::AbstractFileWatcherBackend(FileWatcher & fileWatcher) +AbstractFileWatcherBackend::AbstractFileWatcherBackend(FileWatcher * fileWatcher) : m_fileWatcher(fileWatcher) { } @@ -19,7 +19,7 @@ AbstractFileWatcherBackend::~AbstractFileWatcherBackend() void AbstractFileWatcherBackend::onFileEvent(FileHandle & fh, FileEvent event) { - m_fileWatcher.onFileEvent(fh, event); + m_fileWatcher->onFileEvent(fh, event); } diff --git a/source/cppfs/source/FileHandle.cpp b/source/cppfs/source/FileHandle.cpp index 6c569f0..a137d82 100644 --- a/source/cppfs/source/FileHandle.cpp +++ b/source/cppfs/source/FileHandle.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -584,6 +585,16 @@ bool FileHandle::remove() return true; } +FileWatcher FileHandle::watch(unsigned int events, RecursiveMode recursive) +{ + // Create file system watcher + FileWatcher watcher; + watcher.add(*this, events, recursive); + + // Return watcher + return std::move(watcher); +} + std::unique_ptr FileHandle::createInputStream(std::ios_base::openmode mode) const { // Check backend diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index aff42e5..c940145 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -26,7 +26,11 @@ FileWatcher::FileWatcher(AbstractFileSystem * fs) FileWatcher::FileWatcher(FileWatcher && fileWatcher) : m_backend(std::move(fileWatcher.m_backend)) +, m_eventHandlers(std::move(fileWatcher.m_eventHandlers)) +, m_ownEventHandlers(std::move(fileWatcher.m_ownEventHandlers)) { + // Fix pointer to file watcher + m_backend->m_fileWatcher = this; } FileWatcher::~FileWatcher() @@ -35,8 +39,15 @@ FileWatcher::~FileWatcher() FileWatcher & FileWatcher::operator=(FileWatcher && fileWatcher) { - m_backend = std::move(fileWatcher.m_backend); + // Move backend + m_backend = std::move(fileWatcher.m_backend); + m_eventHandlers = std::move(fileWatcher.m_eventHandlers); + m_ownEventHandlers = std::move(fileWatcher.m_ownEventHandlers); + // Fix pointer to file watcher + m_backend->m_fileWatcher = this; + + // Done return *this; } diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp index f33babe..e015433 100644 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -6,12 +6,12 @@ namespace cppfs { -NullFileWatcher::NullFileWatcher(FileWatcher & fileWatcher) +NullFileWatcher::NullFileWatcher(FileWatcher * fileWatcher) : AbstractFileWatcherBackend(fileWatcher) { } -NullFileWatcher::NullFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs) +NullFileWatcher::NullFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs) : AbstractFileWatcherBackend(fileWatcher) , m_fs(fs) { diff --git a/source/cppfs/source/posix/LocalFileSystem.cpp b/source/cppfs/source/posix/LocalFileSystem.cpp index c4aff82..2fff69f 100644 --- a/source/cppfs/source/posix/LocalFileSystem.cpp +++ b/source/cppfs/source/posix/LocalFileSystem.cpp @@ -36,7 +36,7 @@ FileHandle LocalFileSystem::open(std::string && path) std::unique_ptr LocalFileSystem::createFileWatcher(FileWatcher & fileWatcher) { return std::unique_ptr( - new LocalFileWatcher(fileWatcher, shared_from_this()) + new LocalFileWatcher(&fileWatcher, shared_from_this()) ); } diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index 3aab0d1..118a321 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -18,7 +18,7 @@ namespace cppfs { -LocalFileWatcher::LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs) +LocalFileWatcher::LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs) : AbstractFileWatcherBackend(fileWatcher) , m_fs(fs) , m_inotify(-1) diff --git a/source/cppfs/source/ssh/SshFileSystem.cpp b/source/cppfs/source/ssh/SshFileSystem.cpp index cf89cf9..e15bc3a 100644 --- a/source/cppfs/source/ssh/SshFileSystem.cpp +++ b/source/cppfs/source/ssh/SshFileSystem.cpp @@ -74,7 +74,7 @@ FileHandle SshFileSystem::open(std::string && path) std::unique_ptr SshFileSystem::createFileWatcher(FileWatcher & fileWatcher) { return std::unique_ptr( - new NullFileWatcher(fileWatcher, shared_from_this()) + new NullFileWatcher(&fileWatcher, shared_from_this()) ); } diff --git a/source/cppfs/source/windows/LocalFileSystem.cpp b/source/cppfs/source/windows/LocalFileSystem.cpp index 3ed0ab5..1f3ad95 100644 --- a/source/cppfs/source/windows/LocalFileSystem.cpp +++ b/source/cppfs/source/windows/LocalFileSystem.cpp @@ -35,7 +35,7 @@ FileHandle LocalFileSystem::open(std::string && path) std::unique_ptr LocalFileSystem::createFileWatcher(FileWatcher & fileWatcher) { return std::unique_ptr( - new LocalFileWatcher(fileWatcher, shared_from_this()) + new LocalFileWatcher(&fileWatcher, shared_from_this()) ); } diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index 1c11e15..ea208b7 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -8,7 +8,7 @@ namespace cppfs { -LocalFileWatcher::LocalFileWatcher(FileWatcher & fileWatcher, std::shared_ptr fs) +LocalFileWatcher::LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs) : AbstractFileWatcherBackend(fileWatcher) , m_fs(fs) { diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index d1a92f9..c7ee6c0 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -62,15 +62,12 @@ int main(int argc, char * argv[]) std::string path = paramPath.value(); if (path.empty()) path = "."; - // Create file system watcher - FileWatcher watcher; - // Open directory FileHandle dir = fs::open(path, &login); if (dir.isDirectory()) { // Watch directory - watcher.add(dir); + FileWatcher watcher = dir.watch(); // Create file event handler watcher.addHandler([] (FileHandle & fh, FileEvent event) { From 2fad5816b2beb3da6945961cbebfc237ce97447b Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 13:15:30 +0100 Subject: [PATCH 11/27] Fix FileHandle::watch() in case of other file systems than the system FS (not relevant, yet) --- source/cppfs/source/FileHandle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/cppfs/source/FileHandle.cpp b/source/cppfs/source/FileHandle.cpp index a137d82..0581d43 100644 --- a/source/cppfs/source/FileHandle.cpp +++ b/source/cppfs/source/FileHandle.cpp @@ -588,7 +588,7 @@ bool FileHandle::remove() FileWatcher FileHandle::watch(unsigned int events, RecursiveMode recursive) { // Create file system watcher - FileWatcher watcher; + FileWatcher watcher(fs()); watcher.add(*this, events, recursive); // Return watcher From 773cb634a107668dd3ec4fc47689b1637fe28386 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 16:25:46 +0100 Subject: [PATCH 12/27] Remove bit field logic where not necessary --- source/cppfs/source/FileEventHandler.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/source/cppfs/source/FileEventHandler.cpp b/source/cppfs/source/FileEventHandler.cpp index a77d258..9d291ae 100644 --- a/source/cppfs/source/FileEventHandler.cpp +++ b/source/cppfs/source/FileEventHandler.cpp @@ -18,12 +18,21 @@ FileEventHandler::~FileEventHandler() void FileEventHandler::onFileEvent(FileHandle & fh, FileEvent event) { - if (event & FileCreated) { - onFileCreated(fh); - } else if (event & FileRemoved) { - onFileRemoved(fh); - } else if (event & FileModified) { - onFileModified(fh); + switch (event) { + case FileCreated: + onFileCreated(fh); + break; + + case FileRemoved: + onFileRemoved(fh); + break; + + case FileModified: + onFileModified(fh); + break; + + default: + break; } } From 9d2cf4ecba9b7e15996431fb6be0b045a6e58649 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 25 Nov 2018 16:32:53 +0100 Subject: [PATCH 13/27] Fix handling empty name field (happens when the watched object itself is subject of an event) --- source/cppfs/source/posix/LocalFileWatcher.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index 118a321..eabad05 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -107,8 +107,7 @@ void LocalFileWatcher::watch() auto & watcher = m_watchers[event->wd]; // Get file handle - std::string path = event->name; - FileHandle fh = watcher.fileHandle.open(path); + FileHandle fh = (event->len > 0 ? watcher.fileHandle.open(std::string(event->name)) : watcher.fileHandle); // Watch new directories if (fh.isDirectory() && eventType == FileCreated && watcher.recursive == Recursive) { From ca00053a595f090a960a6769141d288833bacc59 Mon Sep 17 00:00:00 2001 From: robiwan Date: Mon, 26 Nov 2018 16:15:18 +0100 Subject: [PATCH 14/27] Adds windows file watcher, updates watch function prototype --- .../cppfs/AbstractFileWatcherBackend.h | 15 +- source/cppfs/include/cppfs/FileWatcher.h | 16 ++- .../include/cppfs/null/NullFileWatcher.h | 2 +- .../include/cppfs/posix/LocalFileWatcher.h | 2 +- .../include/cppfs/windows/LocalFileWatcher.h | 20 ++- source/cppfs/source/FileWatcher.cpp | 4 +- source/cppfs/source/null/NullFileWatcher.cpp | 2 +- .../cppfs/source/posix/LocalFileWatcher.cpp | 20 ++- .../cppfs/source/windows/LocalFileWatcher.cpp | 131 +++++++++++++++++- source/examples/cppfs-watch/main.cpp | 112 +++++++++------ 10 files changed, 260 insertions(+), 64 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index 7cddbde..64ff018 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -65,14 +65,19 @@ class CPPFS_API AbstractFileWatcherBackend /** * @brief - * Start watching files (blocking) + * Start watching files * + * @param[in] timeoutMilliSeconds + * timeout value in milliseconds, if less than zero, timeout is infinite + * * @remarks - * The function is supposed to block until one or more file system - * events have occured. For each event, onFileEvent has to be called. - * After all events have been processed, the function shall return. + * This function shall watch the file system and block until one or more + * events have occured, or if the timeout has been exceeded (timeout >= 0). + * For each event, onFileEvent has to be called with the type of the event and + * a file handle to the file or directory. After all events have been + * processed, the function shall return. */ - virtual void watch() = 0; + virtual void watch(int timeoutMilliSeconds) = 0; protected: diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index 361dc9f..929ff21 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -173,16 +173,20 @@ class CPPFS_API FileWatcher /** * @brief - * Start watching files (blocking) + * Start watching files + * + * @param[in] timeoutMilliSeconds + * timeout value in milliseconds. If less than zero, timeout is infinite, + * i.e. call will be blocking. * * @remarks - * This function begins to watch the file system and blocks until - * one or more events have occured. On every event, onFileEvent() - * is called with the type of the event and a file handle to the - * file or directory. Afterwards, the function returns. + * This function begins to watch the file system and blocks until one or more + * events have occured, or the timeout (if set) has been exceeded. + * On every event, onFileEvent() is called with the type of the event and + * a file handle to the file or directory. Afterwards, the function returns. * To listen to more events, call watch() again. */ - void watch(); + void watch(int timeoutMilliSeconds = -1); protected: diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index 61043b5..2414c85 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -54,7 +54,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; - virtual void watch() override; + virtual void watch(int timeoutMilliSeconds) override; protected: diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index 3c22642..aaf7a7f 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -43,7 +43,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; - virtual void watch() override; + virtual void watch(int timeoutMilliSeconds) override; protected: diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index 8a15b9c..0e1df72 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -3,9 +3,10 @@ #include +#include #include - +#include namespace cppfs { @@ -41,11 +42,24 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; - virtual void watch() override; + virtual void watch(int timeoutMilliSeconds) override; +protected: + /** + * @brief + * Watcher entry + */ + struct Watcher { + std::shared_ptr handle; + FileHandle fileHandle; + unsigned int events; + RecursiveMode recursive; + std::shared_ptr platform; + }; protected: - std::shared_ptr m_fs; ///< File system that created this watcher + std::shared_ptr m_fs; ///< File system that created this watcher + std::vector m_watchers; ///< Watchers }; diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index c940145..84c2bad 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -105,7 +105,7 @@ void FileWatcher::removeHandler(FileEventHandler * eventHandler) } } -void FileWatcher::watch() +void FileWatcher::watch(int timeoutMilliSeconds) { // Check backend if (!m_backend) { @@ -113,7 +113,7 @@ void FileWatcher::watch() } // Watch files - m_backend->watch(); + m_backend->watch(timeoutMilliSeconds); } void FileWatcher::onFileEvent(FileHandle & fh, FileEvent event) diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp index e015433..4c06e0e 100644 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -30,7 +30,7 @@ void NullFileWatcher::add(FileHandle &, unsigned int, RecursiveMode) { } -void NullFileWatcher::watch() +void NullFileWatcher::watch(int timeoutMilliSeconds) { } diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index eabad05..2a8b210 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -50,7 +50,7 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r uint32_t flags = 0; if (events & FileCreated) flags |= IN_CREATE; if (events & FileRemoved) flags |= IN_DELETE; - if (events & FileModified) flags |= IN_MODIFY; + if (events & FileModified) flags |= (IN_MODIFY | IN_ATTRIB); // Create watcher int handle = inotify_add_watch(m_inotify, fh.path().c_str(), flags); @@ -78,13 +78,28 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r m_watchers[handle].recursive = recursive; } -void LocalFileWatcher::watch() +void LocalFileWatcher::watch(int timeoutMilliSeconds) { // Create buffer for receiving events size_t bufSize = 64 * (sizeof(inotify_event) + NAME_MAX); std::vector buffer; buffer.resize(bufSize); + if (timeoutMilliSeconds >= 0) { + fd_set set; + struct timeval timeout; + timeout.tv_sec = timeoutMilliSeconds / 1000; + timeout.tv_usec = (timeoutMilliSeconds - timeout.tv_sec * 1000) * 1000; + + FD_ZERO(&set); /* clear the set */ + FD_SET(m_inotify, &set); /* add our file descriptor to the set */ + + int rv = select(m_inotify + 1, &set, NULL, NULL, &timeout); + if (rv <= 0) { + return; + } + } + // Read events int size = read(m_inotify, buffer.data(), bufSize); if (size < 0) { @@ -102,6 +117,7 @@ void LocalFileWatcher::watch() if (event->mask & IN_CREATE) eventType = FileCreated; else if (event->mask & IN_DELETE) eventType = FileRemoved; else if (event->mask & IN_MODIFY) eventType = FileModified; + else if (event->mask & IN_ATTRIB) eventType = FileModified; // Get watcher auto & watcher = m_watchers[event->wd]; diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index ea208b7..edc97fa 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -3,6 +3,19 @@ #include +#include + +#include + +namespace +{ + struct LocalWatcher + { + OVERLAPPED ovl; + std::shared_ptr event; + DWORD buffer[16384]; + }; +} namespace cppfs { @@ -16,6 +29,7 @@ LocalFileWatcher::LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr(m_fs.get()); } -void LocalFileWatcher::add(FileHandle &, unsigned int, RecursiveMode) +void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode recursive) { - // [TODO] Implement for Windows + HANDLE hDir = ::CreateFileA(fh.path().c_str(), // pointer to the directory name + FILE_LIST_DIRECTORY, // access (read/write) mode + FILE_SHARE_READ // share mode + | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, // security descriptor + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS // file attributes + | FILE_FLAG_OVERLAPPED, + NULL); // file with attributes to copy + if (hDir == INVALID_HANDLE_VALUE) + throw std::runtime_error("failed to create directory listener"); + + std::shared_ptr hDirectory(hDir, ::CloseHandle); + + std::shared_ptr hEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle); + if (!hEvent) + throw std::runtime_error("failed creating wait event"); + + auto lw = std::make_shared(); + lw->event = std::move(hEvent); + lw->ovl.hEvent = lw->event.get(); + + Watcher w; + w.handle = std::move(hDirectory); + w.fileHandle = fh; + w.events = events; + w.recursive = recursive; + w.platform = std::move(lw); + m_watchers.push_back(std::move(w)); } -void LocalFileWatcher::watch() +void LocalFileWatcher::watch(int timeoutMilliSeconds) { - // [TODO] Implement for Windows + std::vector waitHandles; + for (auto it : m_watchers) { + auto lw = reinterpret_cast(it.platform.get()); + waitHandles.push_back(lw->event.get()); + DWORD flags = 0; + if (it.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; + if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; + if (it.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; + + if (!::ReadDirectoryChangesW( + it.handle.get(), + lw->buffer, + sizeof(lw->buffer), + (BOOL)it.recursive, + flags, + NULL, + &lw->ovl, + NULL)) { + auto error = GetLastError(); + if (error != ERROR_IO_PENDING) { + throw std::runtime_error("Error calling ReadDirectoryChangesW: " + std::to_string(error)); + } + } + } + + for (;;) { + auto waitResult = ::WaitForMultipleObjects(waitHandles.size(), waitHandles.data(), FALSE, timeoutMilliSeconds); + if (waitResult == WAIT_TIMEOUT) { + return; + } + + auto index = waitResult - WAIT_OBJECT_0; + if (index < 0 || index >= int(m_watchers.size())) + continue; + + Watcher& w = m_watchers[index]; + auto lw = reinterpret_cast(w.platform.get()); + DWORD dwBytes = 0; + if (::GetOverlappedResult(w.handle.get(), &lw->ovl, &dwBytes, FALSE) && dwBytes > 0) { + char fileName[32768]; + char* p = (char*)lw->buffer; + for (;;) { + FILE_NOTIFY_INFORMATION* fni = reinterpret_cast(p); + int ret = ::WideCharToMultiByte(CP_ACP, + 0, + fni->FileName, + fni->FileNameLength / sizeof(WCHAR), + fileName, + sizeof(fileName), + NULL, + NULL); + if (ret != 0) { + std::string fname(fileName, fileName + ret); + std::replace(fname.begin(), fname.end(), '\\', '/'); + FileEvent eventType = (FileEvent)0; + switch (fni->Action) { + case FILE_ACTION_ADDED: + eventType = FileCreated; + break; + case FILE_ACTION_REMOVED: + eventType = FileRemoved; + break; + case FILE_ACTION_MODIFIED: + eventType = FileModified; + break; + default: + break; + } + if (w.events & eventType) { + // Get file handle + FileHandle fh = w.fileHandle.open(fname); + // Invoke callback function + onFileEvent(fh, eventType); + } + } + if (fni->NextEntryOffset == 0) + break; + p += fni->NextEntryOffset; + } + } + break; + } } diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index c7ee6c0..b57c855 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -1,5 +1,8 @@ +#include +#include #include +#include #include #include @@ -37,6 +40,10 @@ int main(int argc, char * argv[]) CommandLineOption opConfig("--config", "-c", "file", "Load configuration from file", CommandLineOption::Optional); action.add(&opConfig); + CommandLineOption opTime("--time", "-t", "seconds", "Stop watch loop after given time", CommandLineOption::Optional); + action.add(&opTime); + + CommandLineParameter paramPath("path", CommandLineParameter::Optional); action.add(¶mPath); @@ -49,48 +56,75 @@ int main(int argc, char * argv[]) return 0; } - // Get login credentials - LoginCredentials login; + try { + // Get login credentials + LoginCredentials login; - std::string configFile = opConfig.value(); - if (!configFile.empty()) - { - login.load(configFile); - } - - // Get path - std::string path = paramPath.value(); - if (path.empty()) path = "."; + std::string configFile = opConfig.value(); + if (!configFile.empty()) + { + login.load(configFile); + } - // Open directory - FileHandle dir = fs::open(path, &login); - if (dir.isDirectory()) - { - // Watch directory - FileWatcher watcher = dir.watch(); - - // Create file event handler - watcher.addHandler([] (FileHandle & fh, FileEvent event) { - // Get file type - std::string type = (fh.isDirectory() ? "directory" : "file"); - - // Get operation - std::string operation = ( (event & FileCreated) ? "created" : - ( (event & FileRemoved) ? "removed" : - "modified" ) ); - - // Log event - std::cout << "The " << type << " '" << fh.path() << "' was " << operation << "." << std::endl; - }); - - // Begin watching and printing events - while (true) { - watcher.watch(); + // Get path + std::string path = paramPath.value(); + if (path.empty()) path = "."; + + // Open directory + FileHandle dir = fs::open(path, &login); + if (dir.isDirectory()) + { + // Watch directory + FileWatcher watcher = dir.watch(); + + // Create file event handler + watcher.addHandler([] (FileHandle & fh, FileEvent event) { + // Get file type + std::string type = (fh.isDirectory() ? "directory" : "file"); + + // Get operation + std::string operation = ( (event & FileCreated) ? "created" : + ( (event & FileRemoved) ? "removed" : + "modified" ) ); + + // Log event + std::cout << "The " << type << " '" << fh.path() << "' was " << operation << "." << std::endl; + }); + + // Begin watching and printing events + std::string t = opTime.value(); + if (t.empty()) { + while (true) { + watcher.watch(); + } + } + else { + auto t_sleep = std::stoi(t); + std::cout << "Will stop watching after " << t_sleep << " seconds..." << std::endl; + + // Execute watch loop in a separate thread + std::atomic stop_thread{false}; + std::thread t([&] { + while (!stop_thread) { + watcher.watch(50); // Timeout 50 ms, so we can poll stop_thread variable + } + }); + + // Zzzzz.... + std::this_thread::sleep_for(std::chrono::seconds(t_sleep)); + + // Join watcher thread + stop_thread = true; + t.join(); + } } - } - else - { - std::cout << "'" << path << "' is not a valid directory." << std::endl; + else + { + std::cout << "'" << path << "' is not a valid directory." << std::endl; + } + } catch (std::exception& e) { + std::cerr << "Exception: " << e.what() << std::endl; + return -1; } // Done From 022be82c8afad8c61fa178acd4e1b341d78b2924 Mon Sep 17 00:00:00 2001 From: robiwan Date: Tue, 27 Nov 2018 10:27:29 +0100 Subject: [PATCH 15/27] Windows add of folder to watch is now thread safe --- .../include/cppfs/windows/LocalFileWatcher.h | 7 +- .../cppfs/source/windows/LocalFileWatcher.cpp | 142 +++++++++++------- source/examples/cppfs-watch/main.cpp | 14 +- 3 files changed, 100 insertions(+), 63 deletions(-) diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index 0e1df72..acf7da2 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -3,6 +3,7 @@ #include +#include #include #include @@ -58,8 +59,10 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend }; protected: - std::shared_ptr m_fs; ///< File system that created this watcher - std::vector m_watchers; ///< Watchers + std::shared_ptr m_fs; ///< File system that created this watcher + std::shared_ptr m_waitStopEvent; ///< Event to stop watch function + std::shared_ptr m_watchersCS; ///< Watchers critical section + std::vector m_watchers; ///< Watchers }; diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index edc97fa..cd05e96 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -15,6 +15,19 @@ namespace std::shared_ptr event; DWORD buffer[16384]; }; + + // NB! Using a std::mutex resulted in spurious deadlocks using VS2017, whilst + // using CRITICAL_SECTION works fine. + struct ScopedCriticalSection + { + LPCRITICAL_SECTION m_pcs; + ScopedCriticalSection(LPCRITICAL_SECTION pcs) : m_pcs(pcs) { + ::EnterCriticalSection(m_pcs); + } + ~ScopedCriticalSection() { + ::LeaveCriticalSection(m_pcs); + } + }; } namespace cppfs @@ -24,11 +37,15 @@ namespace cppfs LocalFileWatcher::LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs) : AbstractFileWatcherBackend(fileWatcher) , m_fs(fs) +, m_waitStopEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle) +, m_watchersCS(new CRITICAL_SECTION()) { + ::InitializeCriticalSection((LPCRITICAL_SECTION)m_watchersCS.get()); } LocalFileWatcher::~LocalFileWatcher() { + ::DeleteCriticalSection((LPCRITICAL_SECTION)m_watchersCS.get()); m_watchers.clear(); } @@ -52,8 +69,7 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r throw std::runtime_error("failed to create directory listener"); std::shared_ptr hDirectory(hDir, ::CloseHandle); - - std::shared_ptr hEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle); + std::shared_ptr hEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle); if (!hEvent) throw std::runtime_error("failed creating wait event"); @@ -67,12 +83,17 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r w.events = events; w.recursive = recursive; w.platform = std::move(lw); + + ::SetEvent(m_waitStopEvent.get()); + ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get()); m_watchers.push_back(std::move(w)); } void LocalFileWatcher::watch(int timeoutMilliSeconds) { + ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get()); std::vector waitHandles; + waitHandles.push_back(m_waitStopEvent.get()); for (auto it : m_watchers) { auto lw = reinterpret_cast(it.platform.get()); waitHandles.push_back(lw->event.get()); @@ -81,6 +102,13 @@ void LocalFileWatcher::watch(int timeoutMilliSeconds) if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; if (it.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; + DWORD dwBytes = 0; + if (!::GetOverlappedResult(it.handle.get(), &lw->ovl, &dwBytes, FALSE)) { + auto error = GetLastError(); + if (error == ERROR_IO_INCOMPLETE) + continue; + } + if (!::ReadDirectoryChangesW( it.handle.get(), lw->buffer, @@ -97,64 +125,70 @@ void LocalFileWatcher::watch(int timeoutMilliSeconds) } } - for (;;) { - auto waitResult = ::WaitForMultipleObjects(waitHandles.size(), waitHandles.data(), FALSE, timeoutMilliSeconds); - if (waitResult == WAIT_TIMEOUT) { - return; - } + auto waitResult = ::WaitForMultipleObjects( + waitHandles.size(), + waitHandles.data(), + FALSE, + timeoutMilliSeconds >= 0 ? timeoutMilliSeconds : INFINITE); + if (waitResult == WAIT_TIMEOUT) { + return; + } + if (waitResult == WAIT_OBJECT_0) { + ::ResetEvent(m_waitStopEvent.get()); + return; + } - auto index = waitResult - WAIT_OBJECT_0; - if (index < 0 || index >= int(m_watchers.size())) - continue; + auto index = waitResult - (WAIT_OBJECT_0 + 1); + if (index < 0 || index >= int(m_watchers.size())) { + throw std::runtime_error("Wait returned result: " + std::to_string(waitResult)); + } - Watcher& w = m_watchers[index]; - auto lw = reinterpret_cast(w.platform.get()); - DWORD dwBytes = 0; - if (::GetOverlappedResult(w.handle.get(), &lw->ovl, &dwBytes, FALSE) && dwBytes > 0) { - char fileName[32768]; - char* p = (char*)lw->buffer; - for (;;) { - FILE_NOTIFY_INFORMATION* fni = reinterpret_cast(p); - int ret = ::WideCharToMultiByte(CP_ACP, - 0, - fni->FileName, - fni->FileNameLength / sizeof(WCHAR), - fileName, - sizeof(fileName), - NULL, - NULL); - if (ret != 0) { - std::string fname(fileName, fileName + ret); - std::replace(fname.begin(), fname.end(), '\\', '/'); - FileEvent eventType = (FileEvent)0; - switch (fni->Action) { - case FILE_ACTION_ADDED: - eventType = FileCreated; - break; - case FILE_ACTION_REMOVED: - eventType = FileRemoved; - break; - case FILE_ACTION_MODIFIED: - eventType = FileModified; - break; - default: - break; - } - if (w.events & eventType) { - // Get file handle - FileHandle fh = w.fileHandle.open(fname); - // Invoke callback function - onFileEvent(fh, eventType); - } - } - if (fni->NextEntryOffset == 0) + Watcher& w = m_watchers[index]; + auto lw = reinterpret_cast(w.platform.get()); + ::ResetEvent(lw->event.get()); + DWORD dwBytes = 0; + if (::GetOverlappedResult(w.handle.get(), &lw->ovl, &dwBytes, FALSE) && dwBytes > 0) { + char fileName[32768]; + char* p = (char*)lw->buffer; + for (;;) { + FILE_NOTIFY_INFORMATION* fni = reinterpret_cast(p); + int ret = ::WideCharToMultiByte(CP_ACP, + 0, + fni->FileName, + fni->FileNameLength / sizeof(WCHAR), + fileName, + sizeof(fileName), + NULL, + NULL); + if (ret != 0) { + std::string fname(fileName, fileName + ret); + std::replace(fname.begin(), fname.end(), '\\', '/'); + FileEvent eventType = (FileEvent)0; + switch (fni->Action) { + case FILE_ACTION_ADDED: + eventType = FileCreated; + break; + case FILE_ACTION_REMOVED: + eventType = FileRemoved; break; - p += fni->NextEntryOffset; + case FILE_ACTION_MODIFIED: + eventType = FileModified; + break; + default: + break; + } + if (w.events & eventType) { + // Get file handle + FileHandle fh = w.fileHandle.open(fname); + // Invoke callback function + onFileEvent(fh, eventType); + } } + if (fni->NextEntryOffset == 0) + break; + p += fni->NextEntryOffset; } - break; } } - } // namespace cppfs diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index b57c855..2f53809 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -4,18 +4,18 @@ #include #include -#include #include -#include #include #include +#include +#include -#include -#include -#include #include #include #include +#include +#include +#include using namespace cppassist; @@ -104,7 +104,7 @@ int main(int argc, char * argv[]) // Execute watch loop in a separate thread std::atomic stop_thread{false}; - std::thread t([&] { + std::thread thrd([&] { while (!stop_thread) { watcher.watch(50); // Timeout 50 ms, so we can poll stop_thread variable } @@ -115,7 +115,7 @@ int main(int argc, char * argv[]) // Join watcher thread stop_thread = true; - t.join(); + thrd.join(); } } else From e363bb0f23123d4b777fc33295d62aa23ed7ba7c Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Mon, 3 Dec 2018 19:58:43 +0100 Subject: [PATCH 16/27] Small style related changes --- .../cppfs/AbstractFileWatcherBackend.h | 8 ++++---- source/cppfs/include/cppfs/FileWatcher.h | 7 +++---- .../include/cppfs/null/NullFileWatcher.h | 2 +- .../include/cppfs/posix/LocalFileWatcher.h | 2 +- .../include/cppfs/windows/LocalFileWatcher.h | 3 ++- source/cppfs/source/FileWatcher.cpp | 4 ++-- source/cppfs/source/null/NullFileWatcher.cpp | 2 +- .../cppfs/source/posix/LocalFileWatcher.cpp | 20 +++++++++++-------- .../cppfs/source/windows/LocalFileWatcher.cpp | 10 ++++++---- 9 files changed, 32 insertions(+), 26 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index 64ff018..be52a35 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -67,9 +67,9 @@ class CPPFS_API AbstractFileWatcherBackend * @brief * Start watching files * - * @param[in] timeoutMilliSeconds - * timeout value in milliseconds, if less than zero, timeout is infinite - * + * @param[in] timeout + * Timeout value in milliseconds. If less than zero, timeout is infinite and the function blocks. + * * @remarks * This function shall watch the file system and block until one or more * events have occured, or if the timeout has been exceeded (timeout >= 0). @@ -77,7 +77,7 @@ class CPPFS_API AbstractFileWatcherBackend * a file handle to the file or directory. After all events have been * processed, the function shall return. */ - virtual void watch(int timeoutMilliSeconds) = 0; + virtual void watch(int timeout) = 0; protected: diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index 929ff21..08a1884 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -175,13 +175,12 @@ class CPPFS_API FileWatcher * @brief * Start watching files * - * @param[in] timeoutMilliSeconds - * timeout value in milliseconds. If less than zero, timeout is infinite, - * i.e. call will be blocking. + * @param[in] timeout + * Timeout value in milliseconds. If less than zero, timeout is infinite and the function blocks. * * @remarks * This function begins to watch the file system and blocks until one or more - * events have occured, or the timeout (if set) has been exceeded. + * events have occured, or the timeout (if set) has been exceeded (timeout >= 0). * On every event, onFileEvent() is called with the type of the event and * a file handle to the file or directory. Afterwards, the function returns. * To listen to more events, call watch() again. diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index 2414c85..a198b84 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -54,7 +54,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; - virtual void watch(int timeoutMilliSeconds) override; + virtual void watch(int timeout) override; protected: diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index aaf7a7f..cad1f76 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -43,7 +43,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; - virtual void watch(int timeoutMilliSeconds) override; + virtual void watch(int timeout) override; protected: diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index acf7da2..05079d5 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -43,7 +43,8 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; - virtual void watch(int timeoutMilliSeconds) override; + virtual void watch(int timeout) override; + protected: /** diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index 84c2bad..909057f 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -105,7 +105,7 @@ void FileWatcher::removeHandler(FileEventHandler * eventHandler) } } -void FileWatcher::watch(int timeoutMilliSeconds) +void FileWatcher::watch(int timeout) { // Check backend if (!m_backend) { @@ -113,7 +113,7 @@ void FileWatcher::watch(int timeoutMilliSeconds) } // Watch files - m_backend->watch(timeoutMilliSeconds); + m_backend->watch(timeout); } void FileWatcher::onFileEvent(FileHandle & fh, FileEvent event) diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp index 4c06e0e..9921fb2 100644 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ b/source/cppfs/source/null/NullFileWatcher.cpp @@ -30,7 +30,7 @@ void NullFileWatcher::add(FileHandle &, unsigned int, RecursiveMode) { } -void NullFileWatcher::watch(int timeoutMilliSeconds) +void NullFileWatcher::watch(int) { } diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index 2a8b210..cc0295a 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -78,23 +78,27 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r m_watchers[handle].recursive = recursive; } -void LocalFileWatcher::watch(int timeoutMilliSeconds) +void LocalFileWatcher::watch(int timeout) { // Create buffer for receiving events size_t bufSize = 64 * (sizeof(inotify_event) + NAME_MAX); std::vector buffer; buffer.resize(bufSize); - if (timeoutMilliSeconds >= 0) { + // Set timeout + if (timeout >= 0) { + // Create file descriptor set fd_set set; - struct timeval timeout; - timeout.tv_sec = timeoutMilliSeconds / 1000; - timeout.tv_usec = (timeoutMilliSeconds - timeout.tv_sec * 1000) * 1000; + FD_ZERO(&set); + FD_SET(m_inotify, &set); - FD_ZERO(&set); /* clear the set */ - FD_SET(m_inotify, &set); /* add our file descriptor to the set */ + // Convert timeout into timeval struct + struct timeval tval; + tval.tv_sec = timeout / 1000; + tval.tv_usec = (timeout - tval.tv_sec * 1000) * 1000; - int rv = select(m_inotify + 1, &set, NULL, NULL, &timeout); + // Set timeout on file descriptor + int rv = select(m_inotify + 1, &set, NULL, NULL, &tval); if (rv <= 0) { return; } diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index cd05e96..169c233 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -1,11 +1,12 @@ #include -#include +#include #include -#include +#include + namespace { @@ -30,6 +31,7 @@ namespace }; } + namespace cppfs { @@ -89,7 +91,7 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r m_watchers.push_back(std::move(w)); } -void LocalFileWatcher::watch(int timeoutMilliSeconds) +void LocalFileWatcher::watch(int timeout) { ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get()); std::vector waitHandles; @@ -129,7 +131,7 @@ void LocalFileWatcher::watch(int timeoutMilliSeconds) waitHandles.size(), waitHandles.data(), FALSE, - timeoutMilliSeconds >= 0 ? timeoutMilliSeconds : INFINITE); + timeout >= 0 ? timeout : INFINITE); if (waitResult == WAIT_TIMEOUT) { return; } From f8b16fd3392cdc13ba56734995b121e170a3f5ac Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 12:44:18 +0100 Subject: [PATCH 17/27] cppfs-watch: As file watchers are only available for the local file system, remove configuration option for remote login --- source/examples/cppfs-watch/main.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index 2f53809..e1d814e 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -40,9 +40,8 @@ int main(int argc, char * argv[]) CommandLineOption opConfig("--config", "-c", "file", "Load configuration from file", CommandLineOption::Optional); action.add(&opConfig); - CommandLineOption opTime("--time", "-t", "seconds", "Stop watch loop after given time", CommandLineOption::Optional); + CommandLineOption opTime("--timeout", "-t", "seconds", "Stop watch loop after given time", CommandLineOption::Optional); action.add(&opTime); - CommandLineParameter paramPath("path", CommandLineParameter::Optional); action.add(¶mPath); @@ -57,21 +56,12 @@ int main(int argc, char * argv[]) } try { - // Get login credentials - LoginCredentials login; - - std::string configFile = opConfig.value(); - if (!configFile.empty()) - { - login.load(configFile); - } - // Get path std::string path = paramPath.value(); if (path.empty()) path = "."; // Open directory - FileHandle dir = fs::open(path, &login); + FileHandle dir = fs::open(path); if (dir.isDirectory()) { // Watch directory From f3c27ff1300459ea9f426eda7b3b861c14ac814f Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 13:03:10 +0100 Subject: [PATCH 18/27] cppfs-watch: use single threading in example --- source/examples/cppfs-watch/main.cpp | 42 +++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index e1d814e..773ca2e 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -1,8 +1,6 @@ -#include #include #include -#include #include #include @@ -40,7 +38,7 @@ int main(int argc, char * argv[]) CommandLineOption opConfig("--config", "-c", "file", "Load configuration from file", CommandLineOption::Optional); action.add(&opConfig); - CommandLineOption opTime("--timeout", "-t", "seconds", "Stop watch loop after given time", CommandLineOption::Optional); + CommandLineOption opTime("--timeout", "-t", "seconds", "Timeout after which to stop (in seconds)", CommandLineOption::Optional); action.add(&opTime); CommandLineParameter paramPath("path", CommandLineParameter::Optional); @@ -55,6 +53,7 @@ int main(int argc, char * argv[]) return 0; } + // Execute file watching try { // Get path std::string path = paramPath.value(); @@ -84,35 +83,38 @@ int main(int argc, char * argv[]) // Begin watching and printing events std::string t = opTime.value(); if (t.empty()) { + // No timeout given while (true) { watcher.watch(); } } else { - auto t_sleep = std::stoi(t); - std::cout << "Will stop watching after " << t_sleep << " seconds..." << std::endl; - - // Execute watch loop in a separate thread - std::atomic stop_thread{false}; - std::thread thrd([&] { - while (!stop_thread) { - watcher.watch(50); // Timeout 50 ms, so we can poll stop_thread variable - } - }); - - // Zzzzz.... - std::this_thread::sleep_for(std::chrono::seconds(t_sleep)); - - // Join watcher thread - stop_thread = true; - thrd.join(); + // Get timeout + auto timeout = std::stoi(t); + std::cout << "Will stop watching after " << timeout << " seconds..." << std::endl; + + // Get current time + auto start = std::chrono::system_clock::now(); + auto now = std::chrono::system_clock::now(); + + // Execute watch loop + while (std::chrono::duration_cast(std::chrono::system_clock::now() - start).count() < timeout) + { + // Timeout 500ms to revive the main loop + watcher.watch(500); + + // Check elapsed time + now = std::chrono::system_clock::now(); + } } } else { + // Invalid path specified std::cout << "'" << path << "' is not a valid directory." << std::endl; } } catch (std::exception& e) { + // Error during execution std::cerr << "Exception: " << e.what() << std::endl; return -1; } From d8cf3ce91928a41c32ca5d2c38a64fb9b5d91348 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 13:18:26 +0100 Subject: [PATCH 19/27] Clarify in interface and documentation that only directories can be watched --- .../include/cppfs/AbstractFileWatcherBackend.h | 10 +++++----- source/cppfs/include/cppfs/FileWatcher.h | 14 +++++++------- .../cppfs/include/cppfs/null/NullFileWatcher.h | 2 +- .../include/cppfs/posix/LocalFileWatcher.h | 4 ++-- .../include/cppfs/windows/LocalFileWatcher.h | 4 ++-- source/cppfs/source/FileWatcher.cpp | 10 +++++----- source/cppfs/source/posix/LocalFileWatcher.cpp | 18 +++++++++--------- .../cppfs/source/windows/LocalFileWatcher.cpp | 15 ++++++++------- 8 files changed, 39 insertions(+), 38 deletions(-) diff --git a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h index be52a35..17cf606 100644 --- a/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h +++ b/source/cppfs/include/cppfs/AbstractFileWatcherBackend.h @@ -52,16 +52,16 @@ class CPPFS_API AbstractFileWatcherBackend /** * @brief - * Watch file handle + * Watch directory * - * @param[in] fh - * File handle + * @param[in] dir + * Handle to directory that shall be watched * @param[in] events * Events that are watched (combination of FileEvent values) * @param[in] recursive - * Watch file system recursively? (only relevant if fileHandle points to a directory) + * Watch file system recursively? */ - virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) = 0; + virtual void add(FileHandle & dir, unsigned int events, RecursiveMode recursive) = 0; /** * @brief diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index 08a1884..0aae81f 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -120,14 +120,14 @@ class CPPFS_API FileWatcher /** * @brief - * Watch file handle + * Watch directory * - * @param[in] fh - * File handle + * @param[in] dir + * Handle to directory that shall be watched * @param[in] events * Events that are watched (combination of FileEvent values) * @param[in] recursive - * Watch file system recursively? (only relevant if fh points to a directory) + * Watch file system recursively? * * @remarks * The file handle must belong to the same file system as the @@ -136,7 +136,7 @@ class CPPFS_API FileWatcher * single file system. Also note that file watching is not * supported for remote file systems, such as SSH. */ - void add(FileHandle & fh, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); + void add(FileHandle & dir, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); /** * @brief @@ -180,12 +180,12 @@ class CPPFS_API FileWatcher * * @remarks * This function begins to watch the file system and blocks until one or more - * events have occured, or the timeout (if set) has been exceeded (timeout >= 0). + * events have occured, or the timeout has been exceeded (if timeout >= 0). * On every event, onFileEvent() is called with the type of the event and * a file handle to the file or directory. Afterwards, the function returns. * To listen to more events, call watch() again. */ - void watch(int timeoutMilliSeconds = -1); + void watch(int timeout = -1); protected: diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h index a198b84..abb6fe3 100644 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ b/source/cppfs/include/cppfs/null/NullFileWatcher.h @@ -53,7 +53,7 @@ class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & dir, unsigned int events, RecursiveMode recursive) override; virtual void watch(int timeout) override; diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h index cad1f76..f01daf8 100644 --- a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/posix/LocalFileWatcher.h @@ -42,7 +42,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & dir, unsigned int events, RecursiveMode recursive) override; virtual void watch(int timeout) override; @@ -52,7 +52,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend * Watcher entry */ struct Watcher { - FileHandle fileHandle; + FileHandle dir; unsigned int events; RecursiveMode recursive; }; diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index 05079d5..eae5f12 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -42,7 +42,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend // Virtual AbstractFileWatcherBackend functions virtual AbstractFileSystem * fs() const override; - virtual void add(FileHandle & fh, unsigned int events, RecursiveMode recursive) override; + virtual void add(FileHandle & dir, unsigned int events, RecursiveMode recursive) override; virtual void watch(int timeout) override; @@ -53,7 +53,7 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend */ struct Watcher { std::shared_ptr handle; - FileHandle fileHandle; + FileHandle dir; unsigned int events; RecursiveMode recursive; std::shared_ptr platform; diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index 909057f..84e3945 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -56,20 +56,20 @@ AbstractFileSystem * FileWatcher::fs() const return m_backend ? m_backend->fs() : nullptr; } -void FileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode recursive) +void FileWatcher::add(FileHandle & dir, unsigned int events, RecursiveMode recursive) { // Check backend if (!m_backend) { return; } - // Check that file handle belongs to the same file system as the watcher - if (fh.fs() != fs()) { + // Check that file handle is a directory and belongs to the same file system as the watcher + if (!dir.isDirectory() || dir.fs() != fs()) { return; } - // Add file to watcher - m_backend->add(fh, events, recursive); + // Add directory to watcher + m_backend->add(dir, events, recursive); } void FileWatcher::addHandler(FileEventHandler * eventHandler) diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/posix/LocalFileWatcher.cpp index cc0295a..bba6975 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/posix/LocalFileWatcher.cpp @@ -44,7 +44,7 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode recursive) +void LocalFileWatcher::add(FileHandle & dir, unsigned int events, RecursiveMode recursive) { // Get watch mode uint32_t flags = 0; @@ -53,18 +53,18 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r if (events & FileModified) flags |= (IN_MODIFY | IN_ATTRIB); // Create watcher - int handle = inotify_add_watch(m_inotify, fh.path().c_str(), flags); + int handle = inotify_add_watch(m_inotify, dir.path().c_str(), flags); if (handle < 0) { return; } // Watch directories recursively - if (fh.isDirectory() && recursive == Recursive) { + if (recursive == Recursive) { // List directory entries - for (auto it = fh.begin(); it != fh.end(); ++it) + for (auto it = dir.begin(); it != dir.end(); ++it) { // Check if entry is a directory - FileHandle fh2 = fh.open(*it); + FileHandle fh2 = dir.open(*it); if (fh2.isDirectory()) { // Watch directory add(fh2, events, recursive); @@ -73,9 +73,9 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r } // Associate watcher handle with file handle - m_watchers[handle].fileHandle = fh; - m_watchers[handle].events = events; - m_watchers[handle].recursive = recursive; + m_watchers[handle].dir = dir; + m_watchers[handle].events = events; + m_watchers[handle].recursive = recursive; } void LocalFileWatcher::watch(int timeout) @@ -127,7 +127,7 @@ void LocalFileWatcher::watch(int timeout) auto & watcher = m_watchers[event->wd]; // Get file handle - FileHandle fh = (event->len > 0 ? watcher.fileHandle.open(std::string(event->name)) : watcher.fileHandle); + FileHandle fh = (event->len > 0 ? watcher.dir.open(std::string(event->name)) : watcher.dir); // Watch new directories if (fh.isDirectory() && eventType == FileCreated && watcher.recursive == Recursive) { diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index 169c233..c4a0f7c 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -56,9 +56,9 @@ AbstractFileSystem * LocalFileWatcher::fs() const return static_cast(m_fs.get()); } -void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode recursive) +void LocalFileWatcher::add(FileHandle & dir, unsigned int events, RecursiveMode recursive) { - HANDLE hDir = ::CreateFileA(fh.path().c_str(), // pointer to the directory name + HANDLE hDir = ::CreateFileA(dir.path().c_str(), // pointer to the directory name FILE_LIST_DIRECTORY, // access (read/write) mode FILE_SHARE_READ // share mode | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -80,11 +80,11 @@ void LocalFileWatcher::add(FileHandle & fh, unsigned int events, RecursiveMode r lw->ovl.hEvent = lw->event.get(); Watcher w; - w.handle = std::move(hDirectory); - w.fileHandle = fh; - w.events = events; + w.handle = std::move(hDirectory); + w.dir = dir; + w.events = events; w.recursive = recursive; - w.platform = std::move(lw); + w.platform = std::move(lw); ::SetEvent(m_waitStopEvent.get()); ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get()); @@ -181,7 +181,8 @@ void LocalFileWatcher::watch(int timeout) } if (w.events & eventType) { // Get file handle - FileHandle fh = w.fileHandle.open(fname); + FileHandle fh = w.dir.open(fname); + // Invoke callback function onFileEvent(fh, eventType); } From 60c8fb12e43a8fe5152b2da0f39816339262bbf7 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 14:30:03 +0100 Subject: [PATCH 20/27] Remove null implementation of FileWatcher, check for backend nullptr instead --- source/cppfs/CMakeLists.txt | 4 -- .../include/cppfs/null/NullFileWatcher.h | 65 ------------------- source/cppfs/source/FileWatcher.cpp | 9 ++- source/cppfs/source/null/NullFileWatcher.cpp | 38 ----------- source/cppfs/source/ssh/SshFileSystem.cpp | 6 +- 5 files changed, 8 insertions(+), 114 deletions(-) delete mode 100644 source/cppfs/include/cppfs/null/NullFileWatcher.h delete mode 100644 source/cppfs/source/null/NullFileWatcher.cpp diff --git a/source/cppfs/CMakeLists.txt b/source/cppfs/CMakeLists.txt index 522b0ae..5e9ae2c 100644 --- a/source/cppfs/CMakeLists.txt +++ b/source/cppfs/CMakeLists.txt @@ -73,8 +73,6 @@ set(headers ${include_path}/Change.h ${include_path}/units.h - ${include_path}/null/NullFileWatcher.h - ${include_path}/${localfs}/LocalFileSystem.h ${include_path}/${localfs}/LocalFileHandle.h ${include_path}/${localfs}/LocalFileIterator.h @@ -104,8 +102,6 @@ set(sources ${source_path}/Diff.cpp ${source_path}/Change.cpp - ${source_path}/null/NullFileWatcher.cpp - ${source_path}/${localfs}/LocalFileSystem.cpp ${source_path}/${localfs}/LocalFileHandle.cpp ${source_path}/${localfs}/LocalFileIterator.cpp diff --git a/source/cppfs/include/cppfs/null/NullFileWatcher.h b/source/cppfs/include/cppfs/null/NullFileWatcher.h deleted file mode 100644 index abb6fe3..0000000 --- a/source/cppfs/include/cppfs/null/NullFileWatcher.h +++ /dev/null @@ -1,65 +0,0 @@ - -#pragma once - - -#include - -#include - - -namespace cppfs -{ - - -class AbstractFileSystem; - - -/** -* @brief -* Empty implementation of the file watcher interface -* -* @remarks -* This class is used for file systems that do not support -* file system watchers, such as remote file systems. -*/ -class CPPFS_API NullFileWatcher : public AbstractFileWatcherBackend -{ -public: - /** - * @brief - * Constructor - * - * @param[in] fileWatcher - * File watcher that owns the backend (must NOT be null!) - */ - NullFileWatcher(FileWatcher * fileWatcher); - - /** - * @brief - * Constructor - * - * @param[in] fileWatcher - * File watcher that owns the backend (must NOT be null!) - * @param[in] fs - * File system that created this watcher - */ - NullFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs); - - /** - * @brief - * Destructor - */ - virtual ~NullFileWatcher(); - - // Virtual AbstractFileWatcherBackend functions - virtual AbstractFileSystem * fs() const override; - virtual void add(FileHandle & dir, unsigned int events, RecursiveMode recursive) override; - virtual void watch(int timeout) override; - - -protected: - std::shared_ptr m_fs; ///< File system that created this watcher -}; - - -} // namespace cppfs diff --git a/source/cppfs/source/FileWatcher.cpp b/source/cppfs/source/FileWatcher.cpp index 84e3945..bdeaeca 100644 --- a/source/cppfs/source/FileWatcher.cpp +++ b/source/cppfs/source/FileWatcher.cpp @@ -7,7 +7,6 @@ #include #include #include -#include namespace cppfs @@ -30,7 +29,9 @@ FileWatcher::FileWatcher(FileWatcher && fileWatcher) , m_ownEventHandlers(std::move(fileWatcher.m_ownEventHandlers)) { // Fix pointer to file watcher - m_backend->m_fileWatcher = this; + if (m_backend) { + m_backend->m_fileWatcher = this; + } } FileWatcher::~FileWatcher() @@ -45,7 +46,9 @@ FileWatcher & FileWatcher::operator=(FileWatcher && fileWatcher) m_ownEventHandlers = std::move(fileWatcher.m_ownEventHandlers); // Fix pointer to file watcher - m_backend->m_fileWatcher = this; + if (m_backend) { + m_backend->m_fileWatcher = this; + } // Done return *this; diff --git a/source/cppfs/source/null/NullFileWatcher.cpp b/source/cppfs/source/null/NullFileWatcher.cpp deleted file mode 100644 index 9921fb2..0000000 --- a/source/cppfs/source/null/NullFileWatcher.cpp +++ /dev/null @@ -1,38 +0,0 @@ - -#include - - -namespace cppfs -{ - - -NullFileWatcher::NullFileWatcher(FileWatcher * fileWatcher) -: AbstractFileWatcherBackend(fileWatcher) -{ -} - -NullFileWatcher::NullFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs) -: AbstractFileWatcherBackend(fileWatcher) -, m_fs(fs) -{ -} - -NullFileWatcher::~NullFileWatcher() -{ -} - -AbstractFileSystem * NullFileWatcher::fs() const -{ - return m_fs.get(); -} - -void NullFileWatcher::add(FileHandle &, unsigned int, RecursiveMode) -{ -} - -void NullFileWatcher::watch(int) -{ -} - - -} // namespace cppfs diff --git a/source/cppfs/source/ssh/SshFileSystem.cpp b/source/cppfs/source/ssh/SshFileSystem.cpp index e15bc3a..6c09b3d 100644 --- a/source/cppfs/source/ssh/SshFileSystem.cpp +++ b/source/cppfs/source/ssh/SshFileSystem.cpp @@ -16,8 +16,8 @@ #include #include +#include #include -#include namespace cppfs @@ -73,9 +73,7 @@ FileHandle SshFileSystem::open(std::string && path) std::unique_ptr SshFileSystem::createFileWatcher(FileWatcher & fileWatcher) { - return std::unique_ptr( - new NullFileWatcher(&fileWatcher, shared_from_this()) - ); + return nullptr; } void SshFileSystem::connect() From b45312a7c8e73ed361466775e3a2dca3d83005a7 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 14:40:15 +0100 Subject: [PATCH 21/27] Move LocalFileWatcher to linux directory, as it is specific for Linux --- source/cppfs/CMakeLists.txt | 14 +++++++++++--- .../cppfs/{posix => linux}/LocalFileWatcher.h | 0 .../source/{posix => linux}/LocalFileWatcher.cpp | 2 +- source/cppfs/source/posix/LocalFileSystem.cpp | 10 +++++++++- 4 files changed, 21 insertions(+), 5 deletions(-) rename source/cppfs/include/cppfs/{posix => linux}/LocalFileWatcher.h (100%) rename source/cppfs/source/{posix => linux}/LocalFileWatcher.cpp (98%) diff --git a/source/cppfs/CMakeLists.txt b/source/cppfs/CMakeLists.txt index 5e9ae2c..acc089e 100644 --- a/source/cppfs/CMakeLists.txt +++ b/source/cppfs/CMakeLists.txt @@ -76,7 +76,6 @@ set(headers ${include_path}/${localfs}/LocalFileSystem.h ${include_path}/${localfs}/LocalFileHandle.h ${include_path}/${localfs}/LocalFileIterator.h - ${include_path}/${localfs}/LocalFileWatcher.h ) set(sources @@ -105,9 +104,18 @@ set(sources ${source_path}/${localfs}/LocalFileSystem.cpp ${source_path}/${localfs}/LocalFileHandle.cpp ${source_path}/${localfs}/LocalFileIterator.cpp - ${source_path}/${localfs}/LocalFileWatcher.cpp ) +if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + set(headers ${headers} + ${include_path}/linux/LocalFileWatcher.h + ) + + set(sources ${sources} + ${source_path}/linux/LocalFileWatcher.cpp + ) +endif() + if (OPTION_BUILD_SSH_BACKEND) set(headers ${headers} ${include_path}/ssh/SshFileSystem.h @@ -124,7 +132,7 @@ if (OPTION_BUILD_SSH_BACKEND) ${source_path}/ssh/SshInputStreamBuffer.cpp ${source_path}/ssh/SshOutputStreamBuffer.cpp ) -endif () +endif() # Group source files set(header_group "Header Files (API)") diff --git a/source/cppfs/include/cppfs/posix/LocalFileWatcher.h b/source/cppfs/include/cppfs/linux/LocalFileWatcher.h similarity index 100% rename from source/cppfs/include/cppfs/posix/LocalFileWatcher.h rename to source/cppfs/include/cppfs/linux/LocalFileWatcher.h diff --git a/source/cppfs/source/posix/LocalFileWatcher.cpp b/source/cppfs/source/linux/LocalFileWatcher.cpp similarity index 98% rename from source/cppfs/source/posix/LocalFileWatcher.cpp rename to source/cppfs/source/linux/LocalFileWatcher.cpp index bba6975..38308a4 100644 --- a/source/cppfs/source/posix/LocalFileWatcher.cpp +++ b/source/cppfs/source/linux/LocalFileWatcher.cpp @@ -1,5 +1,5 @@ -#include +#include #include #include diff --git a/source/cppfs/source/posix/LocalFileSystem.cpp b/source/cppfs/source/posix/LocalFileSystem.cpp index 2fff69f..e9755ca 100644 --- a/source/cppfs/source/posix/LocalFileSystem.cpp +++ b/source/cppfs/source/posix/LocalFileSystem.cpp @@ -3,8 +3,12 @@ #include #include +#include #include -#include + +#ifdef SYSTEM_LINUX + #include +#endif namespace cppfs @@ -35,9 +39,13 @@ FileHandle LocalFileSystem::open(std::string && path) std::unique_ptr LocalFileSystem::createFileWatcher(FileWatcher & fileWatcher) { +#ifdef SYSTEM_LINUX return std::unique_ptr( new LocalFileWatcher(&fileWatcher, shared_from_this()) ); +#else + return nullptr; +#endif } From b55db4a8fb6f293768c7d01ac642daadf84a7703 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 14:58:47 +0100 Subject: [PATCH 22/27] Implement new event type FileAttribChanged --- source/cppfs/include/cppfs/FileEventHandler.h | 9 +++++++++ source/cppfs/include/cppfs/FileHandle.h | 10 +++++----- source/cppfs/include/cppfs/FileWatcher.h | 2 +- source/cppfs/include/cppfs/cppfs.h | 7 ++++--- source/cppfs/source/FileEventHandler.cpp | 8 ++++++++ source/cppfs/source/linux/LocalFileWatcher.cpp | 10 ++++++---- source/cppfs/source/windows/LocalFileWatcher.cpp | 7 ++++--- source/examples/cppfs-watch/main.cpp | 7 ++++--- 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/source/cppfs/include/cppfs/FileEventHandler.h b/source/cppfs/include/cppfs/FileEventHandler.h index fad467d..1b7131b 100644 --- a/source/cppfs/include/cppfs/FileEventHandler.h +++ b/source/cppfs/include/cppfs/FileEventHandler.h @@ -76,6 +76,15 @@ class CPPFS_API FileEventHandler * Handle to file or directory */ virtual void onFileModified(FileHandle & fh); + + /** + * @brief + * Called when file attributes have been modified + * + * @param[in] fh + * Handle to file or directory + */ + virtual void onFileAttrChanged(FileHandle & fh); }; diff --git a/source/cppfs/include/cppfs/FileHandle.h b/source/cppfs/include/cppfs/FileHandle.h index 47f99f4..9b1542e 100644 --- a/source/cppfs/include/cppfs/FileHandle.h +++ b/source/cppfs/include/cppfs/FileHandle.h @@ -491,23 +491,23 @@ class CPPFS_API FileHandle /** * @brief - * Create file system watcher for this file or directory + * Create file system watcher for this file handle * * @param[in] events * Events that are watched (combination of FileEvent values) * @param[in] recursive - * Watch file system recursively? (only relevant if fh points to a directory) + * Watch file system recursively? * * @return * File watcher * * @remarks * This is a shortcut for creating a FileWatcher and adding file handles to watch. - * You should use this only to watch a single file or directory. - * To watch more than one file at a time, use FileWatcher and add. + * It will only work if the file handle points to a valid directory. + * To watch more than one directory at a time, use FileWatcher and add. * Avoid creating more than one FileWatcher, as OS limits can be reached. */ - FileWatcher watch(unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); + FileWatcher watch(unsigned int events = FileCreated | FileRemoved | FileModified | FileAttrChanged, RecursiveMode recursive = Recursive); /** * @brief diff --git a/source/cppfs/include/cppfs/FileWatcher.h b/source/cppfs/include/cppfs/FileWatcher.h index 0aae81f..35a0fea 100644 --- a/source/cppfs/include/cppfs/FileWatcher.h +++ b/source/cppfs/include/cppfs/FileWatcher.h @@ -136,7 +136,7 @@ class CPPFS_API FileWatcher * single file system. Also note that file watching is not * supported for remote file systems, such as SSH. */ - void add(FileHandle & dir, unsigned int events = FileCreated | FileRemoved | FileModified, RecursiveMode recursive = Recursive); + void add(FileHandle & dir, unsigned int events = FileCreated | FileRemoved | FileModified | FileAttrChanged, RecursiveMode recursive = Recursive); /** * @brief diff --git a/source/cppfs/include/cppfs/cppfs.h b/source/cppfs/include/cppfs/cppfs.h index 1ce814c..d040ad7 100644 --- a/source/cppfs/include/cppfs/cppfs.h +++ b/source/cppfs/include/cppfs/cppfs.h @@ -35,9 +35,10 @@ enum FilePermissions */ enum FileEvent { - FileCreated = 0x01, ///< A file or directory has been created - FileRemoved = 0x02, ///< A file or directory has been removed - FileModified = 0x04 ///< A file or directory has been modified + FileCreated = 0x01, ///< A file or directory has been created + FileRemoved = 0x02, ///< A file or directory has been removed + FileModified = 0x04, ///< A file or directory has been modified + FileAttrChanged = 0x08 ///< Attributes on a file or directory have been modified }; /** diff --git a/source/cppfs/source/FileEventHandler.cpp b/source/cppfs/source/FileEventHandler.cpp index 9d291ae..71eb23e 100644 --- a/source/cppfs/source/FileEventHandler.cpp +++ b/source/cppfs/source/FileEventHandler.cpp @@ -31,6 +31,10 @@ void FileEventHandler::onFileEvent(FileHandle & fh, FileEvent event) onFileModified(fh); break; + case FileAttrChanged: + onFileAttrChanged(fh); + break; + default: break; } @@ -48,5 +52,9 @@ void FileEventHandler::onFileModified(FileHandle &) { } +void FileEventHandler::onFileAttrChanged(FileHandle &) +{ +} + } // namespace cppfs diff --git a/source/cppfs/source/linux/LocalFileWatcher.cpp b/source/cppfs/source/linux/LocalFileWatcher.cpp index 38308a4..fc604e8 100644 --- a/source/cppfs/source/linux/LocalFileWatcher.cpp +++ b/source/cppfs/source/linux/LocalFileWatcher.cpp @@ -48,9 +48,11 @@ void LocalFileWatcher::add(FileHandle & dir, unsigned int events, RecursiveMode { // Get watch mode uint32_t flags = 0; - if (events & FileCreated) flags |= IN_CREATE; - if (events & FileRemoved) flags |= IN_DELETE; - if (events & FileModified) flags |= (IN_MODIFY | IN_ATTRIB); + if (events & FileCreated) flags |= IN_CREATE; + if (events & FileRemoved) flags |= IN_DELETE; + if (events & FileModified) flags |= IN_MODIFY; + if (events & FileAttrChanged) flags |= IN_ATTRIB; + // Create watcher int handle = inotify_add_watch(m_inotify, dir.path().c_str(), flags); @@ -121,7 +123,7 @@ void LocalFileWatcher::watch(int timeout) if (event->mask & IN_CREATE) eventType = FileCreated; else if (event->mask & IN_DELETE) eventType = FileRemoved; else if (event->mask & IN_MODIFY) eventType = FileModified; - else if (event->mask & IN_ATTRIB) eventType = FileModified; + else if (event->mask & IN_ATTRIB) eventType = FileAttrChanged; // Get watcher auto & watcher = m_watchers[event->wd]; diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index c4a0f7c..073b981 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -100,9 +100,10 @@ void LocalFileWatcher::watch(int timeout) auto lw = reinterpret_cast(it.platform.get()); waitHandles.push_back(lw->event.get()); DWORD flags = 0; - if (it.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; - if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; - if (it.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; + if (it.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; + if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; + if (it.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; + if (it.events & FileAttrChanged) flags |= FILE_NOTIFY_CHANGE_ATTRIBUTES; DWORD dwBytes = 0; if (!::GetOverlappedResult(it.handle.get(), &lw->ovl, &dwBytes, FALSE)) { diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index 773ca2e..96ee987 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -72,9 +72,10 @@ int main(int argc, char * argv[]) std::string type = (fh.isDirectory() ? "directory" : "file"); // Get operation - std::string operation = ( (event & FileCreated) ? "created" : - ( (event & FileRemoved) ? "removed" : - "modified" ) ); + std::string operation = ( (event & FileCreated) ? "created" : + ( (event & FileRemoved) ? "removed" : + ( (event & FileAttrChanged) ? "attributes changed" : + "modified" ) ) ); // Log event std::cout << "The " << type << " '" << fh.path() << "' was " << operation << "." << std::endl; From 4ff0fa4f5e1bbb27ef227bad56600367b9698a1d Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 15:39:29 +0100 Subject: [PATCH 23/27] cppfs-watch: allow multiple paths to be specified and add --recursive flag (off by default) --- source/examples/cppfs-watch/main.cpp | 128 +++++++++++++++------------ 1 file changed, 71 insertions(+), 57 deletions(-) diff --git a/source/examples/cppfs-watch/main.cpp b/source/examples/cppfs-watch/main.cpp index 96ee987..247a444 100644 --- a/source/examples/cppfs-watch/main.cpp +++ b/source/examples/cppfs-watch/main.cpp @@ -35,14 +35,14 @@ int main(int argc, char * argv[]) CommandLineSwitch swHelp("--help", "-h", "Print help text", CommandLineSwitch::Optional); action.add(&swHelp); - CommandLineOption opConfig("--config", "-c", "file", "Load configuration from file", CommandLineOption::Optional); - action.add(&opConfig); + CommandLineSwitch swRecursive("--recursive", "-r", "Watcher directories recursively", CommandLineSwitch::Optional); + action.add(&swRecursive); CommandLineOption opTime("--timeout", "-t", "seconds", "Timeout after which to stop (in seconds)", CommandLineOption::Optional); action.add(&opTime); - CommandLineParameter paramPath("path", CommandLineParameter::Optional); - action.add(¶mPath); + action.setOptionalParametersAllowed(true); + action.setOptionalParameterName("path"); // Parse command line program.parse(argc, argv); @@ -55,64 +55,78 @@ int main(int argc, char * argv[]) // Execute file watching try { - // Get path - std::string path = paramPath.value(); - if (path.empty()) path = "."; + // Create file watcher + FileWatcher watcher; - // Open directory - FileHandle dir = fs::open(path); - if (dir.isDirectory()) + // Get recursive mode + RecursiveMode recursive = swRecursive.activated() ? Recursive : NonRecursive; + + // Get paths to watch + auto paths = action.optionalParameters(); + if (paths.size() < 1) { + paths.push_back("."); + } + + // Add directories to watcher + for (auto path : paths) { - // Watch directory - FileWatcher watcher = dir.watch(); - - // Create file event handler - watcher.addHandler([] (FileHandle & fh, FileEvent event) { - // Get file type - std::string type = (fh.isDirectory() ? "directory" : "file"); - - // Get operation - std::string operation = ( (event & FileCreated) ? "created" : - ( (event & FileRemoved) ? "removed" : - ( (event & FileAttrChanged) ? "attributes changed" : - "modified" ) ) ); - - // Log event - std::cout << "The " << type << " '" << fh.path() << "' was " << operation << "." << std::endl; - }); - - // Begin watching and printing events - std::string t = opTime.value(); - if (t.empty()) { - // No timeout given - while (true) { - watcher.watch(); - } + // Open directory + FileHandle dir = fs::open(path); + if (dir.isDirectory()) + { + std::cout << "Watching '" << path << "'" << std::endl; + + // Watch directory + watcher.add(dir, FileCreated | FileRemoved | FileModified | FileAttrChanged, recursive); } - else { - // Get timeout - auto timeout = std::stoi(t); - std::cout << "Will stop watching after " << timeout << " seconds..." << std::endl; - - // Get current time - auto start = std::chrono::system_clock::now(); - auto now = std::chrono::system_clock::now(); - - // Execute watch loop - while (std::chrono::duration_cast(std::chrono::system_clock::now() - start).count() < timeout) - { - // Timeout 500ms to revive the main loop - watcher.watch(500); - - // Check elapsed time - now = std::chrono::system_clock::now(); - } + else + { + // Invalid directory + std::cout << "'" << path << "' is not a valid directory." << std::endl; } } - else - { - // Invalid path specified - std::cout << "'" << path << "' is not a valid directory." << std::endl; + + // Create file event handler + watcher.addHandler([] (FileHandle & fh, FileEvent event) { + // Get file type + std::string type = (fh.isDirectory() ? "directory" : "file"); + + // Get operation + std::string operation = ( (event & FileCreated) ? "created" : + ( (event & FileRemoved) ? "removed" : + ( (event & FileAttrChanged) ? "attributes changed" : + "modified" ) ) ); + + // Log event + std::cout << "The " << type << " '" << fh.path() << "' was " << operation << "." << std::endl; + }); + + // Begin watching and printing events + std::string t = opTime.value(); + if (t.empty()) { + // No timeout given + while (true) { + watcher.watch(); + } + } + else { + // Get timeout + auto timeout = std::stoi(t); + std::cout << "Will stop watching after " << timeout << " seconds..." << std::endl; + + // Get current time + auto start = std::chrono::system_clock::now(); + auto now = std::chrono::system_clock::now(); + + // Execute watch loop + while (std::chrono::duration_cast(std::chrono::system_clock::now() - start).count() < timeout) + { + // Timeout 500ms to revive the main loop + watcher.watch(500); + + // Check elapsed time + now = std::chrono::system_clock::now(); + } } } catch (std::exception& e) { // Error during execution From cd2fabd30a6d78e330574012b8f89c0ba93e2eac Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 16:11:50 +0100 Subject: [PATCH 24/27] Fix compilation on Windows --- source/cppfs/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/cppfs/CMakeLists.txt b/source/cppfs/CMakeLists.txt index acc089e..520512c 100644 --- a/source/cppfs/CMakeLists.txt +++ b/source/cppfs/CMakeLists.txt @@ -116,6 +116,16 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") ) endif() +if("${CMAKE_SYSTEM_NAME}" MATCHES "Windows") + set(headers ${headers} + ${include_path}/windows/LocalFileWatcher.h + ) + + set(sources ${sources} + ${source_path}/windows/LocalFileWatcher.cpp + ) +endif() + if (OPTION_BUILD_SSH_BACKEND) set(headers ${headers} ${include_path}/ssh/SshFileSystem.h From dc0ebb55cbe732944dc5ed0292c405279114fe62 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 16:32:13 +0100 Subject: [PATCH 25/27] Windows backend: add handling renaming of files and creating and deleting of directories --- .../include/cppfs/windows/LocalFileWatcher.h | 1 + .../cppfs/source/windows/LocalFileWatcher.cpp | 29 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index eae5f12..ffdf971 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -9,6 +9,7 @@ #include #include + namespace cppfs { diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index 073b981..463dab9 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -58,17 +58,21 @@ AbstractFileSystem * LocalFileWatcher::fs() const void LocalFileWatcher::add(FileHandle & dir, unsigned int events, RecursiveMode recursive) { - HANDLE hDir = ::CreateFileA(dir.path().c_str(), // pointer to the directory name - FILE_LIST_DIRECTORY, // access (read/write) mode - FILE_SHARE_READ // share mode - | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, // security descriptor - OPEN_EXISTING, // how to create - FILE_FLAG_BACKUP_SEMANTICS // file attributes - | FILE_FLAG_OVERLAPPED, - NULL); // file with attributes to copy - if (hDir == INVALID_HANDLE_VALUE) + // Open directory + HANDLE hDir = ::CreateFileA( + dir.path().c_str(), // Pointer to the directory name + FILE_LIST_DIRECTORY, // Access (read/write) mode + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // File share mode + NULL, // Security descriptor + OPEN_EXISTING, // How to create + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // File attributes + NULL // File with attributes to copy + ); + + // Check if directory could be opened + if (hDir == INVALID_HANDLE_VALUE) { throw std::runtime_error("failed to create directory listener"); + } std::shared_ptr hDirectory(hDir, ::CloseHandle); std::shared_ptr hEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle); @@ -100,8 +104,8 @@ void LocalFileWatcher::watch(int timeout) auto lw = reinterpret_cast(it.platform.get()); waitHandles.push_back(lw->event.get()); DWORD flags = 0; - if (it.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; - if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME; + if (it.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME; + if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME; if (it.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; if (it.events & FileAttrChanged) flags |= FILE_NOTIFY_CHANGE_ATTRIBUTES; @@ -175,6 +179,7 @@ void LocalFileWatcher::watch(int timeout) eventType = FileRemoved; break; case FILE_ACTION_MODIFIED: + case FILE_ACTION_RENAMED_NEW_NAME: eventType = FileModified; break; default: From 68c41aae63e221b809294a7162a08f2e66582d00 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 19:31:21 +0100 Subject: [PATCH 26/27] Review, clean up and comment Windows file watcher implementation --- .../include/cppfs/windows/LocalFileWatcher.h | 22 +- .../cppfs/source/windows/LocalFileWatcher.cpp | 230 +++++++++++------- 2 files changed, 152 insertions(+), 100 deletions(-) diff --git a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h index ffdf971..d909be0 100644 --- a/source/cppfs/include/cppfs/windows/LocalFileWatcher.h +++ b/source/cppfs/include/cppfs/windows/LocalFileWatcher.h @@ -6,6 +6,9 @@ #include #include +#define WIN32_LEAN_AND_MEAN +#include + #include #include @@ -53,18 +56,21 @@ class CPPFS_API LocalFileWatcher : public AbstractFileWatcherBackend * Watcher entry */ struct Watcher { - std::shared_ptr handle; - FileHandle dir; - unsigned int events; - RecursiveMode recursive; - std::shared_ptr platform; + FileHandle dir; ///< Directory that is watched + unsigned int events; ///< Watched events + RecursiveMode recursive; ///< Watch recursively? + std::shared_ptr dirHandle; ///< Handle for the directory + std::shared_ptr event; ///< Event that is triggered for this watcher + ::OVERLAPPED overlapped; ///< Overlapped data (for asynchronous operation) + char buffer[16384]; ///< Buffer for overlapped data (1024 * sizeof(FILE_NOTIFY_INFORMATION)) }; + protected: std::shared_ptr m_fs; ///< File system that created this watcher - std::shared_ptr m_waitStopEvent; ///< Event to stop watch function - std::shared_ptr m_watchersCS; ///< Watchers critical section - std::vector m_watchers; ///< Watchers + std::vector m_watchers; ///< List of watchers + ::HANDLE m_waitStopEvent; ///< Event to stop watch function + ::CRITICAL_SECTION m_mutexWatchers; ///< Mutex/critical section for accessing m_watchers }; diff --git a/source/cppfs/source/windows/LocalFileWatcher.cpp b/source/cppfs/source/windows/LocalFileWatcher.cpp index 463dab9..b9d6664 100644 --- a/source/cppfs/source/windows/LocalFileWatcher.cpp +++ b/source/cppfs/source/windows/LocalFileWatcher.cpp @@ -3,29 +3,30 @@ #include -#include - +#include #include namespace { - struct LocalWatcher - { - OVERLAPPED ovl; - std::shared_ptr event; - DWORD buffer[16384]; - }; - - // NB! Using a std::mutex resulted in spurious deadlocks using VS2017, whilst - // using CRITICAL_SECTION works fine. + /** + * @brief + * Helper class to lock/unlock a critical section automatically + */ struct ScopedCriticalSection { - LPCRITICAL_SECTION m_pcs; - ScopedCriticalSection(LPCRITICAL_SECTION pcs) : m_pcs(pcs) { + // NB! Using a std::mutex resulted in spurious deadlocks using VS2017, whilst + // using CRITICAL_SECTION works fine. + ::LPCRITICAL_SECTION m_pcs; + + ScopedCriticalSection(::LPCRITICAL_SECTION pcs) + : m_pcs(pcs) + { ::EnterCriticalSection(m_pcs); } - ~ScopedCriticalSection() { + + ~ScopedCriticalSection() + { ::LeaveCriticalSection(m_pcs); } }; @@ -39,16 +40,20 @@ namespace cppfs LocalFileWatcher::LocalFileWatcher(FileWatcher * fileWatcher, std::shared_ptr fs) : AbstractFileWatcherBackend(fileWatcher) , m_fs(fs) -, m_waitStopEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle) -, m_watchersCS(new CRITICAL_SECTION()) +, m_waitStopEvent(::CreateEvent(NULL, TRUE, FALSE, NULL)) { - ::InitializeCriticalSection((LPCRITICAL_SECTION)m_watchersCS.get()); + // Create critical section + ::InitializeCriticalSection(&m_mutexWatchers); } LocalFileWatcher::~LocalFileWatcher() { - ::DeleteCriticalSection((LPCRITICAL_SECTION)m_watchersCS.get()); + // Release critical section + ::DeleteCriticalSection(&m_mutexWatchers); m_watchers.clear(); + + // Release stop-event + ::CloseHandle(m_waitStopEvent); } AbstractFileSystem * LocalFileWatcher::fs() const @@ -59,7 +64,7 @@ AbstractFileSystem * LocalFileWatcher::fs() const void LocalFileWatcher::add(FileHandle & dir, unsigned int events, RecursiveMode recursive) { // Open directory - HANDLE hDir = ::CreateFileA( + ::HANDLE dirHandle = ::CreateFileA( dir.path().c_str(), // Pointer to the directory name FILE_LIST_DIRECTORY, // Access (read/write) mode FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // File share mode @@ -70,61 +75,73 @@ void LocalFileWatcher::add(FileHandle & dir, unsigned int events, RecursiveMode ); // Check if directory could be opened - if (hDir == INVALID_HANDLE_VALUE) { + if (dirHandle == INVALID_HANDLE_VALUE) { throw std::runtime_error("failed to create directory listener"); } - std::shared_ptr hDirectory(hDir, ::CloseHandle); - std::shared_ptr hEvent(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle); - if (!hEvent) + // Create new watcher + std::unique_ptr watcher(new Watcher); + watcher->dir = dir; + watcher->events = events; + watcher->recursive = recursive; + watcher->dirHandle = std::shared_ptr(dirHandle, ::CloseHandle); + watcher->event = std::shared_ptr(::CreateEvent(NULL, TRUE, FALSE, NULL), ::CloseHandle); + watcher->overlapped.hEvent = watcher->event.get(); + + // Check if event could be created + if (!watcher->event) { throw std::runtime_error("failed creating wait event"); + } - auto lw = std::make_shared(); - lw->event = std::move(hEvent); - lw->ovl.hEvent = lw->event.get(); - - Watcher w; - w.handle = std::move(hDirectory); - w.dir = dir; - w.events = events; - w.recursive = recursive; - w.platform = std::move(lw); + // Signal watch function that something has changed + ::SetEvent(m_waitStopEvent); - ::SetEvent(m_waitStopEvent.get()); - ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get()); - m_watchers.push_back(std::move(w)); + // Add watcher to list + ScopedCriticalSection lock(&m_mutexWatchers); + m_watchers.push_back(std::move(*watcher.release())); } void LocalFileWatcher::watch(int timeout) { - ScopedCriticalSection lock((LPCRITICAL_SECTION)m_watchersCS.get()); - std::vector waitHandles; - waitHandles.push_back(m_waitStopEvent.get()); - for (auto it : m_watchers) { - auto lw = reinterpret_cast(it.platform.get()); - waitHandles.push_back(lw->event.get()); - DWORD flags = 0; - if (it.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME; - if (it.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME; - if (it.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; - if (it.events & FileAttrChanged) flags |= FILE_NOTIFY_CHANGE_ATTRIBUTES; + ScopedCriticalSection lock(&m_mutexWatchers); + + // Initialize list of handles to wait for + std::vector<::HANDLE> waitHandles; + waitHandles.push_back(m_waitStopEvent); // This is now WAIT_OBJECT_0 + + // Process all watcher + for (auto & watcher : m_watchers) { + // Add event handle + waitHandles.push_back(watcher.event.get()); - DWORD dwBytes = 0; - if (!::GetOverlappedResult(it.handle.get(), &lw->ovl, &dwBytes, FALSE)) { + // Get events to watch for + DWORD flags = 0; + if (watcher.events & FileCreated) flags |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME; + if (watcher.events & FileRemoved) flags |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME; + if (watcher.events & FileModified) flags |= FILE_NOTIFY_CHANGE_LAST_WRITE; + if (watcher.events & FileAttrChanged) flags |= FILE_NOTIFY_CHANGE_ATTRIBUTES; + + // Check if data is available + DWORD size = 0; + if (!::GetOverlappedResult(watcher.dirHandle.get(), &watcher.overlapped, &size, FALSE)) { auto error = GetLastError(); - if (error == ERROR_IO_INCOMPLETE) + if (error == ERROR_IO_INCOMPLETE) { continue; + } } + // Create watcher if (!::ReadDirectoryChangesW( - it.handle.get(), - lw->buffer, - sizeof(lw->buffer), - (BOOL)it.recursive, + watcher.dirHandle.get(), + watcher.buffer, + sizeof(watcher.buffer), + (BOOL)watcher.recursive, flags, NULL, - &lw->ovl, - NULL)) { + &watcher.overlapped, + NULL)) + { + // Error creating the watcher auto error = GetLastError(); if (error != ERROR_IO_PENDING) { throw std::runtime_error("Error calling ReadDirectoryChangesW: " + std::to_string(error)); @@ -132,72 +149,101 @@ void LocalFileWatcher::watch(int timeout) } } + // Wait for events to happen auto waitResult = ::WaitForMultipleObjects( waitHandles.size(), waitHandles.data(), FALSE, - timeout >= 0 ? timeout : INFINITE); + timeout >= 0 ? timeout : INFINITE + ); + + // Check for timeout if (waitResult == WAIT_TIMEOUT) { return; } + + // Check for waitStopEvent if (waitResult == WAIT_OBJECT_0) { - ::ResetEvent(m_waitStopEvent.get()); + // Watchers have been modified, leave watch function + ::ResetEvent(m_waitStopEvent); return; } + // Get index of watcher that has fired auto index = waitResult - (WAIT_OBJECT_0 + 1); - if (index < 0 || index >= int(m_watchers.size())) { + if (index < 0 || index >= (int)m_watchers.size()) { + // Invalid watcher index throw std::runtime_error("Wait returned result: " + std::to_string(waitResult)); } - Watcher& w = m_watchers[index]; - auto lw = reinterpret_cast(w.platform.get()); - ::ResetEvent(lw->event.get()); - DWORD dwBytes = 0; - if (::GetOverlappedResult(w.handle.get(), &lw->ovl, &dwBytes, FALSE) && dwBytes > 0) { - char fileName[32768]; - char* p = (char*)lw->buffer; - for (;;) { - FILE_NOTIFY_INFORMATION* fni = reinterpret_cast(p); - int ret = ::WideCharToMultiByte(CP_ACP, + // Get watcher + Watcher & watcher = m_watchers[index]; + ::ResetEvent(watcher.event.get()); + + // Read events + DWORD size = 0; + if (::GetOverlappedResult(watcher.dirHandle.get(), &watcher.overlapped, &size, FALSE) && size > 0) { + // Process events + char * entry = reinterpret_cast(watcher.buffer); + while (entry) { + // Get file event notification + FILE_NOTIFY_INFORMATION * fileEvent = reinterpret_cast(entry); + + // Convert filename to 8-bit character string + char fileName[4096]; + int numBytes = ::WideCharToMultiByte(CP_ACP, 0, - fni->FileName, - fni->FileNameLength / sizeof(WCHAR), + fileEvent->FileName, + fileEvent->FileNameLength / sizeof(WCHAR), fileName, sizeof(fileName), NULL, NULL); - if (ret != 0) { - std::string fname(fileName, fileName + ret); - std::replace(fname.begin(), fname.end(), '\\', '/'); + + // Check if conversion was successful + if (numBytes != 0) { + // Get filename in unified format + std::string fname = FilePath(std::string(fileName, fileName + numBytes)).path(); + + // Determine event type FileEvent eventType = (FileEvent)0; - switch (fni->Action) { - case FILE_ACTION_ADDED: - eventType = FileCreated; - break; - case FILE_ACTION_REMOVED: - eventType = FileRemoved; - break; - case FILE_ACTION_MODIFIED: - case FILE_ACTION_RENAMED_NEW_NAME: - eventType = FileModified; - break; - default: - break; + switch (fileEvent->Action) { + case FILE_ACTION_ADDED: + eventType = FileCreated; + break; + + case FILE_ACTION_REMOVED: + eventType = FileRemoved; + break; + + case FILE_ACTION_MODIFIED: + case FILE_ACTION_RENAMED_NEW_NAME: + eventType = FileModified; + break; + + default: + break; } - if (w.events & eventType) { + + // Check if event is watched for + if (watcher.events & eventType) { // Get file handle - FileHandle fh = w.dir.open(fname); + FileHandle fh = watcher.dir.open(fname); // Invoke callback function onFileEvent(fh, eventType); } } - if (fni->NextEntryOffset == 0) - break; - p += fni->NextEntryOffset; + + // Get next event + if (fileEvent->NextEntryOffset != 0) { + entry += fileEvent->NextEntryOffset; + } else { + entry = nullptr; + } } } } + } // namespace cppfs From e2f78b69affcf0c3b76e1efc24d06112f69657e3 Mon Sep 17 00:00:00 2001 From: Stefan Buschmann Date: Sun, 6 Jan 2019 19:41:49 +0100 Subject: [PATCH 27/27] Adjust contributors list --- AUTHORS | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index d26a720..dbcd603 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,9 @@ Stefan Buschmann -Daniel Limberger +Daniel Limberger Willy Scheibel -Thanks to all Contributors + +Thanks to all Contributors: + +Robert Bielik (https://github.com/robiwano)