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 "../Foundation/Compiler.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 "../Foundation/Function.h"
12#include "../Foundation/OpaqueObject.h"
13#include "../Foundation/Result.h"
14#include "../Foundation/StringPath.h"
15
16namespace SC
17{
18
21
24
50
53{
54 private:
55 struct Internal;
56
57 struct InternalDefinition
58 {
59 static constexpr int Windows = 3 * sizeof(void*);
60 static constexpr int Apple = 42 * sizeof(void*);
61 static constexpr int Linux = sizeof(void*) * 4;
62 static constexpr int Default = Linux;
63
64 static constexpr size_t Alignment = alignof(void*);
65
66 using Object = Internal;
67 };
68
69 public:
70 // Must be public to avoid GCC complaining
72
73 private:
74 InternalOpaque internal;
75
76 //...
78 struct ThreadRunnerInternal;
79 struct ThreadRunnerDefinition
80 {
81 static constexpr int MaxWatchablePaths = 1024;
82
83 static constexpr int Windows = (2 * MaxWatchablePaths + 2) * sizeof(void*) + sizeof(uint64_t);
84 static constexpr int Apple = sizeof(void*);
85 static constexpr int Linux = sizeof(void*) * 6;
86 static constexpr int Default = Linux;
87
88 static constexpr size_t Alignment = alignof(void*);
89
90 using Object = ThreadRunnerInternal;
91 };
92
93 struct FolderWatcherInternal;
94 struct FolderWatcherSizes
95 {
96 static constexpr int MaxNumberOfSubdirs = 128; // Max number of subfolders tracked in a watcher
97 static constexpr int MaxChangesBufferSize = 1024;
98
99 static constexpr int Windows = MaxChangesBufferSize + sizeof(void*) + sizeof(void*);
100 static constexpr int Apple = sizeof(void*);
101 static constexpr int Linux = 1056 + 4096 + 8;
102 static constexpr int Default = Linux;
103
104 static constexpr size_t Alignment = alignof(void*);
105
106 using Object = FolderWatcherInternal;
107 };
108
109 public:
112 enum class Operation
113 {
114 Modified,
116 };
117
120 {
124
129
130 private:
131 friend struct Internal;
132#if SC_PLATFORM_APPLE
133 StringSpan fullPath;
134#endif
135 };
136
141 {
146 FolderWatcher(Span<char> subFolderRelativePathsBuffer = {});
147
149
153
155 void setDebugName(const char* debugName);
156
157 private:
158 friend struct FileSystemWatcher;
159 template <typename T_AsyncEventLoop>
160 friend struct FileSystemWatcherAsyncT;
161#if SC_PLATFORM_WINDOWS
162#if SC_ASYNC_ENABLE_LOG
163 AlignedStorage<120> asyncStorage;
164#else
165 AlignedStorage<112> asyncStorage;
166#endif
167#endif
169
170 FileSystemWatcher* parent = nullptr;
171 FolderWatcher* next = nullptr;
172 FolderWatcher* prev = nullptr;
173
174 StringPath path;
175
176#if SC_PLATFORM_LINUX
177 Span<char> subFolderRelativePathsBuffer;
178#endif
179 };
180
183 {
184 virtual ~EventLoopRunner() {}
185
186 protected:
187#if SC_PLATFORM_APPLE
188 virtual Result appleStartWakeUp() = 0;
189 virtual void appleSignalEventObject() = 0;
190 virtual Result appleWakeUpAndWait() = 0;
191
192#elif SC_PLATFORM_LINUX
193 virtual Result linuxStartSharedFilePoll() = 0;
194 virtual Result linuxStopSharedFilePoll() = 0;
195
196 int notifyFd = -1;
197
198#else
199 virtual Result windowsStartFolderFilePoll(FolderWatcher& watcher, void* handle) = 0;
200 virtual Result windowsStopFolderFilePoll(FolderWatcher& watcher) = 0;
201 virtual void* windowsGetOverlapped(FolderWatcher& watcher) = 0;
202#endif
203 friend struct Internal;
204 FileSystemWatcher* fileSystemWatcher = nullptr;
205
206 void internalInit(FileSystemWatcher& fsWatcher, int handle);
207 };
208
211
216
221
225
232
233 void asyncNotify(FolderWatcher* watcher);
234
235 private:
236 friend decltype(internal);
237 friend decltype(FolderWatcher::internal);
238 template <typename T_AsyncEventLoop>
239 friend struct FileSystemWatcherAsyncT;
240 // Trimmed duplicate of IntrusiveDoubleLinkedList<T>
241 struct WatcherLinkedList
242 {
243 FolderWatcher* back = nullptr; // has no next
244 FolderWatcher* front = nullptr; // has no prev
245
246 void queueBack(FolderWatcher& watcher);
247 void remove(FolderWatcher& watcher);
248 };
249 WatcherLinkedList watchers;
250};
251
258template <typename T_AsyncEventLoop>
260{
262
263 using T_AsyncLoopWakeUp = typename T_AsyncEventLoop::LoopWakeUp;
264 using T_AsyncFilePoll = typename T_AsyncEventLoop::FilePoll;
265 using T_EventObject = typename T_AsyncEventLoop::EventObjectType;
266 using T_AsyncResult = typename T_AsyncEventLoop::ResultType;
267
268 void init(T_AsyncEventLoop& loop) { eventLoop = &loop; }
269
270 protected:
271 T_AsyncEventLoop* eventLoop = nullptr;
272
273#if SC_PLATFORM_APPLE
274 virtual Result appleStartWakeUp() override
275 {
276 SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized");
277 T_AsyncLoopWakeUp& wakeUp = asyncWakeUp;
278 wakeUp.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
279 return wakeUp.start(*eventLoop, eventObject);
280 }
281
282 virtual void appleSignalEventObject() override { eventObject.signal(); }
283
284 virtual Result appleWakeUpAndWait() override
285 {
286 const Result res = asyncWakeUp.wakeUp(*eventLoop);
287 eventObject.wait();
288 return res;
289 }
290
291 void onEventLoopNotification(typename T_AsyncLoopWakeUp::Result& result)
292 {
293 fileSystemWatcher->asyncNotify(nullptr);
294 result.reactivateRequest(true);
295 }
296
297 T_AsyncLoopWakeUp asyncWakeUp = {};
298 T_EventObject eventObject = {};
299#elif SC_PLATFORM_LINUX
300 virtual Result linuxStartSharedFilePoll() override
301 {
302 SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized");
303 SC_TRY(eventLoop->associateExternallyCreatedFileDescriptorHandle(notifyFd));
304 asyncPoll.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
305 return asyncPoll.start(*eventLoop, notifyFd);
306 }
307
308 virtual Result linuxStopSharedFilePoll() override { return asyncPoll.stop(*eventLoop); }
309
310 void onEventLoopNotification(typename T_AsyncFilePoll::Result& result)
311 {
312 fileSystemWatcher->asyncNotify(nullptr);
313 result.reactivateRequest(true);
314 }
315
316 T_AsyncFilePoll asyncPoll = {};
317#else
319 virtual Result windowsStartFolderFilePoll(FolderWatcher& watcher, void* handle) override
320 {
321 SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized");
322 SC_TRY(eventLoop->associateExternallyCreatedFileDescriptorHandle(handle));
323 T_AsyncFilePoll& asyncPoll = watcher.asyncStorage.template reinterpret_as<T_AsyncFilePoll>();
324 placementNew(asyncPoll);
325 asyncPoll.setDebugName("FileSystemWatcherAsync Poll");
326 asyncPoll.callback.template bind<Self, &Self::onEventLoopNotification>(*this);
327 return asyncPoll.start(*eventLoop, handle);
328 }
329
330 virtual Result windowsStopFolderFilePoll(FolderWatcher& watcher) override
331 {
332 // This is not strictly needed as file handle is being closed soon after anyway
333 // SC_TRUST_RESULT(eventLoop->removeAllAssociationsFor(fwi.fileHandle));
334 T_AsyncFilePoll& asyncPoll = watcher.asyncStorage.template reinterpret_as<T_AsyncFilePoll>();
335
336 onAsyncPollClose = [&watcher](T_AsyncResult&)
337 {
338 T_AsyncFilePoll& asyncPoll = watcher.asyncStorage.template reinterpret_as<T_AsyncFilePoll>();
339 asyncPoll.~T_AsyncFilePoll();
340 };
341 return asyncPoll.stop(*eventLoop, &onAsyncPollClose);
342 }
343
344 virtual void* windowsGetOverlapped(FolderWatcher& watcher) override
345 {
346 T_AsyncFilePoll& asyncPoll = watcher.asyncStorage.template reinterpret_as<T_AsyncFilePoll>();
347 return asyncPoll.getOverlappedPtr();
348 }
349
350 void onEventLoopNotification(typename T_AsyncFilePoll::Result& result)
351 {
353 auto& storage = reinterpret_cast<decltype(FolderWatcher::asyncStorage)&>(result.getAsync());
354 FolderWatcher& watcher = SC_COMPILER_FIELD_OFFSET(FolderWatcher, asyncStorage, storage);
355 fileSystemWatcher->asyncNotify(&watcher);
356 result.reactivateRequest(true);
358 }
359
360 Function<void(T_AsyncResult&)> onAsyncPollClose;
361#endif
362};
363
365} // namespace SC
#define SC_COMPILER_WARNING_POP
Pops warning from inside a macro.
Definition Compiler.h:120
#define SC_COMPILER_WARNING_PUSH_OFFSETOF
Disables invalid-offsetof gcc and clang warning.
Definition Compiler.h:140
struct SC_FOUNDATION_EXPORT Function
Wraps function pointers, member functions and lambdas without ever allocating.
Definition Function.h:19
unsigned long long uint64_t
Platform independent (8) bytes unsigned int.
Definition PrimitiveTypes.h:33
#define SC_TRY_MSG(expression, failedMessage)
Checks the value of the given expression and if failed, returns a result with failedMessage to caller...
Definition Result.h:60
#define SC_TRY(expression)
Checks the value of the given expression and if failed, returns this value to caller.
Definition Result.h:49
A buffer of bytes with given alignment.
Definition AlignedStorage.h:29
FileSystemWatcherAsyncT is an implementation of SC::FileSystemWatcher that uses SC::Async.
Definition FileSystemWatcher.h:260
Abstract class to use event loop notifications (see SC::FileSystemWatcherAsync).
Definition FileSystemWatcher.h:183
Represents a single folder being watched.
Definition FileSystemWatcher.h:141
Function< void(const Notification &)> notifyCallback
Function that will be called on a notification.
Definition FileSystemWatcher.h:148
FolderWatcher(Span< char > subFolderRelativePathsBuffer={})
Constructs a folder watcher.
void setDebugName(const char *debugName)
Sets debug name for AsyncFilePoll used on Windows (used only for debug purposes)
Result stopWatching()
Stop watching this directory.
Notification holding type and path.
Definition FileSystemWatcher.h:120
StringSpan relativePath
Relative path of the file being notified from basePath
Definition FileSystemWatcher.h:122
Operation operation
Notification type.
Definition FileSystemWatcher.h:123
StringSpan basePath
Reference to the watched directory.
Definition FileSystemWatcher.h:121
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:53
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:113
@ 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.
An ascii string used as boolean result. SC_TRY macro forwards errors to caller.
Definition Result.h:13
View over a contiguous sequence of items (pointer + size in elements).
Definition Span.h:29
Pre-sized char array holding enough space to represent a file system path.
Definition StringPath.h:42
An read-only view over a string (to avoid including Strings library when parsing is not needed).
Definition StringSpan.h:37