Skip to content

Instantly share code, notes, and snippets.

@andreyvit
Created March 1, 2010 09:51
Show Gist options
  • Save andreyvit/318239 to your computer and use it in GitHub Desktop.
Save andreyvit/318239 to your computer and use it in GitHub Desktop.
#!/bin/sh
#*******************************************************************************
# Copyright (c) 2000, 2007 IBM Corporation and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# IBM Corporation - initial API and implementation
#*******************************************************************************
cd `dirname $0`
OUTPUT_DIR=../../../org.eclipse.swt.cocoa.macosx
export OUTPUT_DIR
export MACOSX_DEPLOYMENT_TARGET=10.4
make -f make_macosx.mak $1 $2 $3 $4 $5 $6 $7 $8 $9
package com.yoursway.fsmonitor;
import static com.yoursway.utils.YsCollections.addIfNotNull;
import static com.yoursway.utils.YsPathUtils.isChildOrParent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import com.yoursway.fsmonitor.spi.ChangesDetector;
import com.yoursway.fsmonitor.spi.ChangesListener;
import com.yoursway.fsmonitor.spi.MonitoringRequest;
import com.yoursway.utils.YsPathUtils;
import com.yoursway.utils.annotations.SynchronizedWithMonitorOfField;
import com.yoursway.utils.annotations.UseFromCarbonRunLoopThread;
import com.yoursway.utils.annotations.UsedFromJNI;
public class ChangesDetectorImpl implements ChangesDetector {
static {
System.loadLibrary("ys-fs-monitor-macosx_leopard");
}
private native void initializeNatives();
private native boolean queueSafeReschedulingRequest();
private native long FSEventStreamCreate(String[] paths, long sinceWhen, double latency);
private native void FSEventStreamScheduleWithRunLoop(long streamId);
private native boolean FSEventStreamStart(long streamId);
private native void FSEventStreamStop(long streamId);
private native void FSEventStreamInvalidate(long streamId);
private native void FSEventStreamRelease(long streamId);
private native static void CFRunLoopRun();
class Request implements MonitoringRequest {
private final String monitoredPath;
private final ChangesListener listener;
public Request(File directory, ChangesListener listener) {
if (directory == null)
throw new NullPointerException("directory is null");
if (listener == null)
throw new NullPointerException("listener is null");
try {
directory = directory.getCanonicalFile();
} catch (IOException e) {
directory = directory.getAbsoluteFile();
}
String directoryPath = directory.getPath();
directoryPath = YsPathUtils.removeTrailingSeparator(directoryPath);
this.monitoredPath = directoryPath;
this.listener = listener;
}
public String path() {
return monitoredPath;
}
public void addNotifiersTo(Collection<Runnable> notifiers, String[] paths) {
String monitoredPath = this.monitoredPath;
for (final String path : paths)
if (isChildOrParent(monitoredPath, path))
notifiers.add(new Runnable() {
public void run() {
listener.pathChanged(path);
}
});
}
public void dispose() {
Collection<String> paths;
long changeId;
synchronized (activeRequests) {
activeRequests.remove(this);
paths = calculateActivePaths();
changeId = activeRequestsChangeCount++;
}
schedule(paths, changeId);
}
}
@UseFromCarbonRunLoopThread
private long lastSeenEventId = -1;
@SuppressWarnings("unused")
@UsedFromJNI
private long runLoopHandle = 0;
/**
* A collection of currently active (non-disposed) monitoring requests.
*/
@SynchronizedWithMonitorOfField("activeRequests")
private Collection<Request> activeRequests = new HashSet<Request>();
@SynchronizedWithMonitorOfField("activeRequests")
private long activeRequestsChangeCount = 0;
/**
* In <code>ACTIVE</code> state, the paths that are currently being
* monitored by FSEventStream.
*
* In <code>RESCHEDULING</code> state, the paths that will be monitored by
* FSEventStream once the rescheduling is complete.
*/
@SynchronizedWithMonitorOfField("stateLock")
private Collection<String> currentlyMonitoredPaths = new HashSet<String>();
/**
* Used to prevent later changes from being lost due to a race condition
* that might occur because we are using two separate locks.
*/
@SynchronizedWithMonitorOfField("stateLock")
private long lastScheduledChangedId = -1;
@SynchronizedWithMonitorOfField("stateLock")
private long streamId;
@SynchronizedWithMonitorOfField("stateLock")
private State state = State.INACTIVE;
@SynchronizedWithMonitorOfField("stateLock")
private boolean isScheduled = false;
private Object stateLock = new Object();
public ChangesDetectorImpl() {
initializeNatives();
}
public MonitoringRequest monitor(File directory, ChangesListener listener) {
Request newRequest = new Request(directory, listener);
Collection<String> paths;
long changeId;
synchronized (activeRequests) {
activeRequests.add(newRequest);
paths = calculateActivePaths();
changeId = activeRequestsChangeCount++;
}
schedule(paths, changeId);
return newRequest;
}
private Collection<String> calculateActivePaths() {
Collection<String> paths;
paths = new HashSet<String>(activeRequests.size());
for (Request request : activeRequests)
addIfNotNull(paths, request.path());
return paths;
}
void schedule(Collection<String> newPaths, long changeId) {
synchronized (stateLock) {
if (!state.canChangeToAnotherState())
return;
if (changeId < lastScheduledChangedId)
return; // prevent later changes from being lost
lastScheduledChangedId = changeId;
if (newPaths.equals(currentlyMonitoredPaths))
return;
currentlyMonitoredPaths = newPaths;
if (!state.shouldInitiateRescheduling())
return;
state = State.RESCHEDULING;
}
scheduleRestart();
}
private void scheduleRestart() {
if (!queueSafeReschedulingRequest())
handleSafeToReschedule();
}
@UsedFromJNI
void handleChange(String[] paths, long[] eventIds) {
// System.out.println("CHANGE!");
// for (int i = 0; i < eventIds.length; i++) {
// String path = paths[i];
// long id = eventIds[i];
// if (id > lastSeenEventId)
// lastSeenEventId = id;
// System.out.println(" #" + id + " - " + path);
// }
// System.out.flush();
for (int i = 0; i < paths.length; i++)
paths[i] = removeTrailingSeparator(paths[i]);
// minimize the time we spend inside the synchronized area
Collection<Runnable> notifiers = new ArrayList<Runnable>();
synchronized (activeRequests) {
for (Request request : activeRequests)
request.addNotifiersTo(notifiers, paths);
}
for (Runnable runnable : notifiers)
runnable.run();
}
@UsedFromJNI
void handleSafeToReschedule() {
synchronized (state) {
if (isScheduled) {
FSEventStreamStop(streamId);
// System.out.println("FSEventStream stopped.");
FSEventStreamInvalidate(streamId);
// System.out.println("FSEventStream invalidated.");
FSEventStreamRelease(streamId);
System.out.println("FSEventStream disposed.");
isScheduled = false;
}
if (currentlyMonitoredPaths.isEmpty() || !state.canActivate()) {
if (state.canChangeToAnotherState())
state = State.INACTIVE;
} else {
String[] paths = currentlyMonitoredPaths.toArray(new String[currentlyMonitoredPaths.size()]);
streamId = FSEventStreamCreate(paths, lastSeenEventId, 1.0);
if (streamId == 0)
throw new RuntimeException("Cannot create FSEventStream");
// System.out.println("FSEventStream created.");
FSEventStreamScheduleWithRunLoop(streamId);
// System.out.println("FSEventStream scheduled.");
if (!FSEventStreamStart(streamId))
throw new RuntimeException("Cannot start FSEventStream");
System.out.println("FSEventStream running...");
isScheduled = true;
if (state.canChangeToAnotherState())
state = State.ACTIVE;
}
}
}
public static void main(String[] args) {
ChangesDetectorImpl detector = new ChangesDetectorImpl();
detector.monitor(new File("/Users/andreyvit"), new ChangesListener() {
public void pathChanged(String path) {
System.out.println("Changed: " + path);
}
});
CFRunLoopRun();
detector.dispose();
}
public void dispose() {
synchronized(state) {
state = State.DISPOSED;
}
scheduleRestart();
}
/**
* @deprecated Use {@link YsPathUtils#removeTrailingSeparator(String)} instead
*/
public static String removeTrailingSeparator(String directoryPath) {
return YsPathUtils.removeTrailingSeparator(directoryPath);
}
}
#*******************************************************************************
# Copyright (c) 2000, 2007 IBM Corporation and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# IBM Corporation - initial API and implementation
#*******************************************************************************
# Makefile for SWT libraries on Cocoa/Mac
# include make_common.mak
maj_ver=1
min_ver=0
OUTPUT_DIR=../
SWT_PREFIX=ys-fs-monitor
WS_PREFIX=macosx_leopard
SWT_VERSION=$(maj_ver)$(min_ver)
SWT_LIB=lib$(SWT_PREFIX)-$(WS_PREFIX).jnilib
# Uncomment for Native Stats tool
#NATIVE_STATS = -DNATIVE_STATS
#SWT_DEBUG = -g
ARCHS = -arch i386 -arch ppc
CFLAGS = -c -xobjective-c -Wall $(ARCHS) -DSWT_VERSION=$(SWT_VERSION) $(NATIVE_STATS) $(SWT_DEBUG) -DUSE_ASSEMBLER -DCOCOA \
-I /System/Library/Frameworks/JavaVM.framework/Headers \
-I /System/Library/Frameworks/Cocoa.framework/Headers
LFLAGS = -bundle $(ARCHS) -framework JavaVM -framework Cocoa -framework Carbon -framework CoreServices -framework WebKit
SWT_OBJECTS = os.o
all: $(SWT_LIB)
.c.o:
cc $(CFLAGS) $*.c
$(SWT_LIB): $(SWT_OBJECTS)
cc -o $(SWT_LIB) $(LFLAGS) $(SWT_OBJECTS)
install: all
cp *.jnilib $(OUTPUT_DIR)
clean:
rm -f *.jnilib *.o
/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
#include "os.h"
#include "os_stats.h"
#define OS_NATIVE(func) Java_com_yoursway_fsmonitor_ChangesDetectorImpl_##func
typedef struct {
JavaVM *vm;
jobject that;
jmethodID callback_method_id;
} OurContext;
JNIEnv *get_env(JavaVM *vm) {
JNIEnv *env;
(*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4);
return env;
}
void OurContextRelease(void *clientCallBackInfo) {
JNIEnv *env = get_env(((OurContext *) clientCallBackInfo)->vm);
jobject that = ((OurContext *) clientCallBackInfo)->that;
(*env)->DeleteGlobalRef(env, that);
free(clientCallBackInfo);
}
void OurFSEventStreamCallback(ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
JNIEnv *env = get_env(((OurContext *) clientCallBackInfo)->vm);
jobject that = ((OurContext *) clientCallBackInfo)->that;
jmethodID callback_method_id = ((OurContext *) clientCallBackInfo)->callback_method_id;
jclass string_class = (*env)->FindClass(env, "java/lang/String");
jobjectArray paths_array = (*env)->NewObjectArray(env, numEvents, string_class, NULL);
jlongArray eventids_array = (*env)->NewLongArray(env, numEvents);
int i;
for (i = 0; i < numEvents; i++) {
(*env)->SetLongArrayRegion(env, eventids_array, 0, numEvents, (jlong*) eventIds);
jstring path = (*env)->NewStringUTF(env, ((const char**) eventPaths)[i]);
(*env)->SetObjectArrayElement(env, paths_array, i, path);
}
(*env)->CallVoidMethod(env, that, callback_method_id, paths_array, eventids_array);
if ((*env)->ExceptionOccurred(env)) {
fprintf(stderr, "* java exception occurred\n");
(*env)->ExceptionDescribe(env);
fflush(stdout);
}
}
JNIEXPORT jlong JNICALL OS_NATIVE(FSEventStreamCreate)
(JNIEnv *env, jobject that, jarray paths, jlong since_when, jdouble latency)
{
OurContext *our_context = (OurContext *) malloc(sizeof(OurContext));
(*env)->GetJavaVM(env, &our_context->vm);
our_context->that = (*env)->NewGlobalRef(env, that);
jclass klass = (*env)->GetObjectClass(env, that);
our_context->callback_method_id = (*env)->GetMethodID(env, klass, "handleChange", "([Ljava/lang/String;[J)V");
if (NULL == our_context->callback_method_id) {
free(our_context);
return 0;
}
int i;
FSEventStreamEventId since_when_id = (since_when < 0 ? kFSEventStreamEventIdSinceNow : since_when);
jsize path_count = (*env)->GetArrayLength(env, paths);
CFStringRef refs_array[path_count];
for (i = 0; i < path_count; i++) {
jstring path = (*env)->GetObjectArrayElement(env, paths, i);
const char *utf = (*env)->GetStringUTFChars(env, path, NULL);
refs_array[i] = CFStringCreateWithCString(kCFAllocatorDefault, utf, kCFStringEncodingUTF8);
(*env)->ReleaseStringUTFChars(env, path, utf);
}
CFArrayRef paths_array = CFArrayCreate(kCFAllocatorDefault, (const void**) &refs_array, path_count, &kCFTypeArrayCallBacks);
for (i = 0; i < path_count; i++)
CFRelease(refs_array[i]);
FSEventStreamContext context = {0};
context.info = our_context;
context.release = &OurContextRelease;
FSEventStreamRef stream = FSEventStreamCreate(kCFAllocatorDefault, &OurFSEventStreamCallback,
&context, paths_array, since_when_id, latency, kFSEventStreamCreateFlagNoDefer);
CFRelease(paths_array);
return (jlong) stream;
}
JNIEXPORT void JNICALL OS_NATIVE(FSEventStreamScheduleWithRunLoop) (JNIEnv *env, jobject that, jlong stream_id) {
jclass that_class = (*env)->GetObjectClass(env, that);
jfieldID loop_field = (*env)->GetFieldID(env, that_class, "runLoopHandle", "J");
CFRunLoopRef loop = (CFRunLoopRef) (*env)->GetLongField(env, that, loop_field);
FSEventStreamScheduleWithRunLoop((FSEventStreamRef) stream_id, loop, kCFRunLoopCommonModes);
}
JNIEXPORT jboolean JNICALL OS_NATIVE(FSEventStreamStart) (JNIEnv *env, jobject that, jlong stream_id) {
return FSEventStreamStart((FSEventStreamRef) stream_id) ? true : false;
}
JNIEXPORT void JNICALL OS_NATIVE(FSEventStreamStop) (JNIEnv *env, jobject that, jlong stream_id) {
FSEventStreamStop((FSEventStreamRef) stream_id);
}
JNIEXPORT void JNICALL OS_NATIVE(FSEventStreamInvalidate) (JNIEnv *env, jobject that, jlong stream_id) {
FSEventStreamInvalidate((FSEventStreamRef) stream_id);
}
JNIEXPORT void JNICALL OS_NATIVE(FSEventStreamRelease) (JNIEnv *env, jobject that, jlong stream_id) {
FSEventStreamRelease((FSEventStreamRef) stream_id);
}
JNIEXPORT void JNICALL OS_NATIVE(CFRunLoopRun) (JNIEnv *env, jclass that) {
CFRunLoopRun();
}
void OurTimerCallback(CFRunLoopTimerRef timer, void *info) {
JNIEnv *env = get_env(((OurContext *) info)->vm);
jobject that = ((OurContext *) info)->that;
jmethodID callback_method_id = ((OurContext *) info)->callback_method_id;
(*env)->CallVoidMethod(env, that, callback_method_id);
if ((*env)->ExceptionOccurred(env)) {
fprintf(stderr, "* java exception occurred\n");
(*env)->ExceptionDescribe(env);
fflush(stdout);
}
}
JNIEXPORT void JNICALL OS_NATIVE(initializeNatives) (JNIEnv *env, jobject that) {
CFRunLoopRef loop = CFRunLoopGetCurrent();
jclass that_class = (*env)->GetObjectClass(env, that);
jfieldID loop_field = (*env)->GetFieldID(env, that_class, "runLoopHandle", "J");
(*env)->SetLongField(env, that, loop_field, (jlong) loop);
}
JNIEXPORT jboolean JNICALL OS_NATIVE(queueSafeReschedulingRequest) (JNIEnv *env, jobject that) {
OurContext *our_context = (OurContext *) malloc(sizeof(OurContext));
(*env)->GetJavaVM(env, &our_context->vm);
our_context->that = (*env)->NewGlobalRef(env, that);
jclass klass = (*env)->GetObjectClass(env, that);
our_context->callback_method_id = (*env)->GetMethodID(env, klass, "handleSafeToReschedule", "()V");
if (NULL == our_context->callback_method_id) {
free(our_context);
return 0;
}
jclass that_class = (*env)->GetObjectClass(env, that);
jfieldID loop_field = (*env)->GetFieldID(env, that_class, "runLoopHandle", "J");
CFRunLoopRef loop = (CFRunLoopRef) (*env)->GetLongField(env, that, loop_field);
CFRunLoopTimerContext context = {0};
context.info = our_context;
context.release = &OurContextRelease;
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &OurTimerCallback, &context);
CFRunLoopAddTimer(loop, timer, kCFRunLoopCommonModes);
return true;
}
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
#ifndef INC_os_H
#define INC_os_H
/*#define NDEBUG*/
#include "jni.h"
/* #include <Cocoa/Cocoa.h> */
#include <CoreServices/CoreServices.h>
/* #import <objc/objc-runtime.h> */
#ifndef __i386__
#define objc_msgSend_fpret objc_msgSend
#endif
#endif /* INC_os_H */
#ifndef NATIVE_STATS
#define OS_NATIVE_ENTER(env, that, func) \
@try {
#define OS_NATIVE_EXIT(env, that, func) \
} \
@catch (NSException *nsx) { \
jclass threadClass = (*env)->FindClass(env, "java/lang/Thread"); \
jmethodID dumpStackID = (*env)->GetStaticMethodID(env, threadClass, "dumpStack", "()V"); \
if (dumpStackID != NULL) (*env)->CallStaticVoidMethod(env, threadClass, dumpStackID, 0); \
@throw; \
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment