载入中...
搜索中...
未找到
FileWatch.cpp
浏览该文件的文档.
2#include "common/config.h"
3
4#include <algorithm>
5
6#ifndef EVENGINE_WEBGPU
7
8#include <Poco/Delegate.h>
9#include <Poco/DirectoryWatcher.h>
10#include <Poco/File.h>
11#include <Poco/Path.h>
12
13#include <cstdlib>
14#include <vector>
15
16#endif
17
18namespace eve::filesystem {
19
20namespace {
21
22constexpr auto kDebounceDelay = std::chrono::milliseconds(75);
23
24} // namespace
25
26#ifdef EVENGINE_WEBGPU
27// WebGPU (browser) build has no native directory watching; DirWatch stays an
28// empty struct so the unique_ptr members in the header compile.
29struct FileWatch::DirWatch {};
30#endif
31
32#ifndef EVENGINE_WEBGPU
33namespace {
34
35std::string basenameOf(const std::string &path) {
36 Poco::Path p(path);
37 return p.getFileName();
38}
39
40std::string normalizeDir(const std::string &dir) {
41 std::string s;
42#if !defined(_WIN32)
43 // Resolve symlinks (/var → /private/var on macOS) so watch keys match event paths.
44 char *resolved = realpath(dir.c_str(), nullptr);
45 if (resolved) {
46 s = resolved;
47 free(resolved);
48 } else
49#endif
50 {
51 Poco::Path p(dir);
52 p.makeAbsolute();
53 s = p.toString();
54 }
55 while (s.size() > 1 && (s.back() == '/' || s.back() == '\\')) s.pop_back();
56 return s;
57}
58
59} // namespace
60
62 std::string realDir;
63 std::unique_ptr<Poco::DirectoryWatcher> watcher;
64 std::unordered_map<std::string, std::string> filters; // filter → reportPath
65 int refs = 0;
66};
67#endif // !EVENGINE_WEBGPU
68
69FileWatch::FileWatch() = default;
70
72
73bool FileWatch::add(const std::string &realDir, const std::string &filterName,
74 const std::string &reportPath, int scanInterval) {
75#ifdef EVENGINE_WEBGPU
76 // Browser VFS has no native directory watching; no-op stub.
77 (void)realDir;
78 (void)filterName;
79 (void)reportPath;
80 (void)scanInterval;
81 return false;
82#else
83 if (realDir.empty() || reportPath.empty()) return false;
84 const std::string dir = normalizeDir(realDir);
85 try {
86 Poco::File f(dir);
87 if (!f.exists() || !f.isDirectory()) return false;
88 } catch (...) {
89 return false;
90 }
91
92 // Poco's Delegate::notify() invokes our callback while holding the
93 // delegate's own mutex, and `-=` (DefaultStrategy::remove ->
94 // Delegate::disable()) blocks on that same mutex. So both unsubscribing
95 // the delegates AND destroying the replaced DirectoryWatcher (whose dtor
96 // joins its inotify/OS thread) must happen outside mu_: otherwise the
97 // watcher thread blocked in handlePocoEvent() on mu_ while holding the
98 // delegate mutex deadlocks against us (ABBA) right after a file write.
99 std::unique_ptr<DirWatch> doomed;
100 bool ok = false;
101 {
102 std::lock_guard<std::mutex> lock(mu_);
103
104 auto prev = reportToDir_.find(reportPath);
105 if (prev != reportToDir_.end()) {
106 auto it = byDir_.find(prev->second);
107 if (it != byDir_.end()) {
108 DirWatch *dw = it->second.get();
109 for (auto fit = dw->filters.begin(); fit != dw->filters.end();) {
110 if (fit->second == reportPath) {
111 fit = dw->filters.erase(fit);
112 --dw->refs;
113 } else {
114 ++fit;
115 }
116 }
117 if (dw->refs <= 0) {
118 doomed = std::move(it->second);
119 byDir_.erase(it);
120 }
121 }
122 reportToDir_.erase(prev);
123 }
124
125 DirWatch *dw = nullptr;
126 bool created = false;
127 auto it = byDir_.find(dir);
128 if (it == byDir_.end()) {
129 auto owned = std::make_unique<DirWatch>();
130 owned->realDir = dir;
131 try {
132 owned->watcher = std::make_unique<Poco::DirectoryWatcher>(
133 dir, Poco::DirectoryWatcher::DW_FILTER_ENABLE_ALL, scanInterval);
134 } catch (...) {
135 ok = false;
136 }
137 if (owned->watcher) {
138 dw = owned.get();
139 byDir_[dir] = std::move(owned);
140 created = true;
141 }
142 } else {
143 dw = it->second.get();
144 }
145
146 if (dw && created) {
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);
152 }
153 if (dw) {
154 dw->filters[filterName] = reportPath;
155 ++dw->refs;
156 reportToDir_[reportPath] = dir;
157 ok = true;
158 }
159 }
160 if (doomed) unsubscribeDelegates(doomed.get());
161 return ok;
162#endif
163}
164
165bool FileWatch::remove(const std::string &reportPath) {
166#ifdef EVENGINE_WEBGPU
167 (void)reportPath;
168 return false;
169#else
170 // Same as add(): unsubscribe the delegates AND destroy the
171 // DirectoryWatcher (joins its thread) after mu_ is released, so a watcher
172 // thread blocked in handlePocoEvent() on mu_ cannot deadlock against us.
173 std::unique_ptr<DirWatch> doomed;
174 {
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);
180
181 auto it = byDir_.find(dir);
182 if (it == byDir_.end()) return false;
183 DirWatch *dw = it->second.get();
184 for (auto fit = dw->filters.begin(); fit != dw->filters.end();) {
185 if (fit->second == reportPath) {
186 fit = dw->filters.erase(fit);
187 --dw->refs;
188 } else {
189 ++fit;
190 }
191 }
192 if (dw->refs <= 0) {
193 doomed = std::move(it->second);
194 byDir_.erase(it);
195 }
196 }
197 if (doomed) unsubscribeDelegates(doomed.get());
198 return true;
199#endif
200}
201
203#ifdef EVENGINE_WEBGPU
204 std::lock_guard<std::mutex> lock(mu_);
205 queue_.clear();
206 return;
207#else
208 // Unsubscribe all delegates and destroy the DirectoryWatchers (each joins
209 // its inotify/OS thread) after mu_ is released, so a watcher thread
210 // blocked in handlePocoEvent() on the same lock cannot deadlock against
211 // clear().
212 std::vector<std::unique_ptr<DirWatch>> doomed;
213 {
214 std::lock_guard<std::mutex> lock(mu_);
215 for (auto &kv : byDir_) doomed.push_back(std::move(kv.second));
216 byDir_.clear();
217 reportToDir_.clear();
218 queue_.clear();
219 }
220 for (const auto &dw : doomed) unsubscribeDelegates(dw.get());
221#endif
222}
223
224int FileWatch::count() const {
225 std::lock_guard<std::mutex> lock(mu_);
226#ifdef EVENGINE_WEBGPU
227 return 0;
228#else
229 return int(reportToDir_.size());
230#endif
231}
232
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);
240 queue_.erase(it);
241 return true;
242}
243
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;
248 });
249 if (pending != queue_.end()) {
250 pending->event = std::move(event);
251 pending->readyAt = readyAt;
252 return;
253 }
254 queue_.push_back({std::move(event), readyAt});
255}
256
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);
265}
266
267void FileWatch::handlePocoEvent(const std::string &kind, const std::string &itemPath) {
268 const std::string name = basenameOf(itemPath);
269 Poco::Path parent(itemPath);
270 parent.makeParent();
271 const std::string dir = normalizeDir(parent.toString());
272
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);
279 break;
280 }
281 }
282 }
283 if (it == byDir_.end()) return;
284 DirWatch *dw = it->second.get();
285
286 auto push = [&](const std::string &report) {
287 Event ev;
288 ev.kind = kind;
289 ev.path = report;
290 ev.realPath = itemPath;
291 enqueue(std::move(ev));
292 };
293
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 += "/";
298 report += name;
299 push(report);
300 }
301
302 auto fit = dw->filters.find(name);
303 if (fit != dw->filters.end()) push(fit->second);
304}
305
306void FileWatch::onAdded(const void *, const Poco::DirectoryWatcher::DirectoryEvent &event) {
307 handlePocoEvent("added", event.item.path());
308}
309
310void FileWatch::onRemoved(const void *, const Poco::DirectoryWatcher::DirectoryEvent &event) {
311 handlePocoEvent("removed", event.item.path());
312}
313
314void FileWatch::onModified(const void *, const Poco::DirectoryWatcher::DirectoryEvent &event) {
315 handlePocoEvent("modified", event.item.path());
316}
317
318void FileWatch::onMovedFrom(const void *, const Poco::DirectoryWatcher::DirectoryEvent &event) {
319 handlePocoEvent("movedFrom", event.item.path());
320}
321
322void FileWatch::onMovedTo(const void *, const Poco::DirectoryWatcher::DirectoryEvent &event) {
323 handlePocoEvent("movedTo", event.item.path());
324}
325#endif // !EVENGINE_WEBGPU
326
327} // namespace eve::filesystem
ActiveSource owned
const std::string & s
std::vector< QuestEvent > pending
glm::vec4 p[6]
TokenKind kind
std::int32_t parent
std::string name
size_t queued
Definition OnnxGpgpu.cpp:60
float f
std::string path
Definition PlayHost.cpp:110
int created
V3 dir
Definition TreeMesh.cpp:150
bool add(const std::string &realDir, const std::string &filterName, const std::string &reportPath, int scanInterval=1)
Definition FileWatch.cpp:73
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
Definition FileWatch.cpp:63
std::unordered_map< std::string, std::string > filters
Definition FileWatch.cpp:64