提交 6e780093 编写于 作者: D Daniel Imms

Initial working version of files.watcherExcludes

上级 07c5fe39
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
declare module 'nsfw' { declare module 'nsfw' {
interface NsfwWatcher { interface NsfwWatcher {
start(): void; start(): any;
stop(): void; stop(): any;
} }
interface NsfwWatchingPromise { interface NsfwWatchingPromise {
......
...@@ -153,7 +153,7 @@ export class FileService implements IFileService { ...@@ -153,7 +153,7 @@ export class FileService implements IFileService {
} }
private setupNsfwWorkspceWatching(): void { private setupNsfwWorkspceWatching(): void {
this.toDispose.push(toDisposable(new NsfwWatcherService(this.contextService, this.options.watcherIgnoredPatterns, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging).startWatching())); this.toDispose.push(toDisposable(new NsfwWatcherService(this.contextService, this.configurationService, e => this._onFileChanges.fire(e), this.options.errorLogger, this.options.verboseLogging).startWatching()));
} }
public resolveFile(resource: uri, options?: IResolveFileOptions): TPromise<IFileStat> { public resolveFile(resource: uri, options?: IResolveFileOptions): TPromise<IFileStat> {
......
...@@ -8,7 +8,7 @@ import * as path from 'path'; ...@@ -8,7 +8,7 @@ import * as path from 'path';
import * as watcher from 'vs/workbench/services/files/node/watcher/common'; import * as watcher from 'vs/workbench/services/files/node/watcher/common';
import * as nsfw from 'nsfw'; import * as nsfw from 'nsfw';
import { IWatcherService, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { IWatcherService, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise, ProgressCallback, TValueCallback } from 'vs/base/common/winjs.base';
import { ThrottledDelayer } from 'vs/base/common/async'; import { ThrottledDelayer } from 'vs/base/common/async';
import { FileChangeType } from 'vs/platform/files/common/files'; import { FileChangeType } from 'vs/platform/files/common/files';
...@@ -17,21 +17,36 @@ nsfwActionToRawChangeType[nsfw.actions.CREATED] = FileChangeType.ADDED; ...@@ -17,21 +17,36 @@ nsfwActionToRawChangeType[nsfw.actions.CREATED] = FileChangeType.ADDED;
nsfwActionToRawChangeType[nsfw.actions.MODIFIED] = FileChangeType.UPDATED; nsfwActionToRawChangeType[nsfw.actions.MODIFIED] = FileChangeType.UPDATED;
nsfwActionToRawChangeType[nsfw.actions.DELETED] = FileChangeType.DELETED; nsfwActionToRawChangeType[nsfw.actions.DELETED] = FileChangeType.DELETED;
interface IWatcherObjet {
start(): void;
stop(): void;
}
interface IPathWatcher { interface IPathWatcher {
watcher?: { ready: TPromise<IWatcherObjet>;
start(): void; watcher?: IWatcherObjet;
stop(): void; ignored: string[];
};
} }
export class NsfwWatcherService implements IWatcherService { export class NsfwWatcherService implements IWatcherService {
private static FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms) private static FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms)
private _pathWatchers: { [watchPath: string]: IPathWatcher } = {}; private _pathWatchers: { [watchPath: string]: IPathWatcher } = {};
private _watcherPromise: TPromise<void>;
private _progressCallback: ProgressCallback;
private _verboseLogging: boolean;
public initialize(verboseLogging: boolean): TPromise<void> {
this._verboseLogging = verboseLogging;
this._watcherPromise = new TPromise<void>((c, e, p) => {
this._progressCallback = p;
});
return this._watcherPromise;
}
public watch(request: IWatcherRequest): TPromise<void> { public watch(request: IWatcherRequest): TPromise<void> {
if (request.verboseLogging) { if (this._verboseLogging) {
console.log('request', request); console.log('request', request);
} }
...@@ -39,15 +54,21 @@ export class NsfwWatcherService implements IWatcherService { ...@@ -39,15 +54,21 @@ export class NsfwWatcherService implements IWatcherService {
const fileEventDelayer = new ThrottledDelayer(NsfwWatcherService.FS_EVENT_DELAY); const fileEventDelayer = new ThrottledDelayer(NsfwWatcherService.FS_EVENT_DELAY);
console.log('starting to watch ' + request.basePath); console.log('starting to watch ' + request.basePath);
this._pathWatchers[request.basePath] = {};
let readyPromiseCallback: TValueCallback<IWatcherObjet>;
this._pathWatchers[request.basePath] = {
ready: new TPromise<IWatcherObjet>(c => readyPromiseCallback = c),
ignored: request.ignored
};
const promise = new TPromise<void>((c, e, p) => { const promise = new TPromise<void>((c, e, p) => {
nsfw(request.basePath, events => { nsfw(request.basePath, events => {
console.log('received events for path: ' + request.basePath);
for (let i = 0; i < events.length; i++) { for (let i = 0; i < events.length; i++) {
const e = events[i]; const e = events[i];
// Logging // Logging
if (request.verboseLogging) { if (this._verboseLogging) {
const logPath = e.action === nsfw.actions.RENAMED ? path.join(e.directory, e.oldFile) + ' -> ' + e.newFile : path.join(e.directory, e.file); const logPath = e.action === nsfw.actions.RENAMED ? path.join(e.directory, e.oldFile) + ' -> ' + e.newFile : path.join(e.directory, e.file);
console.log(e.action === nsfw.actions.CREATED ? '[CREATED]' : e.action === nsfw.actions.DELETED ? '[DELETED]' : e.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]', logPath); console.log(e.action === nsfw.actions.CREATED ? '[CREATED]' : e.action === nsfw.actions.DELETED ? '[DELETED]' : e.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]', logPath);
} }
...@@ -57,16 +78,17 @@ export class NsfwWatcherService implements IWatcherService { ...@@ -57,16 +78,17 @@ export class NsfwWatcherService implements IWatcherService {
if (e.action === nsfw.actions.RENAMED) { if (e.action === nsfw.actions.RENAMED) {
// Rename fires when a file's name changes within a single directory // Rename fires when a file's name changes within a single directory
absolutePath = path.join(e.directory, e.oldFile); absolutePath = path.join(e.directory, e.oldFile);
if (!this._isPathIgnored(absolutePath, request.ignored)) { if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.basePath].ignored)) {
undeliveredFileEvents.push({ type: FileChangeType.DELETED, path: absolutePath }); undeliveredFileEvents.push({ type: FileChangeType.DELETED, path: absolutePath });
} }
absolutePath = path.join(e.directory, e.newFile); absolutePath = path.join(e.directory, e.newFile);
if (!this._isPathIgnored(absolutePath, request.ignored)) { if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.basePath].ignored)) {
undeliveredFileEvents.push({ type: FileChangeType.ADDED, path: absolutePath }); undeliveredFileEvents.push({ type: FileChangeType.ADDED, path: absolutePath });
} }
} else { } else {
absolutePath = path.join(e.directory, e.file); absolutePath = path.join(e.directory, e.file);
if (!this._isPathIgnored(absolutePath, request.ignored)) { if (!this._isPathIgnored(absolutePath, this._pathWatchers[request.basePath].ignored)) {
console.log('adding event for path', absolutePath);
undeliveredFileEvents.push({ undeliveredFileEvents.push({
type: nsfwActionToRawChangeType[e.action], type: nsfwActionToRawChangeType[e.action],
path: absolutePath path: absolutePath
...@@ -78,6 +100,7 @@ export class NsfwWatcherService implements IWatcherService { ...@@ -78,6 +100,7 @@ export class NsfwWatcherService implements IWatcherService {
// Delay and send buffer // Delay and send buffer
fileEventDelayer.trigger(() => { fileEventDelayer.trigger(() => {
const events = undeliveredFileEvents; const events = undeliveredFileEvents;
console.log('sending events!', events);
undeliveredFileEvents = []; undeliveredFileEvents = [];
// Broadcast to clients normalized // Broadcast to clients normalized
...@@ -85,7 +108,7 @@ export class NsfwWatcherService implements IWatcherService { ...@@ -85,7 +108,7 @@ export class NsfwWatcherService implements IWatcherService {
p(res); p(res);
// Logging // Logging
if (request.verboseLogging) { if (this._verboseLogging) {
res.forEach(r => { res.forEach(r => {
console.log(' >> normalized', r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]', r.path); console.log(' >> normalized', r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]', r.path);
}); });
...@@ -96,43 +119,70 @@ export class NsfwWatcherService implements IWatcherService { ...@@ -96,43 +119,70 @@ export class NsfwWatcherService implements IWatcherService {
}).then(watcher => { }).then(watcher => {
console.log('watcher ready ' + request.basePath); console.log('watcher ready ' + request.basePath);
this._pathWatchers[request.basePath].watcher = watcher; this._pathWatchers[request.basePath].watcher = watcher;
return watcher.start(); const startPromise = watcher.start();
startPromise.then(() => readyPromiseCallback(watcher));
return startPromise;
}); });
}); });
return promise; return promise;
} }
public setRoots(roots: string[]): TPromise<void> { // TODO: This should probably be the only way to watch a folder
public setRoots(roots: IWatcherRequest[]): TPromise<void> {
const normalizedRoots = this._normalizeRoots(roots); const normalizedRoots = this._normalizeRoots(roots);
const rootsToStartWatching = normalizedRoots.filter(r => !(r in this._pathWatchers));
const rootsToStopWatching = Object.keys(this._pathWatchers).filter(r => normalizedRoots.indexOf(r) === -1);
// TODO: Don't watch inner folders // Start watching roots that are not currently being watched
// TODO: Move verboseLogging to constructor const rootsToStartWatching = normalizedRoots.filter(r => {
return !(r.basePath in this._pathWatchers);
});
// Stop watching roots that don't exist in the new roots
const rootsToStopWatching = Object.keys(this._pathWatchers).filter(r => {
return normalizedRoots.every(normalizedRoot => normalizedRoot.basePath !== r);
});
// TODO: Support updating roots when only the ignored files change
// This should be just a matter of updating the ignored part
const rootsWithChangedOptions = Object.keys(this._pathWatchers).filter(r => {
return normalizedRoots.some(normalizedRoot => {
if (normalizedRoot.basePath !== r) {
return false;
}
const ignored = this._pathWatchers[r].ignored;
// TODO: Improve comments, refactor
if (normalizedRoot.ignored.length !== ignored.length) {
console.log('ignored changed on root: ' + r);
this._pathWatchers[r].ignored = normalizedRoot.ignored;
return true;
}
// Check deep equality
for (let i = 0; i < ignored.length; i++) {
if (normalizedRoot.ignored[i] !== ignored[i]) {
console.log('ignored changed on root: ' + r);
this._pathWatchers[r].ignored = normalizedRoot.ignored;
return true;
}
}
return false;
});
});
// Logging // Logging
if (true) { if (this._verboseLogging) {
console.log(`Set watch roots: start: [${rootsToStartWatching.join(',')}], stop: [${rootsToStopWatching.join(',')}]`); console.log(`Set watch roots: start: [${rootsToStartWatching.map(r => r.basePath).join(',')}], stop: [${rootsToStopWatching.join(',')}], changed: [${rootsWithChangedOptions.join(', ')}]`);
} }
const promises: TPromise<void>[] = []; const promises: TPromise<void>[] = [];
if (rootsToStartWatching.length) {
rootsToStartWatching.forEach(root => {
promises.push(this.watch({
basePath: root,
ignored: [],
// TODO: Inherit from initial request
verboseLogging: true
}));
});
}
if (rootsToStopWatching.length) { // Stop watching some roots
rootsToStopWatching.forEach(root => { rootsToStopWatching.forEach(root => {
this._pathWatchers[root].watcher.stop(); this._pathWatchers[root].ready.then(watcher => watcher.stop());
delete this._pathWatchers[root]; delete this._pathWatchers[root];
}); });
}
// Start watching some roots
rootsToStartWatching.forEach(root => promises.push(this.watch(root)));
// TODO: Don't watch sub-folders of folders // TODO: Don't watch sub-folders of folders
return TPromise.join(promises).then(() => void 0); return TPromise.join(promises).then(() => void 0);
...@@ -142,13 +192,15 @@ export class NsfwWatcherService implements IWatcherService { ...@@ -142,13 +192,15 @@ export class NsfwWatcherService implements IWatcherService {
* Normalizes a set of root paths by removing any folders that are * Normalizes a set of root paths by removing any folders that are
* sub-folders of other roots. * sub-folders of other roots.
*/ */
protected _normalizeRoots(roots: string[]): string[] { protected _normalizeRoots(roots: IWatcherRequest[]): IWatcherRequest[] {
return roots.filter(r => roots.every(other => { return roots.filter(r => roots.every(other => {
return !(r.length > other.length && r.indexOf(other) === 0); return !(r.basePath.length > other.basePath.length && r.basePath.indexOf(other.basePath) === 0);
})); }));
} }
private _isPathIgnored(absolutePath: string, ignored: string[]): boolean { private _isPathIgnored(absolutePath: string, ignored: string[]): boolean {
console.log('is "' + absolutePath + '" ignored? ' + (ignored && ignored.some(ignore => glob.match(ignore, absolutePath))));
console.log(' ignored: ', ignored);
return ignored && ignored.some(ignore => glob.match(ignore, absolutePath)); return ignored && ignored.some(ignore => glob.match(ignore, absolutePath));
} }
} }
...@@ -7,9 +7,16 @@ ...@@ -7,9 +7,16 @@
import assert = require('assert'); import assert = require('assert');
import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService'; import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService';
import { IWatcherRequest } from "vs/workbench/services/files/node/watcher/nsfw/watcher";
class TestNsfwWatcherService extends NsfwWatcherService { class TestNsfwWatcherService extends NsfwWatcherService {
public normalizeRoots(roots: string[]): string[] { return this._normalizeRoots(roots); } public normalizeRoots(roots: string[]): string[] {
// Work with strings as paths to simplify testing
const requests: IWatcherRequest[] = roots.map(r => {
return { basePath: r, ignored: [] };
});
return this._normalizeRoots(requests).map(r => r.basePath);
}
} }
suite('NSFW Watcher Service', () => { suite('NSFW Watcher Service', () => {
......
...@@ -10,11 +10,12 @@ import { TPromise } from 'vs/base/common/winjs.base'; ...@@ -10,11 +10,12 @@ import { TPromise } from 'vs/base/common/winjs.base';
export interface IWatcherRequest { export interface IWatcherRequest {
basePath: string; basePath: string;
ignored: string[]; ignored: string[];
verboseLogging: boolean; // verboseLogging: boolean;
} }
export interface IWatcherService { export interface IWatcherService {
setRoots(roots: string[]): TPromise<void>; initialize(verboseLogging: boolean): TPromise<void>;
setRoots(roots: IWatcherRequest[]): TPromise<void>;
watch(request: IWatcherRequest): TPromise<void>; watch(request: IWatcherRequest): TPromise<void>;
} }
......
...@@ -10,6 +10,8 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; ...@@ -10,6 +10,8 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWatcherRequest, IWatcherService } from './watcher'; import { IWatcherRequest, IWatcherService } from './watcher';
export interface IWatcherChannel extends IChannel { export interface IWatcherChannel extends IChannel {
call(command: 'initialize', verboseLogging: boolean): TPromise<void>;
call(command: 'setRoots', request: IWatcherRequest[]): TPromise<void>;
call(command: 'watch', request: IWatcherRequest): TPromise<void>; call(command: 'watch', request: IWatcherRequest): TPromise<void>;
call(command: string, arg: any): TPromise<any>; call(command: string, arg: any): TPromise<any>;
} }
...@@ -20,6 +22,7 @@ export class WatcherChannel implements IWatcherChannel { ...@@ -20,6 +22,7 @@ export class WatcherChannel implements IWatcherChannel {
call(command: string, arg: any): TPromise<any> { call(command: string, arg: any): TPromise<any> {
switch (command) { switch (command) {
case 'initialize': return this.service.initialize(arg);
case 'setRoots': return this.service.setRoots(arg); case 'setRoots': return this.service.setRoots(arg);
case 'watch': return this.service.watch(arg); case 'watch': return this.service.watch(arg);
} }
...@@ -31,7 +34,11 @@ export class WatcherChannelClient implements IWatcherService { ...@@ -31,7 +34,11 @@ export class WatcherChannelClient implements IWatcherService {
constructor(private channel: IWatcherChannel) { } constructor(private channel: IWatcherChannel) { }
setRoots(roots: string[]): TPromise<void> { initialize(verboseLogging: boolean): TPromise<void> {
return this.channel.call('initialize', verboseLogging);
}
setRoots(roots: IWatcherRequest[]): TPromise<void> {
return this.channel.call('setRoots', roots); return this.channel.call('setRoots', roots);
} }
......
...@@ -11,19 +11,20 @@ import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; ...@@ -11,19 +11,20 @@ import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import uri from 'vs/base/common/uri'; import uri from 'vs/base/common/uri';
import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common';
import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc'; import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc';
import { FileChangesEvent } from 'vs/platform/files/common/files'; import { FileChangesEvent, IFilesConfiguration } from 'vs/platform/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { normalize } from "path"; import { IConfigurationService } from "vs/platform/configuration/common/configuration";
export class FileWatcher { export class FileWatcher {
private static MAX_RESTARTS = 5; private static MAX_RESTARTS = 5;
private service: WatcherChannelClient;
private isDisposed: boolean; private isDisposed: boolean;
private restartCounter: number; private restartCounter: number;
constructor( constructor(
private contextService: IWorkspaceContextService, private contextService: IWorkspaceContextService,
private ignored: string[], private configurationService: IConfigurationService,
private onFileChanges: (changes: FileChangesEvent) => void, private onFileChanges: (changes: FileChangesEvent) => void,
private errorLogger: (msg: string) => void, private errorLogger: (msg: string) => void,
private verboseLogging: boolean, private verboseLogging: boolean,
...@@ -48,13 +49,10 @@ export class FileWatcher { ...@@ -48,13 +49,10 @@ export class FileWatcher {
} }
); );
// Initialize watcher
const channel = getNextTickChannel(client.getChannel<IWatcherChannel>('watcher')); const channel = getNextTickChannel(client.getChannel<IWatcherChannel>('watcher'));
const service = new WatcherChannelClient(channel); this.service = new WatcherChannelClient(channel);
this.service.initialize(this.verboseLogging).then(null, (err) => {
// Start watching
const activeRoots = this.contextService.getWorkspace2().roots;
const basePath: string = normalize(activeRoots[0].fsPath);
service.watch({ basePath, ignored: this.ignored, verboseLogging: this.verboseLogging }).then(null, (err) => {
if (!(err instanceof Error && err.name === 'Canceled' && err.message === 'Canceled')) { if (!(err instanceof Error && err.name === 'Canceled' && err.message === 'Canceled')) {
return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up return TPromise.wrapError(err); // the service lib uses the promise cancel error to indicate the process died, we do not want to bubble this up
} }
...@@ -73,14 +71,11 @@ export class FileWatcher { ...@@ -73,14 +71,11 @@ export class FileWatcher {
} }
} }
}, this.errorLogger); }, this.errorLogger);
if (activeRoots.length > 1) {
service.setRoots(activeRoots.map(r => r.fsPath));
}
this.contextService.onDidChangeWorkspaceRoots(() => { // Start watching
const roots = this.contextService.getWorkspace2().roots; this.updateRoots();
service.setRoots(roots.map(r => r.fsPath)); this.contextService.onDidChangeWorkspaceRoots(() => this.updateRoots());
}); this.configurationService.onDidUpdateConfiguration(() => this.updateRoots());
return () => { return () => {
client.dispose(); client.dispose();
...@@ -88,6 +83,26 @@ export class FileWatcher { ...@@ -88,6 +83,26 @@ export class FileWatcher {
}; };
} }
private updateRoots() {
const roots = this.contextService.getWorkspace2().roots;
console.log('updateRoots');
this.service.setRoots(roots.map(root => {
const configuration = this.configurationService.getConfiguration<IFilesConfiguration>(undefined, {
resource: root
});
let ignored: string[] = [];
console.log(' root: ' + root);
if (configuration.files && configuration.files.watcherExclude) {
console.log(' config: ', configuration.files.watcherExclude);
ignored = Object.keys(configuration.files.watcherExclude).filter(k => !!configuration.files.watcherExclude[k]);
}
return {
basePath: root.fsPath,
ignored
};
}));
}
private onRawFileEvents(events: IRawFileChange[]): void { private onRawFileEvents(events: IRawFileChange[]): void {
// Emit through broadcast service // Emit through broadcast service
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册