diff --git a/Atom/src/PathWatcher.h b/Atom/src/PathWatcher.h index 00fdd302c..4aac7786e 100644 --- a/Atom/src/PathWatcher.h +++ b/Atom/src/PathWatcher.h @@ -8,6 +8,7 @@ typedef void (^WatchCallback)(NSArray *); NSMutableDictionary *_callbacksByFileDescriptor; } -+ (void)watchPath:(NSString *)path callback:(WatchCallback)callback; ++ (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback; ++ (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId; @end diff --git a/Atom/src/PathWatcher.m b/Atom/src/PathWatcher.m index c4ee49c1b..3024d4e7b 100644 --- a/Atom/src/PathWatcher.m +++ b/Atom/src/PathWatcher.m @@ -4,18 +4,28 @@ #import #import + + @interface PathWatcher () -- (void)watchPath:(NSString *)path callback:(WatchCallback)callback; +- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback; - (void)watchFileDescriptor:(int)fd; +- (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId; @end @implementation PathWatcher -+ (void)watchPath:(NSString *)path callback:(WatchCallback)callback { - static PathWatcher *pathWatcher; - ++ (id)instance { + static PathWatcher *pathWatcher; if (!pathWatcher) pathWatcher = [[PathWatcher alloc] init]; - [pathWatcher watchPath:path callback:callback]; + return pathWatcher; +} + ++ (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback { + return [[self instance] watchPath:path callback:callback]; +} + ++ (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId { + return [[self instance] unwatchPath:path callbackId:callbackId]; } - (void)dealloc { @@ -41,35 +51,47 @@ return self; } -- (void)watchPath:(NSString *)path callback:(WatchCallback)callback { - NSLog(@"Watching path %@", path); - +- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback { path = [path stringByStandardizingPath]; + NSString *callbackId; @synchronized(self) { NSNumber *fdNumber = [_fileDescriptorsByPath objectForKey:path]; if (!fdNumber) { int fd = open([path fileSystemRepresentation], O_EVTONLY, 0); - if (fd < 0) return; // TODO: Decide what to do here + if (fd < 0) return nil; // TODO: Decide what to do here [self watchFileDescriptor:fd]; fdNumber = [NSNumber numberWithInt:fd]; [_fileDescriptorsByPath setObject:fdNumber forKey:path]; } - NSMutableArray *callbacks = [_callbacksByFileDescriptor objectForKey:fdNumber]; + NSMutableDictionary *callbacks = [_callbacksByFileDescriptor objectForKey:fdNumber]; if (!callbacks) { - callbacks = [NSMutableArray array]; + callbacks = [NSMutableDictionary dictionary]; [_callbacksByFileDescriptor setObject:callbacks forKey:fdNumber]; } - [callbacks addObject:callback]; + callbackId = [[NSProcessInfo processInfo] globallyUniqueString]; + [callbacks setObject:callback forKey:callbackId]; + } + + return callbackId; +} + +- (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId { + @synchronized(self) { + NSNumber *fdNumber = [_fileDescriptorsByPath objectForKey:path]; + if (!fdNumber) return; + + NSMutableDictionary *callbacks = [_callbacksByFileDescriptor objectForKey:fdNumber]; + if (!callbacks) return; + + [callbacks removeObjectForKey:callbackId]; } } - (void)watchFileDescriptor:(int)fd { - NSLog(@"Watching fd %d", fd); - struct timespec timeout = { 0, 0 }; struct kevent event; int filter = EVFILT_VNODE; @@ -80,8 +102,6 @@ } - (void)watch { - NSLog(@"kicking off watch"); - @autoreleasepool { struct kevent event; struct timespec timeout = { 5, 0 }; // 5 seconds timeout. @@ -98,8 +118,6 @@ NSMutableArray *eventFlags = [NSMutableArray array]; - NSLog(@"flags are: %d, fd is: %d", event.fflags, (int)event.ident); - if (event.fflags & NOTE_WRITE) { [eventFlags addObject:@"modified"]; } @@ -107,7 +125,9 @@ @synchronized(self) { NSNumber *fdNumber = [NSNumber numberWithInt:event.ident]; - for (WatchCallback callback in [_callbacksByFileDescriptor objectForKey:fdNumber]) { + NSDictionary *callbacks = [_callbacksByFileDescriptor objectForKey:fdNumber]; + for (NSString *key in callbacks) { + WatchCallback callback = [callbacks objectForKey:key]; dispatch_async(dispatch_get_main_queue(), ^{ callback(eventFlags); }); diff --git a/Atom/src/native_handler.mm b/Atom/src/native_handler.mm index 09ee0d5ff..18141a10d 100644 --- a/Atom/src/native_handler.mm +++ b/Atom/src/native_handler.mm @@ -13,7 +13,7 @@ NSString *stringFromCefV8Value(const CefRefPtr& 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", "watchPath"}; + const char *functionNames[] = {"exists", "read", "write", "absolute", "list", "isFile", "isDirectory", "remove", "asyncList", "open", "openDialog", "quit", "writeToPasteboard", "readFromPasteboard", "showDevTools", "newWindow", "saveDialog", "exit", "watchPath", "unwatchPath"}; NSUInteger arrayLength = sizeof(functionNames) / sizeof(const char *); for (NSUInteger i = 0; i < arrayLength; i++) { const char *functionName = functionNames[i]; @@ -300,6 +300,13 @@ bool NativeHandler::Execute(const CefString& name, return true; } + else if (name == "unwatchPath") { + NSString *path = stringFromCefV8Value(arguments[0]); + NSString *callbackId = stringFromCefV8Value(arguments[1]); + [PathWatcher unwatchPath:path callbackId:callbackId]; + + return true; + } return false; }; \ No newline at end of file diff --git a/spec/app/directory-spec.coffee b/spec/app/directory-spec.coffee index a0352eca8..531c7ac25 100644 --- a/spec/app/directory-spec.coffee +++ b/spec/app/directory-spec.coffee @@ -7,7 +7,7 @@ describe "Directory", -> beforeEach -> directory = new Directory(require.resolve('fixtures')) - describe "when the contents of the directory change on disk", -> + fdescribe "when the contents of the directory change on disk", -> temporaryFilePath = null beforeEach -> @@ -17,7 +17,7 @@ describe "Directory", -> afterEach -> fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath) - fit "triggers 'contents-change' event handlers", -> + it "triggers 'contents-change' event handlers", -> changeHandler = null runs -> @@ -33,3 +33,32 @@ describe "Directory", -> waitsFor "second change", -> changeHandler.callCount > 0 + fdescribe "when the directory unsubscribes from events", -> + temporaryFilePath = null + + beforeEach -> + temporaryFilePath = fs.join(directory.path, 'temporary') + fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath) + + afterEach -> + fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath) + + it "no longer triggers events", -> + changeHandler = null + + runs -> + changeHandler = jasmine.createSpy('changeHandler') + directory.on 'contents-change', changeHandler + fs.write(temporaryFilePath, '') + + waitsFor "first change", -> changeHandler.callCount > 0 + + runs -> + changeHandler.reset() + directory.unsubscribeFromNativeChangeEvents() + + ticks = 0 + waitsFor "100 ticks", -> ticks++ < 100 + + runs -> + expect(changeHandler.callCount).toBe 0 diff --git a/spec/extensions/tree-view-spec.coffee b/spec/extensions/tree-view-spec.coffee index 5d411fa7e..8fd055109 100644 --- a/spec/extensions/tree-view-spec.coffee +++ b/spec/extensions/tree-view-spec.coffee @@ -260,11 +260,10 @@ 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", -> - fit "updates the directory view to display the directory's new contents", -> + it "updates the directory view to display the directory's new contents", -> entriesCountBefore = rootDirectoryView.entries.find('.entry').length fs.write temporaryFilePath, 'hi' diff --git a/src/app/directory.coffee b/src/app/directory.coffee index 784f3510f..6179e4fb2 100644 --- a/src/app/directory.coffee +++ b/src/app/directory.coffee @@ -27,10 +27,10 @@ class Directory @unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0 subscribeToNativeChangeEvents: -> - $native.watchPath @path, (eventTypes) => + @watchId = $native.watchPath @path, (eventTypes) => @trigger 'contents-change' if eventTypes.modified? unsubscribeFromNativeChangeEvents: -> - $native.unwatchPath(@path) + $native.unwatchPath(@path, @watchId) _.extend Directory.prototype, EventEmitter