cmFileMonitor.cxx
/* 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 <cassert>
#include <cstddef>
#include <unordered_map>
#include <utility>
#include <cm/memory>
#include "cmsys/SystemTools.hxx"
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 = default;
cmIBaseWatcher* Find(const std::string& ps)
{
const auto i = this->Children.find(ps);
return (i == this->Children.end()) ? nullptr : i->second.get();
}
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() { 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(ps, std::unique_ptr<cmIBaseWatcher>(watcher));
}
private:
std::unordered_map<std::string, std::unique_ptr<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(cm::make_unique<cmRootWatcher>(l))
{
}
cmFileMonitor::~cmFileMonitor() = default;
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.get();
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.get());
nextWatcher =
new cmRootDirectoryWatcher(this->Root.get(), currentSegment);
assert(currentWatcher->Find(currentSegment) == nextWatcher);
} else if (fileSegment) { // File part
assert(currentWatcher != this->Root.get());
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;
}