9#include "DirectoryScanner.h"
12#include "llvm/ADT/STLExtras.h"
13#include "llvm/ADT/StringRef.h"
14#include "llvm/Support/Error.h"
15#include "llvm/Support/Path.h"
16#include <CoreServices/CoreServices.h>
17#include <TargetConditionals.h>
24static void stopFSEventStream(FSEventStreamRef);
49 dispatch_queue_t Queue, FSEventStreamRef EventStream,
52 llvm::StringRef WatchedDirPath)
53 : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
54 WatchedDirPath(WatchedDirPath) {}
56 ~DirectoryWatcherMac()
override {
60 dispatch_sync(Queue, ^{
61 stopFSEventStream(EventStream);
62 EventStream =
nullptr;
65 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
""),
70 dispatch_release(Queue);
74 dispatch_queue_t Queue;
75 FSEventStreamRef EventStream;
77 const std::string WatchedDirPath;
80struct EventStreamContextData {
81 std::string WatchedPath;
84 EventStreamContextData(
85 std::string &&WatchedPath,
88 : WatchedPath(
std::move(WatchedPath)), Receiver(Receiver) {}
91 static void dispose(
const void *ctx) {
92 delete static_cast<const EventStreamContextData *
>(ctx);
97constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
98 kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
99 kFSEventStreamEventFlagMustScanSubDirs;
101constexpr const FSEventStreamEventFlags ModifyingFileEvents =
102 kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
103 kFSEventStreamEventFlagItemModified;
105static void eventStreamCallback(ConstFSEventStreamRef Stream,
106 void *ClientCallBackInfo,
size_t NumEvents,
108 const FSEventStreamEventFlags EventFlags[],
109 const FSEventStreamEventId EventIds[]) {
110 auto *ctx =
static_cast<EventStreamContextData *
>(ClientCallBackInfo);
112 std::vector<DirectoryWatcher::Event> Events;
113 for (
size_t i = 0; i < NumEvents; ++i) {
114 StringRef
Path = ((
const char **)EventPaths)[i];
115 const FSEventStreamEventFlags Flags = EventFlags[i];
117 if (Flags & StreamInvalidatingFlags) {
119 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
""});
121 }
else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
124 if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
125 Path == ctx->WatchedPath) {
127 DirectoryWatcher::Event::EventKind::WatchedDirRemoved,
""});
129 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
""});
134 }
else if (Flags & kFSEventStreamEventFlagItemRemoved) {
135 Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
136 llvm::sys::path::filename(
Path));
138 }
else if (Flags & ModifyingFileEvents) {
140 Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
141 llvm::sys::path::filename(
Path));
143 Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
144 llvm::sys::path::filename(
Path));
151 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
""});
152 llvm_unreachable(
"Unknown FSEvent type.");
155 if (!Events.empty()) {
156 ctx->Receiver(Events,
false);
160FSEventStreamRef createFSEventStream(
163 dispatch_queue_t Queue) {
167 CFMutableArrayRef PathsToWatch = [&]() {
168 CFMutableArrayRef PathsToWatch =
169 CFArrayCreateMutable(
nullptr, 0, &kCFTypeArrayCallBacks);
170 CFStringRef CfPathStr =
171 CFStringCreateWithBytes(
nullptr, (
const UInt8 *)
Path.data(),
172 Path.size(), kCFStringEncodingUTF8,
false);
173 CFArrayAppendValue(PathsToWatch, CfPathStr);
174 CFRelease(CfPathStr);
178 FSEventStreamContext Context = [&]() {
179 std::string RealPath;
182 StringRef
P = llvm::Twine(
Path).toNullTerminatedStringRef(Storage);
183 char Buffer[PATH_MAX];
184 if (::realpath(
P.begin(), Buffer) !=
nullptr)
187 RealPath =
Path.str();
190 FSEventStreamContext Context;
192 Context.info =
new EventStreamContextData(std::move(RealPath), Receiver);
193 Context.retain =
nullptr;
194 Context.release = EventStreamContextData::dispose;
195 Context.copyDescription =
nullptr;
199 FSEventStreamRef Result = FSEventStreamCreate(
200 nullptr, eventStreamCallback, &Context, PathsToWatch,
201 kFSEventStreamEventIdSinceNow, 0.0,
202 kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
203 CFRelease(PathsToWatch);
208void stopFSEventStream(FSEventStreamRef EventStream) {
211 FSEventStreamStop(EventStream);
212 FSEventStreamInvalidate(EventStream);
213 FSEventStreamRelease(EventStream);
219 bool WaitForInitialSync) {
220 dispatch_queue_t Queue =
221 dispatch_queue_create(
"DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
224 llvm::report_fatal_error(
225 "DirectoryWatcher::create can not accept an empty Path.");
227 auto EventStream = createFSEventStream(
Path, Receiver, Queue);
228 assert(EventStream &&
"EventStream expected to be non-null");
230 std::unique_ptr<DirectoryWatcher>
Result =
231 std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver,
Path);
235 const std::string CopiedPath =
Path.str();
242 FSEventStreamSetDispatchQueue(EventStream, Queue);
243 FSEventStreamStart(EventStream);
247 if (WaitForInitialSync) {
248 dispatch_sync(Queue, InitWork);
250 dispatch_async(Queue, InitWork);
262 bool WaitForInitialSync) {
263 return llvm::make_error<llvm::StringError>(
264 "DirectoryWatcher is not implemented for this platform!",
265 llvm::inconvertibleErrorCode());
Provides notifications for file changes in a directory.
static llvm::Expected< std::unique_ptr< DirectoryWatcher > > create(llvm::StringRef Path, std::function< void(llvm::ArrayRef< DirectoryWatcher::Event > Events, bool IsInitial)> Receiver, bool WaitForInitialSync)
llvm fatal_error if
The JSON file list parser is used to communicate input to InstallAPI.
std::optional< sys::fs::file_status > getFileStatus(StringRef Path)
std::vector< std::string > scanDirectory(StringRef Path)
@ Result
The result type of a method or function.
std::vector< DirectoryWatcher::Event > getAsFileEvents(const std::vector< std::string > &Scan)
Create event with EventKind::Added for every element in Scan.
Diagnostic wrappers for TextAPI types for error reporting.