Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
FileSystemWatcher.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4
5#include "../Common/CompilerMacrosExport.h"
6#ifndef SC_EXPORT_LIBRARY_FILE_SYSTEM_WATCHER
7#define SC_EXPORT_LIBRARY_FILE_SYSTEM_WATCHER 0
8#endif
9#define SC_FILE_SYSTEM_WATCHER_EXPORT SC_COMPILER_LIBRARY_EXPORT(SC_EXPORT_LIBRARY_FILE_SYSTEM_WATCHER)
10
11#include "../Common/Assert.h"
12#include "../Common/CompilerOffsetOf.h"
13#include "../Common/Function.h"
14#include "../Common/IGrowableBufferStringPath.h"
15#include "../Common/OpaqueObject.h"
16#include "../Common/PlatformMacrosType.h"
17#include "../Common/Result.h"
18
19namespace SC
20{
21SC_DECLARE_ASSERT_PROVIDER(FileSystemWatcherAssert, SC_FILE_SYSTEM_WATCHER_EXPORT);
22
23#define SC_FILE_SYSTEM_WATCHER_ASSERT_RELEASE(e) SC_ASSERT_PROVIDER_RELEASE(SC::FileSystemWatcherAssert, e)
24#define SC_FILE_SYSTEM_WATCHER_ASSERT_DEBUG(e) SC_ASSERT_PROVIDER_DEBUG(SC::FileSystemWatcherAssert, e)
25#define SC_FILE_SYSTEM_WATCHER_TRUST_RESULT(expression) SC_FILE_SYSTEM_WATCHER_ASSERT_RELEASE(expression)
26
29
32
58
61{
62 private:
63 struct Internal;
64
65 struct InternalDefinition
66 {
67 static constexpr int Windows = 3 * sizeof(void*);
68 static constexpr int Apple = 42 * sizeof(void*);
69 static constexpr int Linux = sizeof(void*) * 4;
70 static constexpr int Default = Linux;
71
72 static constexpr size_t Alignment = alignof(void*);
73
74 using Object = Internal;
75 };
76
77 public:
78 // Must be public to avoid GCC complaining
79 using InternalOpaque = OpaqueObject<InternalDefinition>;
80
81 private:
82 InternalOpaque internal;
83
84 //...
86 struct ThreadRunnerInternal;
87 struct ThreadRunnerDefinition
88 {
89 static constexpr int MaxWatchablePaths = 1024;
90
91 static constexpr int Windows = (2 * MaxWatchablePaths + 2) * sizeof(void*) + sizeof(uint64_t);
92 static constexpr int Apple = sizeof(void*);
93 static constexpr int Linux = sizeof(void*) * 6;
94 static constexpr int Default = Linux;
95
96 static constexpr size_t Alignment = alignof(void*);
97
98 using Object = ThreadRunnerInternal;
99 };
100
101 struct FolderWatcherInternal;
102 struct FolderWatcherSizes
103 {
104 static constexpr int MaxNumberOfSubdirs = 128; // Max number of subfolders tracked in a watcher
105 static constexpr int MaxChangesBufferSize = 1024;
106
107 static constexpr int Windows = MaxChangesBufferSize + sizeof(void*) + sizeof(void*);
108 static constexpr int Apple = sizeof(void*);
109 static constexpr int Linux = 1056 + 4096 + 8;
110 static constexpr int Default = Linux;
111
112 static constexpr size_t Alignment = alignof(void*);
113
114 using Object = FolderWatcherInternal;
115 };
116
117 public:
120 enum class Operation
121 {
122 Modified,
124 };
125
128 {
129 StringSpan basePath;
130 StringSpan relativePath;
132
136 SC::Result getFullPath(StringPath& path) const;
137
138 private:
139 friend struct Internal;
140#if SC_PLATFORM_APPLE
141 StringSpan fullPath;
142#endif
143 };
144
149 {
154 FolderWatcher(Span<char> subFolderRelativePathsBuffer = {});
155
156 Function<void(const Notification&)> notifyCallback;
157
160 Result stopWatching();
161
163 void setDebugName(const char* debugName);
164
165 private:
166 friend struct FileSystemWatcher;
167 template <typename T_AsyncEventLoop>
168 friend struct FileSystemWatcherAsyncT;
169#if SC_PLATFORM_WINDOWS
170#if SC_ASYNC_ENABLE_LOG
171 AlignedStorage<160> asyncStorage;
172#else
173 AlignedStorage<152> asyncStorage;
174#endif
175#endif
176 OpaqueObject<FolderWatcherSizes> internal;
177
178 FileSystemWatcher* parent = nullptr;
179 FolderWatcher* next = nullptr;
180 FolderWatcher* prev = nullptr;
181
182 StringPath path;
183
184#if SC_PLATFORM_LINUX
185 Span<char> subFolderRelativePathsBuffer;
186#endif
187 };
188
191 {
192 virtual ~EventLoopRunner() {}
193
194 protected:
195#if SC_PLATFORM_APPLE
196 virtual Result appleStartWakeUp() = 0;
197 virtual void appleSignalEventObject() = 0;
198 virtual Result appleWakeUpAndWait() = 0;
199
200#elif SC_PLATFORM_LINUX
201 virtual Result linuxStartSharedFileReadiness() = 0;
202 virtual Result linuxStopSharedFileReadiness() = 0;
203
204 int notifyFd = -1;
205
206#else
207 virtual Result windowsStartFolderExternalCompletion(FolderWatcher& watcher, void* handle) = 0;
208 virtual Result windowsRequestStopFolderExternalCompletion(FolderWatcher& watcher) = 0;
209 virtual Result windowsWaitFolderExternalCompletionStopped(FolderWatcher& watcher) = 0;
210 virtual Result windowsMarkFolderExternalCompletionPending(FolderWatcher& watcher) = 0;
211 virtual Result windowsClearFolderExternalCompletionPending(FolderWatcher& watcher) = 0;
212 virtual void* windowsGetOverlapped(FolderWatcher& watcher) = 0;
213#endif
214 friend struct Internal;
215 FileSystemWatcher* fileSystemWatcher = nullptr;
216
217 void internalInit(FileSystemWatcher& fsWatcher, int handle);
218 };
219
221 using ThreadRunner = OpaqueObject<ThreadRunnerDefinition>;
222
226 Result init(ThreadRunner& runner);
227
231 Result init(EventLoopRunner& runner);
232
235 Result close();
236
242 Result watch(FolderWatcher& watcher, StringSpan path);
243
244 void asyncNotify(FolderWatcher* watcher);
245
246 private:
247 friend decltype(internal);
248 friend decltype(FolderWatcher::internal);
249 template <typename T_AsyncEventLoop>
250 friend struct FileSystemWatcherAsyncT;
251 // Trimmed duplicate of IntrusiveDoubleLinkedList<T>
252 struct WatcherLinkedList
253 {
254 FolderWatcher* back = nullptr; // has no next
255 FolderWatcher* front = nullptr; // has no prev
256
257 void queueBack(FolderWatcher& watcher);
258 void remove(FolderWatcher& watcher);
259 };
260 WatcherLinkedList watchers;
261};
262
269template <typename T_AsyncEventLoop>
271{
273
274 using T_AsyncLoopWakeUp = typename T_AsyncEventLoop::LoopWakeUp;
275 using T_AsyncFileReadiness = typename T_AsyncEventLoop::FileReadiness;
276 using T_AsyncExternalCompletion = typename T_AsyncEventLoop::ExternalCompletion;
277 using T_EventObject = typename T_AsyncEventLoop::EventObjectType;
278 using T_AsyncResult = typename T_AsyncEventLoop::ResultType;
279
280 void init(T_AsyncEventLoop& loop) { eventLoop = &loop; }
281
282 protected:
283 T_AsyncEventLoop* eventLoop = nullptr;
284
285#if SC_PLATFORM_APPLE
286 virtual Result appleStartWakeUp() override
287 {
288 SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized");
289 T_AsyncLoopWakeUp& wakeUp = asyncWakeUp;
290 wakeUp.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
291 return wakeUp.start(*eventLoop, eventObject);
292 }
293
294 virtual void appleSignalEventObject() override { eventObject.signal(); }
295
296 virtual Result appleWakeUpAndWait() override
297 {
298 const Result res = asyncWakeUp.wakeUp(*eventLoop);
299 eventObject.wait();
300 return res;
301 }
302
303 void onEventLoopNotification(typename T_AsyncLoopWakeUp::Result& result)
304 {
305 fileSystemWatcher->asyncNotify(nullptr);
306 result.reactivateRequest(true);
307 }
308
309 T_AsyncLoopWakeUp asyncWakeUp = {};
310 T_EventObject eventObject = {};
311#elif SC_PLATFORM_LINUX
312 virtual Result linuxStartSharedFileReadiness() override
313 {
314 SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized");
315 SC_TRY(eventLoop->associateExternallyCreatedFileDescriptorHandle(notifyFd));
316 asyncPoll.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
317 return asyncPoll.start(*eventLoop, notifyFd);
318 }
319
320 virtual Result linuxStopSharedFileReadiness() override { return asyncPoll.stop(*eventLoop); }
321
322 void onEventLoopNotification(typename T_AsyncFileReadiness::Result& result)
323 {
324 fileSystemWatcher->asyncNotify(nullptr);
325 result.reactivateRequest(true);
326 }
327
328 T_AsyncFileReadiness asyncPoll = {};
329#else
331 virtual Result windowsStartFolderExternalCompletion(FolderWatcher& watcher, void* handle) override
332 {
333 SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized");
334 T_AsyncExternalCompletion& completion =
335 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
336 placementNew(completion);
337 completion.setDebugName("FileSystemWatcherAsync Completion");
338 completion.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
339 return completion.start(*eventLoop, handle);
340 }
341
342 virtual Result windowsRequestStopFolderExternalCompletion(FolderWatcher& watcher) override
343 {
344 T_AsyncExternalCompletion& completion =
345 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
346
347 windowsFolderCompletionStopped = false;
348 onAsyncCompletionClose = [this, &watcher](T_AsyncResult&)
349 {
350 T_AsyncExternalCompletion& completion =
351 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
352 completion.~T_AsyncExternalCompletion();
353 windowsFolderCompletionStopped = true;
354 };
355 return completion.stop(*eventLoop, &onAsyncCompletionClose);
356 }
357
358 virtual Result windowsWaitFolderExternalCompletionStopped(FolderWatcher&) override
359 {
360 while (not windowsFolderCompletionStopped)
361 {
362 SC_TRY(eventLoop->runOnce());
363 }
364 return Result(true);
365 }
366
367 virtual Result windowsMarkFolderExternalCompletionPending(FolderWatcher& watcher) override
368 {
369 T_AsyncExternalCompletion& completion =
370 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
371 return completion.markSubmissionPending();
372 }
373
374 virtual Result windowsClearFolderExternalCompletionPending(FolderWatcher& watcher) override
375 {
376 T_AsyncExternalCompletion& completion =
377 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
378 return completion.clearSubmissionPending();
379 }
380
381 virtual void* windowsGetOverlapped(FolderWatcher& watcher) override
382 {
383 T_AsyncExternalCompletion& completion =
384 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
385 return completion.getWindowsOverlapped();
386 }
387
388 void onEventLoopNotification(typename T_AsyncExternalCompletion::Result& result)
389 {
390 SC_COMPILER_WARNING_PUSH_OFFSETOF;
391 auto& storage = reinterpret_cast<decltype(FolderWatcher::asyncStorage)&>(result.getAsync());
392 FolderWatcher& watcher = SC_COMPILER_FIELD_OFFSET(FolderWatcher, asyncStorage, storage);
393 fileSystemWatcher->asyncNotify(&watcher);
394 if (watcher.parent != nullptr and result.getAsync().hasSubmissionPending())
395 {
396 result.reactivateRequest(true);
397 }
398 SC_COMPILER_WARNING_POP_OFFSETOF;
399 }
400
401 Function<void(T_AsyncResult&)> onAsyncCompletionClose;
402 bool windowsFolderCompletionStopped = false;
403#endif
404};
405
407} // namespace SC
FileSystemWatcherAsyncT is an implementation of SC::FileSystemWatcher that uses SC::Async.
Definition FileSystemWatcher.h:271
Abstract class to use event loop notifications (see SC::FileSystemWatcherAsync).
Definition FileSystemWatcher.h:191
Represents a single folder being watched.
Definition FileSystemWatcher.h:149
Function< void(const Notification &)> notifyCallback
Function that will be called on a notification.
Definition FileSystemWatcher.h:156
FolderWatcher(Span< char > subFolderRelativePathsBuffer={})
Constructs a folder watcher.
void setDebugName(const char *debugName)
Sets debug name for AsyncExternalCompletion used on Windows (used only for debug purposes)
Result stopWatching()
Stop watching this directory.
Notification holding type and path.
Definition FileSystemWatcher.h:128
StringSpan relativePath
Relative path of the file being notified from basePath
Definition FileSystemWatcher.h:130
Operation operation
Notification type.
Definition FileSystemWatcher.h:131
StringSpan basePath
Reference to the watched directory.
Definition FileSystemWatcher.h:129
SC::Result getFullPath(StringPath &path) const
Get the full path of the file being watched.
Notifies about events (add, remove, rename, modified) on files and directories.
Definition FileSystemWatcher.h:61
Result close()
Stops all watchers and frees the ThreadRunner or EventLoopRunner passed in init.
Result init(EventLoopRunner &runner)
Setup watcher to receive async notifications on an event loop.
Result init(ThreadRunner &runner)
Setup watcher to receive notifications from a background thread.
Operation
Specifies the event classes.
Definition FileSystemWatcher.h:121
@ Modified
A file or directory has been modified in its contents and/or timestamp.
@ AddRemoveRename
A file or directory has been added, removed or renamed.
Result watch(FolderWatcher &watcher, StringSpan path)
Starts watching a single directory, calling FolderWatcher::notifyCallback on file events.
OpaqueObject< ThreadRunnerDefinition > ThreadRunner
Delivers notifications on a background thread.
Definition FileSystemWatcher.h:221