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#include "FileSystemWatcherError.h"
19
20namespace SC
21{
22SC_DECLARE_ASSERT_PROVIDER(FileSystemWatcherAssert, SC_FILE_SYSTEM_WATCHER_EXPORT);
23
24#define SC_FILE_SYSTEM_WATCHER_ASSERT_RELEASE(e) SC_ASSERT_PROVIDER_RELEASE(SC::FileSystemWatcherAssert, e)
25#define SC_FILE_SYSTEM_WATCHER_ASSERT_DEBUG(e) SC_ASSERT_PROVIDER_DEBUG(SC::FileSystemWatcherAssert, e)
26#define SC_FILE_SYSTEM_WATCHER_TRUST_RESULT(expression) SC_FILE_SYSTEM_WATCHER_ASSERT_RELEASE(expression)
27
30
33
59
62{
63 private:
64 struct Internal;
65
66 struct InternalDefinition
67 {
68 static constexpr int Windows = 3 * sizeof(void*);
69 // Two extra pointers are temporary while Result and ResultFileSystemWatcher carry migration context.
70 static constexpr int Apple = 44 * sizeof(void*);
71 static constexpr int Linux = sizeof(void*) * 4;
72 static constexpr int Default = Linux;
73
74 static constexpr size_t Alignment = alignof(void*);
75
76 using Object = Internal;
77 };
78
79 public:
80 // Must be public to avoid GCC complaining
81 using InternalOpaque = OpaqueObject<InternalDefinition>;
82
83 private:
84 InternalOpaque internal;
85
86 //...
88 struct ThreadRunnerInternal;
89 struct ThreadRunnerDefinition
90 {
91 static constexpr int MaxWatchablePaths = 1024;
92
93 static constexpr int Windows = (2 * MaxWatchablePaths + 2) * sizeof(void*) + sizeof(uint64_t);
94 static constexpr int Apple = sizeof(void*);
95 static constexpr int Linux = sizeof(void*) * 6;
96 static constexpr int Default = Linux;
97
98 static constexpr size_t Alignment = alignof(void*);
99
100 using Object = ThreadRunnerInternal;
101 };
102
103 struct FolderWatcherInternal;
104 struct FolderWatcherSizes
105 {
106 static constexpr int MaxNumberOfSubdirs = 128; // Max number of subfolders tracked in a watcher
107 static constexpr int MaxChangesBufferSize = 16 * 1024;
108
109 static constexpr int Windows = MaxChangesBufferSize + sizeof(void*) + sizeof(void*);
110 static constexpr int Apple = sizeof(void*);
111 static constexpr int Linux = 1056 + 4096 + 8;
112 static constexpr int Default = Linux;
113
114 static constexpr size_t Alignment = alignof(void*);
115
116 using Object = FolderWatcherInternal;
117 };
118
119 public:
122 enum class Operation
123 {
124 Modified,
126 };
127
130 {
131 StringSpan basePath;
132 StringSpan relativePath;
134
138 ResultFileSystemWatcher getFullPath(StringPath& path) const;
139
140 private:
141 friend struct Internal;
142#if SC_PLATFORM_APPLE
143 StringSpan fullPath;
144#endif
145 };
146
151 {
156 FolderWatcher(Span<char> subFolderRelativePathsBuffer = {});
157
158 Function<void(const Notification&)> notifyCallback;
159
163
165 void setDebugName(const char* debugName);
166
167 private:
168 friend struct FileSystemWatcher;
169 template <typename T_AsyncEventLoop>
170 friend struct FileSystemWatcherAsyncT;
171#if SC_PLATFORM_WINDOWS
172#if SC_ASYNC_ENABLE_LOG
173 AlignedStorage<160> asyncStorage;
174#else
175 AlignedStorage<152> asyncStorage;
176#endif
177#endif
178 OpaqueObject<FolderWatcherSizes> internal;
179
180 FileSystemWatcher* parent = nullptr;
181 FolderWatcher* next = nullptr;
182 FolderWatcher* prev = nullptr;
183
184 StringPath path;
185
186#if SC_PLATFORM_LINUX
187 Span<char> subFolderRelativePathsBuffer;
188#endif
189 };
190
193 {
194 virtual ~EventLoopRunner() {}
195
196 protected:
197#if SC_PLATFORM_APPLE
198 virtual Result appleStartWakeUp() = 0;
199 virtual Result appleStopWakeUp() = 0;
200 virtual void appleSignalEventObject() = 0;
201 virtual Result appleWakeUpAndWait() = 0;
202
203#elif SC_PLATFORM_LINUX
204 virtual Result linuxStartSharedFileReadiness() = 0;
205 virtual Result linuxStopSharedFileReadiness() = 0;
206
207 int notifyFd = -1;
208
209#else
210 virtual Result windowsStartFolderExternalCompletion(FolderWatcher& watcher, void* handle) = 0;
211 virtual Result windowsRequestStopFolderExternalCompletion(FolderWatcher& watcher) = 0;
212 virtual Result windowsWaitFolderExternalCompletionStopped(FolderWatcher& watcher) = 0;
213 virtual Result windowsMarkFolderExternalCompletionPending(FolderWatcher& watcher) = 0;
214 virtual Result windowsClearFolderExternalCompletionPending(FolderWatcher& watcher) = 0;
215 virtual void* windowsGetOverlapped(FolderWatcher& watcher) = 0;
216#endif
217 friend struct Internal;
218 FileSystemWatcher* fileSystemWatcher = nullptr;
219
220 void internalInit(FileSystemWatcher& fsWatcher, int handle);
221 };
222
224 using ThreadRunner = OpaqueObject<ThreadRunnerDefinition>;
225
230
235
239
245 ResultFileSystemWatcher watch(FolderWatcher& watcher, StringSpan path);
246
247 void asyncNotify(FolderWatcher* watcher, size_t bytesTransferred = 0);
248
249 private:
250 friend decltype(internal);
251 friend decltype(FolderWatcher::internal);
252 template <typename T_AsyncEventLoop>
253 friend struct FileSystemWatcherAsyncT;
254 // Trimmed duplicate of IntrusiveDoubleLinkedList<T>
255 struct WatcherLinkedList
256 {
257 FolderWatcher* back = nullptr; // has no next
258 FolderWatcher* front = nullptr; // has no prev
259
260 void queueBack(FolderWatcher& watcher);
261 void remove(FolderWatcher& watcher);
262 };
263 WatcherLinkedList watchers;
264};
265
272template <typename T_AsyncEventLoop>
274{
276
277 using T_AsyncLoopWakeUp = typename T_AsyncEventLoop::LoopWakeUp;
278 using T_AsyncFileReadiness = typename T_AsyncEventLoop::FileReadiness;
279 using T_AsyncExternalCompletion = typename T_AsyncEventLoop::ExternalCompletion;
280 using T_EventObject = typename T_AsyncEventLoop::EventObjectType;
281 using T_AsyncResult = typename T_AsyncEventLoop::ResultType;
282
283 void init(T_AsyncEventLoop& loop) { eventLoop = &loop; }
284
285 protected:
286 T_AsyncEventLoop* eventLoop = nullptr;
287
288#if SC_PLATFORM_APPLE
289 virtual Result appleStartWakeUp() override
290 {
291 if (eventLoop == nullptr or fileSystemWatcher == nullptr)
292 return Result::Error(FileSystemWatcherResultCategory, FileSystemWatcherError::NotInitialized);
293 T_AsyncLoopWakeUp& wakeUp = asyncWakeUp;
294 wakeUp.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
295 return wakeUp.start(*eventLoop);
296 }
297
298 virtual void appleSignalEventObject() override { eventObject.signal(); }
299
300 virtual Result appleStopWakeUp() override
301 {
302 notificationGeneration++;
303 if (not asyncWakeUp.isFree())
304 {
305 SC_TRY(asyncWakeUp.stop(*eventLoop));
306 while (not asyncWakeUp.isFree())
307 {
308 SC_TRY(eventLoop->runNoWait());
309 }
310 }
311 // The producer is quiescent now; consume any unused refresh/close handoff signal.
312 eventObject.signal();
313 eventObject.wait();
314 return Result(true);
315 }
316
317 virtual Result appleWakeUpAndWait() override
318 {
319 const Result res = asyncWakeUp.wakeUp(*eventLoop);
320 eventObject.wait();
321 return res;
322 }
323
324 void onEventLoopNotification(typename T_AsyncLoopWakeUp::Result& result)
325 {
326 const size_t generation = notificationGeneration;
327 result.reactivateRequest(true);
328 fileSystemWatcher->asyncNotify(nullptr);
329 if (generation == notificationGeneration)
330 {
331 eventObject.signal();
332 }
333 }
334
335 size_t notificationGeneration = 0;
336
337 T_AsyncLoopWakeUp asyncWakeUp = {};
338 T_EventObject eventObject = {};
339#elif SC_PLATFORM_LINUX
340 virtual Result linuxStartSharedFileReadiness() override
341 {
342 if (eventLoop == nullptr or fileSystemWatcher == nullptr)
343 return Result::Error(FileSystemWatcherResultCategory, FileSystemWatcherError::NotInitialized);
344 SC_TRY(eventLoop->associateExternallyCreatedFileDescriptorHandle(notifyFd));
345 asyncPoll.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
346 return asyncPoll.start(*eventLoop, notifyFd);
347 }
348
349 virtual Result linuxStopSharedFileReadiness() override { return asyncPoll.stop(*eventLoop); }
350
351 void onEventLoopNotification(typename T_AsyncFileReadiness::Result& result)
352 {
353 fileSystemWatcher->asyncNotify(nullptr);
354 result.reactivateRequest(true);
355 }
356
357 T_AsyncFileReadiness asyncPoll = {};
358#else
360 virtual Result windowsStartFolderExternalCompletion(FolderWatcher& watcher, void* handle) override
361 {
362 if (eventLoop == nullptr or fileSystemWatcher == nullptr)
363 return Result::Error(FileSystemWatcherResultCategory, FileSystemWatcherError::NotInitialized);
364 T_AsyncExternalCompletion& completion =
365 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
366 placementNew(completion);
367 completion.setDebugName("FileSystemWatcherAsync Completion");
368 completion.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
369 return completion.start(*eventLoop, handle);
370 }
371
372 virtual Result windowsRequestStopFolderExternalCompletion(FolderWatcher& watcher) override
373 {
374 T_AsyncExternalCompletion& completion =
375 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
376
377 windowsFolderCompletionStopped = false;
378 onAsyncCompletionClose = [this, &watcher](T_AsyncResult&)
379 {
380 T_AsyncExternalCompletion& completion =
381 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
382 completion.~T_AsyncExternalCompletion();
383 windowsFolderCompletionStopped = true;
384 };
385 return completion.stop(*eventLoop, &onAsyncCompletionClose);
386 }
387
388 virtual Result windowsWaitFolderExternalCompletionStopped(FolderWatcher&) override
389 {
390 while (not windowsFolderCompletionStopped)
391 {
392 SC_TRY(eventLoop->runOnce());
393 }
394 return Result(true);
395 }
396
397 virtual Result windowsMarkFolderExternalCompletionPending(FolderWatcher& watcher) override
398 {
399 T_AsyncExternalCompletion& completion =
400 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
401 return completion.markSubmissionPending();
402 }
403
404 virtual Result windowsClearFolderExternalCompletionPending(FolderWatcher& watcher) override
405 {
406 T_AsyncExternalCompletion& completion =
407 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
408 return completion.clearSubmissionPending();
409 }
410
411 virtual void* windowsGetOverlapped(FolderWatcher& watcher) override
412 {
413 T_AsyncExternalCompletion& completion =
414 watcher.asyncStorage.template reinterpret_as<T_AsyncExternalCompletion>();
415 return completion.getWindowsOverlapped();
416 }
417
418 void onEventLoopNotification(typename T_AsyncExternalCompletion::Result& result)
419 {
420 SC_COMPILER_WARNING_PUSH_OFFSETOF;
421 auto& storage = reinterpret_cast<decltype(FolderWatcher::asyncStorage)&>(result.getAsync());
422 FolderWatcher& watcher = SC_COMPILER_FIELD_OFFSET(FolderWatcher, asyncStorage, storage);
423 fileSystemWatcher->asyncNotify(&watcher, result.completionData.bytesTransferred);
424 if (watcher.parent != nullptr and result.getAsync().hasSubmissionPending())
425 {
426 result.reactivateRequest(true);
427 }
428 SC_COMPILER_WARNING_POP_OFFSETOF;
429 }
430
431 Function<void(T_AsyncResult&)> onAsyncCompletionClose;
432 bool windowsFolderCompletionStopped = false;
433#endif
434};
435
437} // namespace SC
FileSystemWatcherAsyncT is an implementation of SC::FileSystemWatcher that uses SC::Async.
Definition FileSystemWatcher.h:274
Abstract class to use event loop notifications (see SC::FileSystemWatcherAsync).
Definition FileSystemWatcher.h:193
Represents a single folder being watched.
Definition FileSystemWatcher.h:151
Function< void(const Notification &)> notifyCallback
Function that will be called on a notification.
Definition FileSystemWatcher.h:158
FolderWatcher(Span< char > subFolderRelativePathsBuffer={})
Constructs a folder watcher.
ResultFileSystemWatcher stopWatching()
Stop watching this directory.
void setDebugName(const char *debugName)
Sets debug name for AsyncExternalCompletion used on Windows (used only for debug purposes)
Notification holding type and path.
Definition FileSystemWatcher.h:130
StringSpan relativePath
Relative path of the file being notified from basePath
Definition FileSystemWatcher.h:132
Operation operation
Notification type.
Definition FileSystemWatcher.h:133
StringSpan basePath
Reference to the watched directory.
Definition FileSystemWatcher.h:131
ResultFileSystemWatcher 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:62
ResultFileSystemWatcher watch(FolderWatcher &watcher, StringSpan path)
Starts watching a single directory, calling FolderWatcher::notifyCallback on file events.
ResultFileSystemWatcher close()
Stops all watchers and frees the ThreadRunner or EventLoopRunner passed in init.
Operation
Specifies the event classes.
Definition FileSystemWatcher.h:123
@ 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.
ResultFileSystemWatcher init(ThreadRunner &runner)
Setup watcher to receive notifications from a background thread.
OpaqueObject< ThreadRunnerDefinition > ThreadRunner
Delivers notifications on a background thread.
Definition FileSystemWatcher.h:224
ResultFileSystemWatcher init(EventLoopRunner &runner)
Setup watcher to receive async notifications on an event loop.
FileSystemWatcher result retaining backend-stage and native-error context.
Definition FileSystemWatcherError.h:72