2#include "common/config.h"
8#include <Poco/Delegate.h>
9#include <Poco/DirectoryWatcher.h>
22constexpr auto kDebounceDelay = std::chrono::milliseconds(75);
29struct FileWatch::DirWatch {};
32#ifndef EVENGINE_WEBGPU
35std::string basenameOf(
const std::string &
path) {
37 return p.getFileName();
40std::string normalizeDir(
const std::string &
dir) {
44 char *resolved = realpath(
dir.c_str(),
nullptr);
55 while (
s.size() > 1 && (
s.back() ==
'/' ||
s.back() ==
'\\'))
s.pop_back();
63 std::unique_ptr<Poco::DirectoryWatcher>
watcher;
64 std::unordered_map<std::string, std::string>
filters;
73bool FileWatch::add(
const std::string &realDir,
const std::string &filterName,
74 const std::string &reportPath,
int scanInterval) {
83 if (realDir.empty() || reportPath.empty())
return false;
84 const std::string
dir = normalizeDir(realDir);
87 if (!
f.exists() || !
f.isDirectory())
return false;
99 std::unique_ptr<DirWatch> doomed;
102 std::lock_guard<std::mutex> lock(mu_);
104 auto prev = reportToDir_.find(reportPath);
105 if (prev != reportToDir_.end()) {
106 auto it = byDir_.find(prev->second);
107 if (it != byDir_.end()) {
110 if (fit->second == reportPath) {
118 doomed = std::move(it->second);
122 reportToDir_.erase(prev);
127 auto it = byDir_.find(
dir);
128 if (it == byDir_.end()) {
129 auto owned = std::make_unique<DirWatch>();
132 owned->watcher = std::make_unique<Poco::DirectoryWatcher>(
133 dir, Poco::DirectoryWatcher::DW_FILTER_ENABLE_ALL, scanInterval);
137 if (
owned->watcher) {
143 dw = it->second.get();
147 dw->
watcher->itemAdded += Poco::delegate(
this, &FileWatch::onAdded);
148 dw->
watcher->itemRemoved += Poco::delegate(
this, &FileWatch::onRemoved);
149 dw->
watcher->itemModified += Poco::delegate(
this, &FileWatch::onModified);
150 dw->
watcher->itemMovedFrom += Poco::delegate(
this, &FileWatch::onMovedFrom);
151 dw->
watcher->itemMovedTo += Poco::delegate(
this, &FileWatch::onMovedTo);
154 dw->
filters[filterName] = reportPath;
156 reportToDir_[reportPath] =
dir;
160 if (doomed) unsubscribeDelegates(doomed.get());
166#ifdef EVENGINE_WEBGPU
173 std::unique_ptr<DirWatch> doomed;
175 std::lock_guard<std::mutex> lock(mu_);
176 auto prev = reportToDir_.find(reportPath);
177 if (prev == reportToDir_.end())
return false;
178 const std::string
dir = prev->second;
179 reportToDir_.erase(prev);
181 auto it = byDir_.find(
dir);
182 if (it == byDir_.end())
return false;
185 if (fit->second == reportPath) {
193 doomed = std::move(it->second);
197 if (doomed) unsubscribeDelegates(doomed.get());
203#ifdef EVENGINE_WEBGPU
204 std::lock_guard<std::mutex> lock(mu_);
212 std::vector<std::unique_ptr<DirWatch>> doomed;
214 std::lock_guard<std::mutex> lock(mu_);
215 for (
auto &kv : byDir_) doomed.push_back(std::move(kv.second));
217 reportToDir_.clear();
220 for (
const auto &dw : doomed) unsubscribeDelegates(dw.get());
225 std::lock_guard<std::mutex> lock(mu_);
226#ifdef EVENGINE_WEBGPU
229 return int(reportToDir_.size());
234 std::lock_guard<std::mutex> lock(mu_);
235 const auto now = std::chrono::steady_clock::now();
236 auto it = std::find_if(queue_.begin(), queue_.end(),
237 [now](
const PendingEvent &event) { return event.readyAt <= now; });
238 if (it == queue_.end())
return false;
239 out = std::move(it->event);
244void FileWatch::enqueue(Event event) {
245 const auto readyAt = std::chrono::steady_clock::now() + kDebounceDelay;
246 auto pending = std::find_if(queue_.begin(), queue_.end(), [&](
const PendingEvent &
queued) {
247 return queued.event.realPath == event.realPath;
250 pending->event = std::move(event);
254 queue_.push_back({std::move(event), readyAt});
257#ifndef EVENGINE_WEBGPU
258void FileWatch::unsubscribeDelegates(DirWatch *dw) {
259 if (!dw || !dw->watcher)
return;
260 dw->watcher->itemAdded -= Poco::delegate(
this, &FileWatch::onAdded);
261 dw->watcher->itemRemoved -= Poco::delegate(
this, &FileWatch::onRemoved);
262 dw->watcher->itemModified -= Poco::delegate(
this, &FileWatch::onModified);
263 dw->watcher->itemMovedFrom -= Poco::delegate(
this, &FileWatch::onMovedFrom);
264 dw->watcher->itemMovedTo -= Poco::delegate(
this, &FileWatch::onMovedTo);
267void FileWatch::handlePocoEvent(
const std::string &
kind,
const std::string &itemPath) {
268 const std::string
name = basenameOf(itemPath);
269 Poco::Path
parent(itemPath);
271 const std::string
dir = normalizeDir(
parent.toString());
273 std::lock_guard<std::mutex> lock(mu_);
274 auto it = byDir_.find(
dir);
275 if (it == byDir_.end()) {
276 for (
auto &kv : byDir_) {
277 if (itemPath.size() >= kv.first.size() && itemPath.compare(0, kv.first.size(), kv.first) == 0) {
278 it = byDir_.find(kv.first);
283 if (it == byDir_.end())
return;
284 DirWatch *dw = it->second.get();
286 auto push = [&](
const std::string &report) {
290 ev.realPath = itemPath;
291 enqueue(std::move(ev));
294 auto all = dw->filters.find(
"");
295 if (all != dw->filters.end()) {
296 std::string report = all->second;
297 if (!report.empty() && report.back() !=
'/') report +=
"/";
302 auto fit = dw->filters.find(
name);
303 if (fit != dw->filters.end()) push(fit->second);
306void FileWatch::onAdded(
const void *,
const Poco::DirectoryWatcher::DirectoryEvent &event) {
307 handlePocoEvent(
"added", event.item.path());
310void FileWatch::onRemoved(
const void *,
const Poco::DirectoryWatcher::DirectoryEvent &event) {
311 handlePocoEvent(
"removed", event.item.path());
314void FileWatch::onModified(
const void *,
const Poco::DirectoryWatcher::DirectoryEvent &event) {
315 handlePocoEvent(
"modified", event.item.path());
318void FileWatch::onMovedFrom(
const void *,
const Poco::DirectoryWatcher::DirectoryEvent &event) {
319 handlePocoEvent(
"movedFrom", event.item.path());
322void FileWatch::onMovedTo(
const void *,
const Poco::DirectoryWatcher::DirectoryEvent &event) {
323 handlePocoEvent(
"movedTo", event.item.path());
std::vector< QuestEvent > pending
bool add(const std::string &realDir, const std::string &filterName, const std::string &reportPath, int scanInterval=1)
bool poll(Event &out)
Pop one event; false if empty.
bool remove(const std::string &reportPath)
Remove by reportPath previously passed to add().
std::unique_ptr< Poco::DirectoryWatcher > watcher
std::unordered_map< std::string, std::string > filters