diff options
Diffstat (limited to 'Source/cmFileMonitor.cxx')
-rw-r--r-- | Source/cmFileMonitor.cxx | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/Source/cmFileMonitor.cxx b/Source/cmFileMonitor.cxx new file mode 100644 index 0000000..56ee739 --- /dev/null +++ b/Source/cmFileMonitor.cxx @@ -0,0 +1,387 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmFileMonitor.h" + +#include "cmAlgorithms.h" +#include "cmsys/SystemTools.hxx" + +#include <cassert> +#include <stddef.h> +#include <unordered_map> +#include <utility> + +namespace { +void on_directory_change(uv_fs_event_t* handle, const char* filename, + int events, int status); +void on_fs_close(uv_handle_t* handle); +} // namespace + +class cmIBaseWatcher +{ +public: + virtual ~cmIBaseWatcher() = default; + + virtual void Trigger(const std::string& pathSegment, int events, + int status) const = 0; + virtual std::string Path() const = 0; + virtual uv_loop_t* Loop() const = 0; + + virtual void StartWatching() = 0; + virtual void StopWatching() = 0; + + virtual std::vector<std::string> WatchedFiles() const = 0; + virtual std::vector<std::string> WatchedDirectories() const = 0; +}; + +class cmVirtualDirectoryWatcher : public cmIBaseWatcher +{ +public: + ~cmVirtualDirectoryWatcher() override { cmDeleteAll(this->Children); } + + cmIBaseWatcher* Find(const std::string& ps) + { + const auto i = this->Children.find(ps); + return (i == this->Children.end()) ? nullptr : i->second; + } + + void Trigger(const std::string& pathSegment, int events, + int status) const final + { + if (pathSegment.empty()) { + for (auto const& child : this->Children) { + child.second->Trigger(std::string(), events, status); + } + } else { + const auto i = this->Children.find(pathSegment); + if (i != this->Children.end()) { + i->second->Trigger(std::string(), events, status); + } + } + } + + void StartWatching() override + { + for (auto const& child : this->Children) { + child.second->StartWatching(); + } + } + + void StopWatching() override + { + for (auto const& child : this->Children) { + child.second->StopWatching(); + } + } + + std::vector<std::string> WatchedFiles() const final + { + std::vector<std::string> result; + for (auto const& child : this->Children) { + for (std::string const& f : child.second->WatchedFiles()) { + result.push_back(f); + } + } + return result; + } + + std::vector<std::string> WatchedDirectories() const override + { + std::vector<std::string> result; + for (auto const& child : this->Children) { + for (std::string const& dir : child.second->WatchedDirectories()) { + result.push_back(dir); + } + } + return result; + } + + void Reset() + { + cmDeleteAll(this->Children); + this->Children.clear(); + } + + void AddChildWatcher(const std::string& ps, cmIBaseWatcher* watcher) + { + assert(!ps.empty()); + assert(this->Children.find(ps) == this->Children.end()); + assert(watcher); + + this->Children.emplace(std::make_pair(ps, watcher)); + } + +private: + std::unordered_map<std::string, cmIBaseWatcher*> Children; // owned! +}; + +// Root of all the different (on windows!) root directories: +class cmRootWatcher : public cmVirtualDirectoryWatcher +{ +public: + cmRootWatcher(uv_loop_t* loop) + : mLoop(loop) + { + assert(loop); + } + + std::string Path() const final + { + assert(false); + return std::string(); + } + uv_loop_t* Loop() const final { return this->mLoop; } + +private: + uv_loop_t* const mLoop; // no ownership! +}; + +// Real directories: +class cmRealDirectoryWatcher : public cmVirtualDirectoryWatcher +{ +public: + cmRealDirectoryWatcher(cmVirtualDirectoryWatcher* p, const std::string& ps) + : Parent(p) + , PathSegment(ps) + { + assert(p); + assert(!ps.empty()); + + p->AddChildWatcher(ps, this); + } + + void StartWatching() final + { + if (!this->Handle) { + this->Handle = new uv_fs_event_t; + + uv_fs_event_init(this->Loop(), this->Handle); + this->Handle->data = this; + uv_fs_event_start(this->Handle, &on_directory_change, Path().c_str(), 0); + } + cmVirtualDirectoryWatcher::StartWatching(); + } + + void StopWatching() final + { + if (this->Handle) { + uv_fs_event_stop(this->Handle); + if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(this->Handle))) { + uv_close(reinterpret_cast<uv_handle_t*>(this->Handle), &on_fs_close); + } + this->Handle = nullptr; + } + cmVirtualDirectoryWatcher::StopWatching(); + } + + uv_loop_t* Loop() const final { return this->Parent->Loop(); } + + std::vector<std::string> WatchedDirectories() const override + { + std::vector<std::string> result = { Path() }; + for (std::string const& dir : + cmVirtualDirectoryWatcher::WatchedDirectories()) { + result.push_back(dir); + } + return result; + } + +protected: + cmVirtualDirectoryWatcher* const Parent; + const std::string PathSegment; + +private: + uv_fs_event_t* Handle = nullptr; // owner! +}; + +// Root directories: +class cmRootDirectoryWatcher : public cmRealDirectoryWatcher +{ +public: + cmRootDirectoryWatcher(cmRootWatcher* p, const std::string& ps) + : cmRealDirectoryWatcher(p, ps) + { + } + + std::string Path() const final { return this->PathSegment; } +}; + +// Normal directories below root: +class cmDirectoryWatcher : public cmRealDirectoryWatcher +{ +public: + cmDirectoryWatcher(cmRealDirectoryWatcher* p, const std::string& ps) + : cmRealDirectoryWatcher(p, ps) + { + } + + std::string Path() const final + { + return this->Parent->Path() + this->PathSegment + "/"; + } +}; + +class cmFileWatcher : public cmIBaseWatcher +{ +public: + cmFileWatcher(cmRealDirectoryWatcher* p, const std::string& ps, + cmFileMonitor::Callback cb) + : Parent(p) + , PathSegment(ps) + , CbList({ std::move(cb) }) + { + assert(p); + assert(!ps.empty()); + p->AddChildWatcher(ps, this); + } + + void StartWatching() final {} + + void StopWatching() final {} + + void AppendCallback(cmFileMonitor::Callback const& cb) + { + this->CbList.push_back(cb); + } + + std::string Path() const final + { + return this->Parent->Path() + this->PathSegment; + } + + std::vector<std::string> WatchedDirectories() const final { return {}; } + + std::vector<std::string> WatchedFiles() const final + { + return { this->Path() }; + } + + void Trigger(const std::string& ps, int events, int status) const final + { + assert(ps.empty()); + assert(status == 0); + static_cast<void>(ps); + + const std::string path = this->Path(); + for (cmFileMonitor::Callback const& cb : this->CbList) { + cb(path, events, status); + } + } + + uv_loop_t* Loop() const final { return this->Parent->Loop(); } + +private: + cmRealDirectoryWatcher* Parent; + const std::string PathSegment; + std::vector<cmFileMonitor::Callback> CbList; +}; + +namespace { + +void on_directory_change(uv_fs_event_t* handle, const char* filename, + int events, int status) +{ + const cmIBaseWatcher* const watcher = + static_cast<const cmIBaseWatcher*>(handle->data); + const std::string pathSegment(filename ? filename : ""); + watcher->Trigger(pathSegment, events, status); +} + +void on_fs_close(uv_handle_t* handle) +{ + delete reinterpret_cast<uv_fs_event_t*>(handle); +} + +} // namespace + +cmFileMonitor::cmFileMonitor(uv_loop_t* l) + : Root(new cmRootWatcher(l)) +{ +} + +cmFileMonitor::~cmFileMonitor() +{ + delete this->Root; +} + +void cmFileMonitor::MonitorPaths(const std::vector<std::string>& paths, + Callback const& cb) +{ + for (std::string const& p : paths) { + std::vector<std::string> pathSegments; + cmsys::SystemTools::SplitPath(p, pathSegments, true); + const bool pathIsFile = !cmsys::SystemTools::FileIsDirectory(p); + + const size_t segmentCount = pathSegments.size(); + if (segmentCount < 2) { // Expect at least rootdir and filename + continue; + } + cmVirtualDirectoryWatcher* currentWatcher = this->Root; + for (size_t i = 0; i < segmentCount; ++i) { + assert(currentWatcher); + + const bool fileSegment = (i == segmentCount - 1 && pathIsFile); + const bool rootSegment = (i == 0); + assert( + !(fileSegment && + rootSegment)); // Can not be both filename and root part of the path! + + const std::string& currentSegment = pathSegments[i]; + if (currentSegment.empty()) { + continue; + } + + cmIBaseWatcher* nextWatcher = currentWatcher->Find(currentSegment); + if (!nextWatcher) { + if (rootSegment) { // Root part + assert(currentWatcher == this->Root); + nextWatcher = new cmRootDirectoryWatcher(this->Root, currentSegment); + assert(currentWatcher->Find(currentSegment) == nextWatcher); + } else if (fileSegment) { // File part + assert(currentWatcher != this->Root); + nextWatcher = new cmFileWatcher( + dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher), + currentSegment, cb); + assert(currentWatcher->Find(currentSegment) == nextWatcher); + } else { // Any normal directory in between + nextWatcher = new cmDirectoryWatcher( + dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher), + currentSegment); + assert(currentWatcher->Find(currentSegment) == nextWatcher); + } + } else { + if (fileSegment) { + auto filePtr = dynamic_cast<cmFileWatcher*>(nextWatcher); + assert(filePtr); + filePtr->AppendCallback(cb); + continue; + } + } + currentWatcher = dynamic_cast<cmVirtualDirectoryWatcher*>(nextWatcher); + } + } + this->Root->StartWatching(); +} + +void cmFileMonitor::StopMonitoring() +{ + this->Root->StopWatching(); + this->Root->Reset(); +} + +std::vector<std::string> cmFileMonitor::WatchedFiles() const +{ + std::vector<std::string> result; + if (this->Root) { + result = this->Root->WatchedFiles(); + } + return result; +} + +std::vector<std::string> cmFileMonitor::WatchedDirectories() const +{ + std::vector<std::string> result; + if (this->Root) { + result = this->Root->WatchedDirectories(); + } + return result; +} |