mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Fix PathWatcher failures
Now when a file is removed, we always remove its subscriptions and its kevent.
This commit is contained in:
@@ -137,23 +137,10 @@ static NSMutableArray *gPathWatchers;
|
||||
}
|
||||
|
||||
if (callbacks.count == 0) {
|
||||
bool fileExists = access([path fileSystemRepresentation], F_OK) != -1;
|
||||
if (fileExists) {
|
||||
[self removeKeventForPath:path];
|
||||
}
|
||||
[self removeKeventForPath:path];
|
||||
[_callbacksByPath removeObjectForKey:path];
|
||||
}
|
||||
}
|
||||
else {
|
||||
NSString *message = [NSString stringWithFormat:@"Trying to unwatch %@, which we aren't watching", path];
|
||||
NSLog(@"WARNING: %@", message);
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:message, NSLocalizedDescriptionKey, nil];
|
||||
if (error) {
|
||||
NSError *e = [NSError errorWithDomain:@"PathWatcher" code:0 userInfo:userInfo];
|
||||
*error = e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +149,7 @@ static NSMutableArray *gPathWatchers;
|
||||
|
||||
@synchronized(self) {
|
||||
if ([_fileDescriptorsByPath objectForKey:path]) {
|
||||
NSLog(@"we already have a kevent");
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -209,7 +197,6 @@ static NSMutableArray *gPathWatchers;
|
||||
- (void)changePath:(NSString *)path toNewPath:(NSString *)newPath {
|
||||
@synchronized(self) {
|
||||
NSDictionary *callbacks = [NSDictionary dictionaryWithDictionary:[_callbacksByPath objectForKey:path]];
|
||||
[self removeKeventForPath:path];
|
||||
[self unwatchPath:path callbackId:nil error:nil];
|
||||
for (NSString *callbackId in [callbacks allKeys]) {
|
||||
[self watchPath:newPath callback:[callbacks objectForKey:callbackId] callbackId:callbackId];
|
||||
@@ -243,7 +230,6 @@ static NSMutableArray *gPathWatchers;
|
||||
}
|
||||
else if (event.fflags & NOTE_DELETE) {
|
||||
eventFlag = @"remove";
|
||||
[self removeKeventForPath:path];
|
||||
}
|
||||
else if (event.fflags & NOTE_RENAME) {
|
||||
eventFlag = @"move";
|
||||
@@ -256,6 +242,7 @@ static NSMutableArray *gPathWatchers;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NSDictionary *callbacks;
|
||||
@synchronized(self) {
|
||||
@@ -269,9 +256,13 @@ static NSMutableArray *gPathWatchers;
|
||||
}
|
||||
});
|
||||
|
||||
if (event.fflags & NOTE_RENAME) {
|
||||
if ([eventFlag isEqual:@"move"]) {
|
||||
[self changePath:path toNewPath:newPath];
|
||||
}
|
||||
|
||||
if ([eventFlag isEqual:@"remove"]) {
|
||||
[self unwatchPath:path callbackId:nil error:nil];
|
||||
}
|
||||
|
||||
[path release];
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@ describe 'Buffer', ->
|
||||
buffer = new Buffer(path).retain()
|
||||
|
||||
afterEach ->
|
||||
fs.remove(path)
|
||||
buffer.release()
|
||||
buffer = null
|
||||
fs.remove(path) if fs.exists(path)
|
||||
|
||||
it "does not trigger a contents-change event when Atom modifies the file", ->
|
||||
buffer.insert([0,0], "HELLO!")
|
||||
@@ -309,7 +311,7 @@ describe 'Buffer', ->
|
||||
|
||||
describe ".save()", ->
|
||||
saveBuffer = null
|
||||
|
||||
|
||||
afterEach ->
|
||||
saveBuffer.release()
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ describe 'File', ->
|
||||
|
||||
describe "when a file is deleted and the recreated within a small amount of time (git sometimes does this)", ->
|
||||
it "triggers a contents change event if the contents change", ->
|
||||
jasmine.unspy(File.prototype, 'detectResurrectionAfterDelay')
|
||||
jasmine.unspy(window, "setTimeout")
|
||||
|
||||
changeHandler = jasmine.createSpy("file changed")
|
||||
@@ -94,16 +95,23 @@ describe 'File', ->
|
||||
file.on 'contents-change', changeHandler
|
||||
file.on 'remove', removeHandler
|
||||
|
||||
fs.remove(path)
|
||||
fs.write(path, "HE HAS RISEN!")
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
fs.remove(path)
|
||||
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
waits 20
|
||||
runs ->
|
||||
fs.write(path, "HE HAS RISEN!")
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
|
||||
waitsFor "resurrection change event", ->
|
||||
changeHandler.callCount == 1
|
||||
|
||||
runs ->
|
||||
expect(removeHandler).not.toHaveBeenCalled()
|
||||
fs.write(path, "Hallelujah!")
|
||||
changeHandler.reset()
|
||||
|
||||
waitsFor "change event", ->
|
||||
waitsFor "post-resurrection change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
@@ -27,6 +27,7 @@ beforeEach ->
|
||||
spyOn(RootView.prototype, 'updateWindowTitle').andCallFake ->
|
||||
spyOn(window, "setTimeout").andCallFake window.fakeSetTimeout
|
||||
spyOn(window, "clearTimeout").andCallFake window.fakeClearTimeout
|
||||
spyOn(File.prototype, "detectResurrectionAfterDelay").andCallFake -> @detectResurrection()
|
||||
|
||||
afterEach ->
|
||||
delete window.rootView if window.rootView
|
||||
|
||||
@@ -31,23 +31,9 @@ class File
|
||||
afterUnsubscribe: ->
|
||||
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
@watchId = $native.watchPath @path, (eventType, path) =>
|
||||
@handleNativeChangeEvent(eventType, path)
|
||||
|
||||
handleNativeChangeEvent: (eventType, path) ->
|
||||
console.log eventType
|
||||
if eventType is "remove"
|
||||
@unsubscribeFromNativeChangeEvents()
|
||||
detectResurrection = =>
|
||||
if @exists()
|
||||
@subscribeToNativeChangeEvents()
|
||||
@handleNativeChangeEvent("contents-change", path)
|
||||
else
|
||||
@trigger "remove"
|
||||
@off()
|
||||
|
||||
_.delay detectResurrection, 50
|
||||
@detectResurrectionAfterDelay()
|
||||
else if eventType is "move"
|
||||
@setPath(path)
|
||||
@trigger "move"
|
||||
@@ -58,6 +44,21 @@ class File
|
||||
@md5 = newMd5
|
||||
@trigger 'contents-change'
|
||||
|
||||
detectResurrectionAfterDelay: ->
|
||||
_.delay (=> @detectResurrection()), 50
|
||||
|
||||
detectResurrection: ->
|
||||
if @exists()
|
||||
@subscribeToNativeChangeEvents()
|
||||
@handleNativeChangeEvent("contents-change", @getPath())
|
||||
else
|
||||
@trigger "remove"
|
||||
@off()
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
@watchId = $native.watchPath @path, (eventType, path) =>
|
||||
@handleNativeChangeEvent(eventType, path)
|
||||
|
||||
unsubscribeFromNativeChangeEvents: ->
|
||||
$native.unwatchPath(@path, @watchId)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user