(original) (raw)

//---------------------------------------------------------------------------------------------------------------------- // SaneCppFileSystemWatcherAsync.h - Sane C++ FileSystemWatcherAsync Library (single file build) //---------------------------------------------------------------------------------------------------------------------- // Dependencies: SaneCppAsync.h, SaneCppFile.h, SaneCppFileSystem.h, SaneCppFileSystemWatcher.h, SaneCppFoundation.h, SaneCppSocket.h, SaneCppThreading.h // Version: release/2025/11 (cf7313e5) // LOC header: 29 (code) + 37 (comments) // LOC implementation: 77 (code) + 23 (comments) // Documentation: https://pagghiu.github.io/SaneCppLibraries // Source Code: https://github.com/pagghiu/SaneCppLibraries //---------------------------------------------------------------------------------------------------------------------- // All copyrights and SPDX information for this library (each amalgamated section has its own copyright attributions): // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT //---------------------------------------------------------------------------------------------------------------------- #include "SaneCppAsync.h" #include "SaneCppFile.h" #include "SaneCppFileSystem.h" #include "SaneCppFileSystemWatcher.h" #include "SaneCppFoundation.h" #include "SaneCppSocket.h" #include "SaneCppThreading.h" #if !defined(SANE_CPP_FILESYSTEMWATCHERASYNC_HEADER) #define SANE_CPP_FILESYSTEMWATCHERASYNC_HEADER 1 //---------------------------------------------------------------------------------------------------------------------- // FileSystemWatcherAsync/FileSystemWatcherAsync.h //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT namespace SC { //! @defgroup group_file_system_watcher_async FileSystem Watcher Async //! @copybrief library_file_system_watcher_async (see @ref library_file_system_watcher_async for more details) //! @addtogroup group_file_system_watcher_async //! @{ /// @brief FileSystemWatcherAsync is an implementation of SC::FileSystemWatcher that uses SC::Async. /// /// The main reason for this class to exist in a dedicated library is to break the dependency of /// SC::FileSystemWatcher from SC::AsyncEventLoop. /// /// Example: /// \snippet Tests/Libraries/FileSystemWatcherAsync/FileSystemWatcherAsyncTest.cpp fileSystemWatcherAsyncSnippet /// /// @note /// This class has been designed to be implemented with SC::AsyncEventLoop but it's probably possible /// implementing another backend using a different event loop library that is capable of providing /// similar abstractions for file polling and event loop wake-up from a foreign thread. struct FileSystemWatcherAsync : public FileSystemWatcher::EventLoopRunner { void init(AsyncEventLoop& loop) { eventLoop = &loop; } protected: AsyncEventLoop* eventLoop = nullptr; #if SC_PLATFORM_APPLE virtual Result appleStartWakeUp() override; virtual void appleSignalEventObject() override; virtual Result appleWakeUpAndWait() override; void onEventLoopNotification(AsyncLoopWakeUp::Result& result); AsyncLoopWakeUp asyncWakeUp = {}; EventObject eventObject = {}; #elif SC_PLATFORM_LINUX virtual Result linuxStartSharedFilePoll() override; virtual Result linuxStopSharedFilePoll() override; void onEventLoopNotification(AsyncFilePoll::Result& result); AsyncFilePoll asyncPoll = {}; #else using FolderWatcher = FileSystemWatcher::FolderWatcher; virtual Result windowsStartFolderFilePoll(FolderWatcher& watcher, void* handle) override; virtual Result windowsStopFolderFilePoll(FolderWatcher& watcher) override; virtual void* windowsGetOverlapped(FolderWatcher& watcher) override; void onEventLoopNotification(AsyncFilePoll::Result& result); Function<void(asyncresult&)> onClose; #endif }; //! @} } // namespace SC #endif // SANE_CPP_FILESYSTEMWATCHERASYNC_HEADER #if defined(SANE_CPP_IMPLEMENTATION) && !defined(SANE_CPP_FILESYSTEMWATCHERASYNC_IMPLEMENTATION) #define SANE_CPP_FILESYSTEMWATCHERASYNC_IMPLEMENTATION 1 //---------------------------------------------------------------------------------------------------------------------- // FileSystemWatcherAsync/FileSystemWatcherAsync.cpp //---------------------------------------------------------------------------------------------------------------------- // Copyright (c) Stefano Cristiano // SPDX-License-Identifier: MIT #if SC_PLATFORM_APPLE SC::Result SC::FileSystemWatcherAsync::appleStartWakeUp() { SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized"); AsyncLoopWakeUp& wakeUp = asyncWakeUp; wakeUp.callback.bind<filesystemwatcherasync, &filesystemwatcherasync::oneventloopnotification="">(*this); return wakeUp.start(*eventLoop, eventObject); } SC::Result SC::FileSystemWatcherAsync::appleWakeUpAndWait() { const Result res = asyncWakeUp.wakeUp(*eventLoop); eventObject.wait(); return res; } void SC::FileSystemWatcherAsync::onEventLoopNotification(AsyncLoopWakeUp::Result& result) { fileSystemWatcher->asyncNotify(nullptr); result.reactivateRequest(true); } void SC::FileSystemWatcherAsync::appleSignalEventObject() { eventObject.signal(); } #elif SC_PLATFORM_LINUX SC::Result SC::FileSystemWatcherAsync::linuxStartSharedFilePoll() { SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized"); FileDescriptor notifyHandle; SC_TRY(notifyHandle.assign(notifyFd)) auto deferDetach = MakeDeferred([¬ifyHandle] { notifyHandle.detach(); }); SC_TRY(eventLoop->associateExternallyCreatedFileDescriptor(notifyHandle)); asyncPoll.callback.bind<filesystemwatcherasync, &filesystemwatcherasync::oneventloopnotification="">(*this); return asyncPoll.start(*eventLoop, notifyFd); } void SC::FileSystemWatcherAsync::onEventLoopNotification(AsyncFilePoll::Result& result) { fileSystemWatcher->asyncNotify(nullptr); result.reactivateRequest(true); } SC::Result SC::FileSystemWatcherAsync::linuxStopSharedFilePoll() { return asyncPoll.stop(*eventLoop); } #else SC::Result SC::FileSystemWatcherAsync::windowsStartFolderFilePoll(FolderWatcher& watcher, void* handle) { SC_TRY_MSG(eventLoop != nullptr and fileSystemWatcher != nullptr, "FileSystemWatcherAsync not initialized"); // TODO: Consider associating / removing file handle with IOCP directly inside AsyncFilePoll FileDescriptor fileHandle; SC_TRY(fileHandle.assign(handle)); Result res = eventLoop->associateExternallyCreatedFileDescriptor(fileHandle); fileHandle.detach(); SC_TRY(res); AsyncFilePoll& asyncPoll = watcher.asyncStorage.reinterpret_as(); placementNew(asyncPoll); asyncPoll.setDebugName("FileSystemWatcherAsync Poll"); asyncPoll.callback.bind<filesystemwatcherasync, &filesystemwatcherasync::oneventloopnotification="">(*this); return asyncPoll.start(*eventLoop, handle); } SC::Result SC::FileSystemWatcherAsync::windowsStopFolderFilePoll(FolderWatcher& watcher) { // This is not strictly needed as file handle is being closed soon after anyway // SC_TRUST_RESULT(eventLoop->removeAllAssociationsFor(fwi.fileHandle)); AsyncFilePoll& asyncPoll = watcher.asyncStorage.reinterpret_as(); onClose = [&watcher](AsyncResult&) { AsyncFilePoll& asyncPoll = watcher.asyncStorage.reinterpret_as(); asyncPoll.~AsyncFilePoll(); }; return asyncPoll.stop(*eventLoop, &onClose); } void* SC::FileSystemWatcherAsync::windowsGetOverlapped(FolderWatcher& watcher) { AsyncFilePoll& asyncPoll = watcher.asyncStorage.reinterpret_as(); return asyncPoll.getOverlappedPtr(); } void SC::FileSystemWatcherAsync::onEventLoopNotification(AsyncFilePoll::Result& result) { SC_COMPILER_WARNING_PUSH_OFFSETOF; auto& storage = reinterpret_cast<decltype(folderwatcher::asyncstorage)&>(result.getAsync()); FolderWatcher& watcher = SC_COMPILER_FIELD_OFFSET(FolderWatcher, asyncStorage, storage); fileSystemWatcher->asyncNotify(&watcher); result.reactivateRequest(true); SC_COMPILER_WARNING_POP; } #endif #endif // SANE_CPP_FILESYSTEMWATCHERASYNC_IMPLEMENTATION </decltype(folderwatcher::asyncstorage)&></filesystemwatcherasync,></filesystemwatcherasync,></filesystemwatcherasync,></void(asyncresult&)>