mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
When a file is moved (via the fs) associated File objects update their path and trigger a 'move' event
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
#import "include/cef_v8.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void (^WatchCallback)(NSArray *);
|
||||
typedef void (^WatchCallback)(NSArray *, NSString *);
|
||||
|
||||
@interface PathWatcher : NSObject {
|
||||
int _kq;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#import <sys/event.h>
|
||||
#import <sys/time.h>
|
||||
#import <sys/param.h>
|
||||
#import <fcntl.h>
|
||||
|
||||
static NSMutableArray *gPathWatchers;
|
||||
@@ -11,6 +12,8 @@ static NSMutableArray *gPathWatchers;
|
||||
- (void)watchFileDescriptor:(int)fd;
|
||||
- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback callbackId:(NSString *)callbackId;
|
||||
- (void)stopWatching;
|
||||
- (void)reassignFileDescriptorFor:(NSString *)path to:(NSString *)newPath;
|
||||
- (bool)isAtomicWrite:(struct kevent)event;
|
||||
@end
|
||||
|
||||
@implementation PathWatcher
|
||||
@@ -194,24 +197,33 @@ static NSMutableArray *gPathWatchers;
|
||||
|
||||
NSNumber *fdNumber = [NSNumber numberWithInt:event.ident];
|
||||
NSMutableArray *eventFlags = [NSMutableArray array];
|
||||
NSString *path = [self pathForFileDescriptor:fdNumber];
|
||||
|
||||
if (event.fflags & NOTE_WRITE) {
|
||||
[eventFlags addObject:@"modified"];
|
||||
}
|
||||
else if ([self isAtomicWrite:event]) {
|
||||
[eventFlags addObject:@"modified"];
|
||||
|
||||
// The fd for the path has changed. Remove references to old fd and
|
||||
// make sure the path and callbacks are linked with new fd.
|
||||
@synchronized(self) {
|
||||
NSDictionary *callbacks = [NSDictionary dictionaryWithDictionary:[_callbacksByFileDescriptor objectForKey:fdNumber]];
|
||||
NSString *path = [self pathForFileDescriptor:fdNumber];
|
||||
|
||||
NSDictionary *callbacks = [NSDictionary dictionaryWithDictionary:[_callbacksByFileDescriptor objectForKey:fdNumber]];
|
||||
[self unwatchPath:path callbackId:nil error:nil];
|
||||
for (NSString *callbackId in [callbacks allKeys]) {
|
||||
[self watchPath:path callback:[callbacks objectForKey:callbackId] callbackId:callbackId];
|
||||
}
|
||||
|
||||
[eventFlags addObject:@"modified"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if (event.fflags & NOTE_RENAME) {
|
||||
[eventFlags addObject:@"moved"];
|
||||
|
||||
char pathBuffer[MAXPATHLEN];
|
||||
fcntl((int)event.ident, F_GETPATH, &pathBuffer);
|
||||
NSString *newPath = [NSString stringWithUTF8String:pathBuffer];
|
||||
[self reassignFileDescriptorFor:path to:newPath];
|
||||
path = newPath;
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
@@ -219,7 +231,7 @@ static NSMutableArray *gPathWatchers;
|
||||
for (NSString *key in callbacks) {
|
||||
WatchCallback callback = [callbacks objectForKey:key];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
callback(eventFlags);
|
||||
callback(eventFlags, path);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -241,4 +253,12 @@ static NSMutableArray *gPathWatchers;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)reassignFileDescriptorFor:(NSString *)path to:(NSString *)newPath {
|
||||
@synchronized(self) {
|
||||
NSNumber *fdNumber = [_fileDescriptorsByPath objectForKey:path];
|
||||
[_fileDescriptorsByPath removeObjectForKey:path];
|
||||
[_fileDescriptorsByPath setObject:fdNumber forKey:newPath];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -304,7 +304,7 @@ bool NativeHandler::Execute(const CefString& name,
|
||||
|
||||
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
|
||||
|
||||
WatchCallback callback = ^(NSArray *eventList) {
|
||||
WatchCallback callback = ^(NSArray *eventList, NSString *path) {
|
||||
context->Enter();
|
||||
|
||||
CefV8ValueList args;
|
||||
@@ -317,6 +317,7 @@ bool NativeHandler::Execute(const CefString& name,
|
||||
}
|
||||
|
||||
args.push_back(eventObject);
|
||||
args.push_back(CefV8Value::CreateString(std::string([path UTF8String], [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding])));
|
||||
function->ExecuteFunction(function, args, retval, e, true);
|
||||
|
||||
context->Exit();
|
||||
|
||||
@@ -2,25 +2,24 @@ File = require 'file'
|
||||
fs = require 'fs'
|
||||
|
||||
describe 'File', ->
|
||||
file = null
|
||||
[path, file] = []
|
||||
|
||||
beforeEach ->
|
||||
path = fs.join(require.resolve('fixtures'), "temp.txt")
|
||||
path = fs.join(require.resolve('fixtures'), "atom-file-test.txt") # Don't put in /tmp because /tmp symlinks to /private/tmp and screws up the rename test
|
||||
fs.remove(path) if fs.exists(path)
|
||||
fs.write(path, "this is old!")
|
||||
file = new File(path)
|
||||
|
||||
afterEach ->
|
||||
file.off()
|
||||
fs.remove(file.getPath()) if fs.exists(file.getPath())
|
||||
fs.remove(path) if fs.exists(path)
|
||||
|
||||
describe "when the contents of the file change", ->
|
||||
it "triggers 'contents-change' event handlers", ->
|
||||
changeHandler = null
|
||||
runs ->
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
file.on 'contents-change', changeHandler
|
||||
fs.write(file.getPath(), "this is new!")
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
file.on 'contents-change', changeHandler
|
||||
fs.write(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
@@ -31,3 +30,46 @@ describe 'File', ->
|
||||
|
||||
waitsFor "second change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
describe "when a file is moved (via the filesystem)", ->
|
||||
newPath = null
|
||||
|
||||
beforeEach ->
|
||||
newPath = fs.join(fs.directory(path), "atom-file-was-moved-test.txt")
|
||||
|
||||
afterEach ->
|
||||
fs.remove(newPath) if fs.exists(newPath)
|
||||
|
||||
it "it updates its path", ->
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'move', moveHandler
|
||||
|
||||
fs.move(path, newPath)
|
||||
|
||||
waitsFor "move event", ->
|
||||
moveHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(file.getPath()).toBe newPath
|
||||
|
||||
it "maintains 'contents-change' events set on previous path", ->
|
||||
moveHandler = null
|
||||
moveHandler = jasmine.createSpy('moveHandler')
|
||||
file.on 'move', moveHandler
|
||||
changeHandler = null
|
||||
changeHandler = jasmine.createSpy('changeHandler')
|
||||
file.on 'contents-change', changeHandler
|
||||
|
||||
fs.move(path, newPath)
|
||||
|
||||
waitsFor "move event", ->
|
||||
moveHandler.callCount > 0
|
||||
|
||||
runs ->
|
||||
expect(changeHandler).not.toHaveBeenCalled()
|
||||
fs.write(file.getPath(), "this is new!")
|
||||
|
||||
waitsFor "change event", ->
|
||||
changeHandler.callCount > 0
|
||||
|
||||
|
||||
@@ -610,14 +610,15 @@ describe "TreeView", ->
|
||||
|
||||
describe "when the directories along the new path don't exist", ->
|
||||
it "creates the target directory before moving the file", ->
|
||||
runs ->
|
||||
newPath = fs.join(rootDirPath, 'new/directory', 'renamed-test-file.txt')
|
||||
moveDialog.miniEditor.setText(newPath)
|
||||
rootView.project.destroy() # Ensure there are no open buffers (renaming a file asynchronously updates the buffer's path, this causes the afterEach block to unwatch the previous path, which no longer exists.)
|
||||
|
||||
moveDialog.trigger 'tree-view:confirm'
|
||||
newPath = fs.join(rootDirPath, 'new/directory', 'renamed-test-file.txt')
|
||||
moveDialog.miniEditor.setText(newPath)
|
||||
|
||||
expect(fs.exists(newPath)).toBeTruthy()
|
||||
expect(fs.exists(filePath)).toBeFalsy()
|
||||
moveDialog.trigger 'tree-view:confirm'
|
||||
|
||||
expect(fs.exists(newPath)).toBeTruthy()
|
||||
expect(fs.exists(filePath)).toBeFalsy()
|
||||
|
||||
describe "when a file or directory already exists at the target path", ->
|
||||
it "shows an error message and does not close the dialog", ->
|
||||
|
||||
@@ -11,6 +11,8 @@ class File
|
||||
constructor: (@path) ->
|
||||
@updateMd5()
|
||||
|
||||
setPath: (@path) ->
|
||||
|
||||
getPath: ->
|
||||
@path
|
||||
|
||||
@@ -27,7 +29,11 @@ class File
|
||||
@unsubscribeFromNativeChangeEvents() if @subscriptionCount() == 0
|
||||
|
||||
subscribeToNativeChangeEvents: ->
|
||||
@watchId = $native.watchPath @path, (eventTypes) =>
|
||||
@watchId = $native.watchPath @path, (eventTypes, path) =>
|
||||
if eventTypes.moved?
|
||||
@setPath(path)
|
||||
@trigger 'move'
|
||||
|
||||
newMd5 = fs.md5ForPath(@getPath())
|
||||
if eventTypes.modified? and newMd5 != @md5
|
||||
@md5 = newMd5
|
||||
|
||||
Reference in New Issue
Block a user