| /* 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: |
| cmIBaseWatcher() = default; |
| 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); |
| } |
| |
| ~cmRealDirectoryWatcher() override |
| { |
| // Handle is freed via uv_handle_close callback! |
| } |
| |
| 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 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); |
| 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; |
| } |