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

Initial working version of files.watcherExcludes

上级 07c5fe39
......@@ -5,8 +5,8 @@
declare module 'nsfw' {
interface NsfwWatcher {
start(): void;
stop(): void;
start(): any;
stop(): any;
}
interface NsfwWatchingPromise {
......
......@@ -153,7 +153,7 @@ export class FileService implements IFileService {
}
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> {
......
......@@ -8,7 +8,7 @@ import * as path from 'path';
import * as watcher from 'vs/workbench/services/files/node/watcher/common';
import * as nsfw from 'nsfw';
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 { FileChangeType } from 'vs/platform/files/common/files';
......@@ -17,21 +17,36 @@ nsfwActionToRawChangeType[nsfw.actions.CREATED] = FileChangeType.ADDED;
nsfwActionToRawChangeType[nsfw.actions.MODIFIED] = FileChangeType.UPDATED;
nsfwActionToRawChangeType[nsfw.actions.DELETED] = FileChangeType.DELETED;
interface IWatcherObjet {
start(): void;
stop(): void;
}
interface IPathWatcher {
watcher?: {
start(): void;
stop(): void;
};
ready: TPromise<IWatcherObjet>;
watcher?: IWatcherObjet;
ignored: string[];
}
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 _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> {
if (request.verboseLogging) {
if (this._verboseLogging) {
console.log('request', request);
}
......@@ -39,15 +54,21 @@ export class NsfwWatcherService implements IWatcherService {
const fileEventDelayer = new ThrottledDelayer(NsfwWatcherService.FS_EVENT_DELAY);
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) => {
nsfw(request.basePath, events => {
console.log('received events for path: ' + request.basePath);
for (let i = 0; i < events.length; i++) {
const e = events[i];
// 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);
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 {
if (e.action === nsfw.actions.RENAMED) {
// Rename fires when a file's name changes within a single directory
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 });
}
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 });
}
} else {
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({
type: nsfwActionToRawChangeType[e.action],
path: absolutePath
......@@ -78,6 +100,7 @@ export class NsfwWatcherService implements IWatcherService {
// Delay and send buffer
fileEventDelayer.trigger(() => {
const events = undeliveredFileEvents;
console.log('sending events!', events);
undeliveredFileEvents = [];
// Broadcast to clients normalized
......@@ -85,7 +108,7 @@ export class NsfwWatcherService implements IWatcherService {
p(res);
// Logging
if (request.verboseLogging) {
if (this._verboseLogging) {
res.forEach(r => {
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 {
}).then(watcher => {
console.log('watcher ready ' + request.basePath);
this._pathWatchers[request.basePath].watcher = watcher;
return watcher.start();
const startPromise = watcher.start();
startPromise.then(() => readyPromiseCallback(watcher));
return startPromise;
});
});
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 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
// TODO: Move verboseLogging to constructor
// Start watching roots that are not currently being watched
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
if (true) {
console.log(`Set watch roots: start: [${rootsToStartWatching.join(',')}], stop: [${rootsToStopWatching.join(',')}]`);
if (this._verboseLogging) {
console.log(`Set watch roots: start: [${rootsToStartWatching.map(r => r.basePath).join(',')}], stop: [${rootsToStopWatching.join(',')}], changed: [${rootsWithChangedOptions.join(', ')}]`);
}
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) {
rootsToStopWatching.forEach(root => {
this._pathWatchers[root].watcher.stop();
delete this._pathWatchers[root];
});
}
// Stop watching some roots
rootsToStopWatching.forEach(root => {
this._pathWatchers[root].ready.then(watcher => watcher.stop());
delete this._pathWatchers[root];
});
// Start watching some roots
rootsToStartWatching.forEach(root => promises.push(this.watch(root)));
// TODO: Don't watch sub-folders of folders
return TPromise.join(promises).then(() => void 0);
......@@ -142,13 +192,15 @@ export class NsfwWatcherService implements IWatcherService {
* Normalizes a set of root paths by removing any folders that are
* sub-folders of other roots.
*/
protected _normalizeRoots(roots: string[]): string[] {
protected _normalizeRoots(roots: IWatcherRequest[]): IWatcherRequest[] {
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 {
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));
}
}
......@@ -7,9 +7,16 @@
import assert = require('assert');
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 {
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', () => {
......
......@@ -10,11 +10,12 @@ import { TPromise } from 'vs/base/common/winjs.base';
export interface IWatcherRequest {
basePath: string;
ignored: string[];
verboseLogging: boolean;
// verboseLogging: boolean;
}
export interface IWatcherService {
setRoots(roots: string[]): TPromise<void>;
initialize(verboseLogging: boolean): TPromise<void>;
setRoots(roots: IWatcherRequest[]): TPromise<void>;
watch(request: IWatcherRequest): TPromise<void>;
}
......
......@@ -10,6 +10,8 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IWatcherRequest, IWatcherService } from './watcher';
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: string, arg: any): TPromise<any>;
}
......@@ -20,6 +22,7 @@ export class WatcherChannel implements IWatcherChannel {
call(command: string, arg: any): TPromise<any> {
switch (command) {
case 'initialize': return this.service.initialize(arg);
case 'setRoots': return this.service.setRoots(arg);
case 'watch': return this.service.watch(arg);
}
......@@ -31,7 +34,11 @@ export class WatcherChannelClient implements IWatcherService {
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);
}
......
......@@ -11,19 +11,20 @@ import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import uri from 'vs/base/common/uri';
import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common';
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 { normalize } from "path";
import { IConfigurationService } from "vs/platform/configuration/common/configuration";
export class FileWatcher {
private static MAX_RESTARTS = 5;
private service: WatcherChannelClient;
private isDisposed: boolean;
private restartCounter: number;
constructor(
private contextService: IWorkspaceContextService,
private ignored: string[],
private configurationService: IConfigurationService,
private onFileChanges: (changes: FileChangesEvent) => void,
private errorLogger: (msg: string) => void,
private verboseLogging: boolean,
......@@ -48,13 +49,10 @@ export class FileWatcher {
}
);
// Initialize watcher
const channel = getNextTickChannel(client.getChannel<IWatcherChannel>('watcher'));
const service = new WatcherChannelClient(channel);
// 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) => {
this.service = new WatcherChannelClient(channel);
this.service.initialize(this.verboseLogging).then(null, (err) => {
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
}
......@@ -73,14 +71,11 @@ export class FileWatcher {
}
}
}, this.errorLogger);
if (activeRoots.length > 1) {
service.setRoots(activeRoots.map(r => r.fsPath));
}
this.contextService.onDidChangeWorkspaceRoots(() => {
const roots = this.contextService.getWorkspace2().roots;
service.setRoots(roots.map(r => r.fsPath));
});
// Start watching
this.updateRoots();
this.contextService.onDidChangeWorkspaceRoots(() => this.updateRoots());
this.configurationService.onDidUpdateConfiguration(() => this.updateRoots());
return () => {
client.dispose();
......@@ -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 {
// Emit through broadcast service
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册