| // Copyright 2016 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <CoreServices/CoreServices.h> |
| #include <jni.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| |
| #include <list> |
| #include <string> |
| |
| namespace { |
| |
| // A structure to pass around the FSEvents info and the list of paths. |
| struct JNIEventsDiffAwareness { |
| // FSEvents run loop (thread) |
| CFRunLoopRef runLoop; |
| |
| // FSEvents stream reference (reference to the listened stream) |
| FSEventStreamRef stream; |
| |
| // If true, fsevents dropped events so we don't know what changed exactly. |
| bool everything_changed; |
| |
| // List of paths that have been changed since last polling. |
| std::list<std::string> paths; |
| |
| // Mutex to protect concurrent accesses to paths and everything_changed. |
| pthread_mutex_t mutex; |
| |
| JNIEventsDiffAwareness() : everything_changed(false) { |
| pthread_mutex_init(&mutex, nullptr); |
| } |
| |
| ~JNIEventsDiffAwareness() { pthread_mutex_destroy(&mutex); } |
| }; |
| |
| // Callback called when an event is reported by the FSEvents API |
| void FsEventsDiffAwarenessCallback(ConstFSEventStreamRef streamRef, |
| void *clientCallBackInfo, size_t numEvents, |
| void *eventPaths, |
| const FSEventStreamEventFlags eventFlags[], |
| const FSEventStreamEventId eventIds[]) { |
| char **paths = static_cast<char **>(eventPaths); |
| |
| JNIEventsDiffAwareness *info = |
| static_cast<JNIEventsDiffAwareness *>(clientCallBackInfo); |
| pthread_mutex_lock(&(info->mutex)); |
| for (size_t i = 0; i < numEvents; i++) { |
| if ((eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) != 0) { |
| // Either we lost events or they were coalesced. Assume everything changed |
| // and give up, which matches the fsevents documentation in that the |
| // caller is expected to rescan the directory contents on its own. |
| info->everything_changed = true; |
| break; |
| } else if ((eventFlags[i] & kFSEventStreamEventFlagItemIsDir) != 0 && |
| (eventFlags[i] & kFSEventStreamEventFlagItemRenamed) != 0) { |
| // A directory was renamed. When this happens, fsevents may or may not |
| // give us individual events about which files changed underneath, which |
| // means we have to rescan the directories in order to know what changed. |
| // |
| // The problem is that we cannot rescan the source of the move to discover |
| // which files "disappeared"... so we have no choice but to rescan |
| // everything. Well, in theory, we could try to track directory inodes and |
| // using those to guess which files within them moved... but that'd be way |
| // too much complexity for this rather-uncommon use case. |
| info->everything_changed = true; |
| break; |
| } else { |
| info->paths.push_back(std::string(paths[i])); |
| } |
| } |
| pthread_mutex_unlock(&(info->mutex)); |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_create( |
| JNIEnv *env, jobject fsEventsDiffAwareness, jobjectArray paths, |
| jdouble latency) { |
| // Create a FSEventStreamContext to pass around (env, fsEventsDiffAwareness) |
| JNIEventsDiffAwareness *info = new JNIEventsDiffAwareness(); |
| |
| FSEventStreamContext context; |
| context.version = 0; |
| context.info = static_cast<void *>(info); |
| context.retain = nullptr; |
| context.release = nullptr; |
| context.copyDescription = nullptr; |
| |
| // Create an CFArrayRef of CFStringRef from the Java array of String |
| jsize length = env->GetArrayLength(paths); |
| CFStringRef *pathsArray = new CFStringRef[length]; |
| for (int i = 0; i < length; i++) { |
| jstring path = (jstring)env->GetObjectArrayElement(paths, i); |
| const char *pathCStr = env->GetStringUTFChars(path, nullptr); |
| pathsArray[i] = |
| CFStringCreateWithCString(nullptr, pathCStr, kCFStringEncodingUTF8); |
| env->ReleaseStringUTFChars(path, pathCStr); |
| } |
| CFArrayRef pathsToWatch = |
| CFArrayCreate(nullptr, (const void **)pathsArray, 1, nullptr); |
| delete[] pathsArray; |
| info->stream = FSEventStreamCreate( |
| nullptr, &FsEventsDiffAwarenessCallback, &context, pathsToWatch, |
| kFSEventStreamEventIdSinceNow, static_cast<CFAbsoluteTime>(latency), |
| kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents); |
| |
| // Save the info pointer to FSEventsDiffAwareness#nativePointer |
| jbyteArray array = env->NewByteArray(sizeof(info)); |
| env->SetByteArrayRegion(array, 0, sizeof(info), |
| reinterpret_cast<const jbyte *>(&info)); |
| jclass clazz = env->GetObjectClass(fsEventsDiffAwareness); |
| jfieldID fid = env->GetFieldID(clazz, "nativePointer", "J"); |
| env->SetLongField(fsEventsDiffAwareness, fid, reinterpret_cast<jlong>(info)); |
| } |
| |
| JNIEventsDiffAwareness *GetInfo(JNIEnv *env, jobject fsEventsDiffAwareness) { |
| jclass clazz = env->GetObjectClass(fsEventsDiffAwareness); |
| jfieldID fid = env->GetFieldID(clazz, "nativePointer", "J"); |
| jlong field = env->GetLongField(fsEventsDiffAwareness, fid); |
| return reinterpret_cast<JNIEventsDiffAwareness *>(field); |
| } |
| |
| } // namespace |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_run( |
| JNIEnv *env, jobject fsEventsDiffAwareness, jobject listening) { |
| JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness); |
| info->runLoop = CFRunLoopGetCurrent(); |
| FSEventStreamScheduleWithRunLoop(info->stream, info->runLoop, |
| kCFRunLoopDefaultMode); |
| FSEventStreamStart(info->stream); |
| |
| jclass countDownLatchClass = env->GetObjectClass(listening); |
| jmethodID countDownMethod = |
| env->GetMethodID(countDownLatchClass, "countDown", "()V"); |
| env->CallVoidMethod(listening, countDownMethod); |
| CFRunLoopRun(); |
| } |
| |
| extern "C" JNIEXPORT jobjectArray JNICALL |
| Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_poll( |
| JNIEnv *env, jobject fsEventsDiffAwareness) { |
| JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness); |
| pthread_mutex_lock(&(info->mutex)); |
| |
| jobjectArray result; |
| if (info->everything_changed) { |
| result = nullptr; |
| } else { |
| jclass classString = env->FindClass("java/lang/String"); |
| result = env->NewObjectArray(info->paths.size(), classString, nullptr); |
| int i = 0; |
| for (auto it = info->paths.begin(); it != info->paths.end(); it++, i++) { |
| env->SetObjectArrayElement(result, i, env->NewStringUTF(it->c_str())); |
| } |
| } |
| |
| info->everything_changed = false; |
| info->paths.clear(); |
| |
| pthread_mutex_unlock(&(info->mutex)); |
| return result; |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_skyframe_MacOSXFsEventsDiffAwareness_doClose( |
| JNIEnv *env, jobject fsEventsDiffAwareness) { |
| JNIEventsDiffAwareness *info = GetInfo(env, fsEventsDiffAwareness); |
| CFRunLoopStop(info->runLoop); |
| FSEventStreamStop(info->stream); |
| FSEventStreamUnscheduleFromRunLoop(info->stream, info->runLoop, |
| kCFRunLoopDefaultMode); |
| FSEventStreamInvalidate(info->stream); |
| FSEventStreamRelease(info->stream); |
| delete info; |
| } |