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