PathWatcher executes callback when path is changed

This commit is contained in:
Corey Johnson & Nathan Sobo 2012-04-25 13:49:53 -07:00
parent 3f97d98ef9
commit 5aed1b012a
7 changed files with 160 additions and 5 deletions

View File

@ -78,6 +78,7 @@
0487D15D14FEE6D60045E5E3 /* libcef_dll_wrapper2.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0487D12514FEE6D60045E5E3 /* libcef_dll_wrapper2.cc */; };
0487D15F14FEE7880045E5E3 /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 0487D15E14FEE7880045E5E3 /* Resources */; };
0487D16014FEE78E0045E5E3 /* Resources in Copy Chrome Resources */ = {isa = PBXBuildFile; fileRef = 0487D15E14FEE7880045E5E3 /* Resources */; };
048A2FC7154870DC0051715C /* PathWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 048A2FC6154870DC0051715C /* PathWatcher.m */; };
04E1DDDD152A0941001A9D07 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04E1DDDC152A0941001A9D07 /* Sparkle.framework */; };
04E1DDDF152A09C3001A9D07 /* Sparkle.framework in Copy Sparkle Framework */ = {isa = PBXBuildFile; fileRef = 04E1DDDC152A0941001A9D07 /* Sparkle.framework */; };
/* End PBXBuildFile section */
@ -299,6 +300,8 @@
0487D12414FEE6D60045E5E3 /* libcef_dll_wrapper.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = libcef_dll_wrapper.cc; sourceTree = "<group>"; };
0487D12514FEE6D60045E5E3 /* libcef_dll_wrapper2.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = libcef_dll_wrapper2.cc; sourceTree = "<group>"; };
0487D15E14FEE7880045E5E3 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = "<group>"; };
048A2FC5154870DC0051715C /* PathWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PathWatcher.h; sourceTree = "<group>"; };
048A2FC6154870DC0051715C /* PathWatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PathWatcher.m; sourceTree = "<group>"; };
04E1DDDC152A0941001A9D07 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = frameworks/Sparkle.framework; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -405,6 +408,8 @@
0487C93E14FED6090045E5E3 /* AtomController.h */,
0487C93F14FED6090045E5E3 /* AtomController.mm */,
042180E614FF080D00DF25EA /* BrowserDelegate.h */,
048A2FC5154870DC0051715C /* PathWatcher.h */,
048A2FC6154870DC0051715C /* PathWatcher.m */,
0487C94014FED6090045E5E3 /* client_handler.h */,
0487C94114FED6090045E5E3 /* client_handler.mm */,
0487C94314FED6090045E5E3 /* native_handler.h */,
@ -758,6 +763,7 @@
0487CD9614FEE1360045E5E3 /* client_handler.mm in Sources */,
0487CD9714FEE1380045E5E3 /* main.mm in Sources */,
0487CD9814FEE13B0045E5E3 /* native_handler.mm in Sources */,
048A2FC7154870DC0051715C /* PathWatcher.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

12
Atom/src/PathWatcher.h Normal file
View File

@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
typedef void (^WatchCallback)(NSArray *);
@interface PathWatcher : NSObject {
int _kq;
NSMutableDictionary *_callbacksByFileDescriptor;
}
+ (void)watchPath:(NSString *)path callback:(WatchCallback)callback;
@end

103
Atom/src/PathWatcher.m Normal file
View File

@ -0,0 +1,103 @@
#import "PathWatcher.h"
#import <sys/event.h>
#import <sys/time.h>
#import <fcntl.h>
@interface PathWatcher ()
- (void)watchPath:(NSString *)path callback:(WatchCallback)callback;
@end
@implementation PathWatcher
+ (void)watchPath:(NSString *)path callback:(WatchCallback)callback {
static PathWatcher *pathWatcher;
if (!pathWatcher) pathWatcher = [[PathWatcher alloc] init];
[pathWatcher watchPath:path callback:callback];
}
- (void)dealloc {
close(_kq);
for (NSNumber *fdNumber in [_callbacksByFileDescriptor allKeys]) {
close([fdNumber intValue]);
}
[_callbacksByFileDescriptor release];
}
- (id)init {
self = [super init];
_callbacksByFileDescriptor = [[NSMutableDictionary alloc] init];
_kq = kqueue();
if (_kq == -1) {
[NSException raise:@"Could not create kqueue" format:nil];
}
[self performSelectorInBackground:@selector(watch) withObject:NULL];
return self;
}
- (void)watchPath:(NSString *)path callback:(WatchCallback)callback {
struct timespec timeout = { 0, 0 };
struct kevent event;
int fd = open([path fileSystemRepresentation], O_EVTONLY, 0);
if (fd >= 0) {
int filter = EVFILT_VNODE;
int flags = EV_ADD | EV_ENABLE | EV_CLEAR;
int filterFlags = NOTE_ATTRIB | NOTE_WRITE | NOTE_EXTEND | NOTE_RENAME | NOTE_DELETE;
EV_SET(&event, fd, filter, flags, filterFlags, 0, (void *)path);
@synchronized(self) {
NSNumber *fdNumber = [NSNumber numberWithInt:fd];
NSMutableArray *callbacks = [_callbacksByFileDescriptor objectForKey:fdNumber];
if (!callbacks) {
callbacks = [NSMutableArray array];
[_callbacksByFileDescriptor setObject:callbacks forKey:fdNumber];
}
[callbacks addObject:callback];
kevent(_kq, &event, 1, NULL, 0, &timeout);
}
}
}
- (void)watch {
@autoreleasepool {
struct kevent event;
struct timespec timeout = { 5, 0 }; // 5 seconds timeout.
while (true) {
int numberOfEvents = kevent(_kq, NULL, 0, &event, 1, &timeout);
if (numberOfEvents < 0) {
[NSException raise:@"KQueue Error" format:@"error %d", numberOfEvents, nil];
}
if (numberOfEvents == 0) {
continue;
}
NSMutableArray *eventFlags = [NSMutableArray array];
if (event.fflags & NOTE_WRITE) {
[eventFlags addObject:@"modified"];
}
@synchronized(self) {
NSNumber *fdNumber = [NSNumber numberWithInt:event.ident];
for (WatchCallback callback in [_callbacksByFileDescriptor objectForKey:fdNumber]) {
dispatch_async(dispatch_get_main_queue(), ^{
callback(eventFlags);
});
}
}
}
[self release];
}
}
@end

View File

@ -3,6 +3,7 @@
#import "Atom.h"
#import "AtomController.h"
#import "client_handler.h"
#import "PathWatcher.h"
NSString *stringFromCefV8Value(const CefRefPtr<CefV8Value>& value) {
std::string cc_value = value->GetStringValue().ToString();
@ -12,7 +13,7 @@ NSString *stringFromCefV8Value(const CefRefPtr<CefV8Value>& value) {
NativeHandler::NativeHandler() : CefV8Handler() {
m_object = CefV8Value::CreateObject(NULL);
const char *functionNames[] = {"exists", "read", "write", "absolute", "list", "isFile", "isDirectory", "remove", "asyncList", "open", "openDialog", "quit", "writeToPasteboard", "readFromPasteboard", "showDevTools", "newWindow", "saveDialog", "exit"};
const char *functionNames[] = {"exists", "read", "write", "absolute", "list", "isFile", "isDirectory", "remove", "asyncList", "open", "openDialog", "quit", "writeToPasteboard", "readFromPasteboard", "showDevTools", "newWindow", "saveDialog", "exit", "watchPath"};
NSUInteger arrayLength = sizeof(functionNames) / sizeof(const char *);
for (NSUInteger i = 0; i < arrayLength; i++) {
const char *functionName = functionNames[i];
@ -271,6 +272,34 @@ bool NativeHandler::Execute(const CefString& name,
exit(exitStatus);
return true;
}
else if (name == "watchPath") {
NSString *path = stringFromCefV8Value(arguments[0]);
CefRefPtr<CefV8Value> function = arguments[1];
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
WatchCallback callback = ^(NSArray *eventList) {
context->Enter();
CefV8ValueList args;
CefRefPtr<CefV8Value> retval;
CefRefPtr<CefV8Exception> e;
CefRefPtr<CefV8Value> eventObject = CefV8Value::CreateObject(NULL);
for (NSString *event in eventList) {
eventObject->SetValue([event UTF8String], CefV8Value::CreateBool(true), V8_PROPERTY_ATTRIBUTE_NONE);
}
args.push_back(eventObject);
function->ExecuteFunction(function, args, retval, e, true);
context->Exit();
};
[PathWatcher watchPath:path callback:[[callback copy] autorelease]];
return true;
}
return false;
};

View File

@ -260,6 +260,7 @@ describe "TreeView", ->
temporaryFilePath = fs.join(require.resolve('fixtures'), 'temporary')
afterEach ->
console.log "REMOVE" if fs.exists(temporaryFilePath)
fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath)
describe "when a file is added or removed in an expanded directory", ->
@ -268,8 +269,12 @@ describe "TreeView", ->
fs.write temporaryFilePath, 'hi'
expect(rootDirectoryView.entries.find('.entry').length).toBe entriesCountBefore + 1
expect(rootDirectoryView.entries.find('.file:contains(temporary)')).toExist()
waitsFor "file to be added", ->
rootDirectoryView.entries.find('.entry').length == entriesCountBefore + 1
runs ->
expect(rootDirectoryView.entries.find('.entry').length).toBe entriesCountBefore + 1
expect(rootDirectoryView.entries.find('.file:contains(temporary)')).toExist()
describe "when a file is renamed in an expanded directory", ->

View File

@ -34,4 +34,3 @@ class Directory
$native.unwatchPath(@path)
_.extend Directory.prototype, EventEmitter

View File

@ -119,7 +119,8 @@ class DirectoryView extends View
@isExpanded = false
watchEntries: ->
@directory.on "contents-change.#{@directory.path}", => @buildEntries()
@directory.on "contents-change.#{@directory.path}", =>
@buildEntries()
unwatchEntries: ->
@directory.off ".#{@directory.path}"