files2 - extract nodejs watcher to its own service

This commit is contained in:
Benjamin Pasero
2019-04-03 12:42:37 +02:00
parent dca554cf3b
commit 375face31f
2 changed files with 127 additions and 62 deletions
@@ -6,22 +6,22 @@
import { mkdir, open, close, read, write } from 'fs';
import { promisify } from 'util';
import { IDisposable, Disposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, FileChangeType } from 'vs/platform/files/common/files';
import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { statLink, readdir, unlink, move, copy, readFile, writeFile, fileExists, truncate, rimraf, RimRafMode } from 'vs/base/node/pfs';
import { watchFolder, watchFile } from 'vs/base/node/watcher';
import { normalize, basename, dirname } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/extpath';
import { retry, ThrottledDelayer } from 'vs/base/common/async';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { localize } from 'vs/nls';
import { IDiskFileChange, normalizeFileChanges, toFileChanges } from 'vs/workbench/services/files2/node/watcher/normalizer';
import { IDiskFileChange, toFileChanges } from 'vs/workbench/services/files2/node/watcher/normalizer';
import { FileWatcher as UnixWatcherService } from 'vs/workbench/services/files2/node/watcher/unix/watcherService';
import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files2/node/watcher/win32/watcherService';
import { FileWatcher as NsfwWatcherService } from 'vs/workbench/services/files2/node/watcher/nsfw/watcherService';
import { FileWatcher as NodeJSWatcherService } from 'vs/workbench/services/files2/node/watcher/nodejs/watcherService';
export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider {
@@ -314,9 +314,6 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
private _onDidChangeFile: Emitter<IFileChange[]> = this._register(new Emitter<IFileChange[]>());
get onDidChangeFile(): Event<IFileChange[]> { return this._onDidChangeFile.event; }
private nonRecursiveFileChangesDelayer: ThrottledDelayer<void> = this._register(new ThrottledDelayer<void>(50));
private nonRecursiveFileChangesBuffer: IDiskFileChange[] = [];
private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService;
private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = [];
private recursiveWatchRequestDelayer: ThrottledDelayer<void> = this._register(new ThrottledDelayer<void>(0));
@@ -406,62 +403,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
}
private watchNonRecursive(resource: URI): IDisposable {
let disposed = false;
let disposable = toDisposable(() => disposed = true);
this.stat(resource).then(fileStat => {
if (disposed) {
return;
}
// Watch Folder
if (fileStat.type === FileType.Directory) {
disposable = watchFolder(resource.fsPath, (eventType, path) => {
this.onNonRecursiveFileChange({ type: eventType === 'changed' ? FileChangeType.UPDATED : eventType === 'added' ? FileChangeType.ADDED : FileChangeType.DELETED, path });
}, error => this.logService.error(error));
}
// Watch File
else {
disposable = watchFile(resource.fsPath, (eventType, path) => {
this.onNonRecursiveFileChange({ type: eventType === 'changed' ? FileChangeType.UPDATED : FileChangeType.DELETED, path });
}, error => this.logService.error(error));
}
}, error => this.logService.error(error));
return toDisposable(() => dispose(disposable));
}
private onNonRecursiveFileChange(event: IDiskFileChange): void {
// Add to buffer
this.nonRecursiveFileChangesBuffer.push(event);
// Logging
if (this.logService.getLevel() === LogLevel.Trace) {
this.logService.trace(`[File Watcher (node.js)] ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`);
}
// Handle emit through delayer to accommodate for bulk changes and thus reduce spam
this.nonRecursiveFileChangesDelayer.trigger(() => {
const nonRecursiveFileChanges = this.nonRecursiveFileChangesBuffer;
this.nonRecursiveFileChangesBuffer = [];
// Event normalization
const normalizedNonRecursiveFileChangesEvents = normalizeFileChanges(nonRecursiveFileChanges);
// Logging
if (this.logService.getLevel() === LogLevel.Trace) {
normalizedNonRecursiveFileChangesEvents.forEach(event => {
this.logService.trace(`[File Watcher (node.js)] >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`);
});
}
// Fire
this._onDidChangeFile.fire(toFileChanges(normalizedNonRecursiveFileChangesEvents));
return Promise.resolve();
});
return new NodeJSWatcherService(
this.toFilePath(resource),
changes => this._onDidChangeFile.fire(toFileChanges(changes)),
error => this._onDidWatchErrorOccur.fire(new Error(error)),
info => this.logService.trace(info),
this.logService.getLevel() === LogLevel.Trace
);
}
//#endregion
@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files2/node/watcher/normalizer';
import { Disposable } from 'vs/base/common/lifecycle';
import { stat } from 'vs/base/node/pfs';
import { watchFolder, watchFile } from 'vs/base/node/watcher';
import { FileChangeType } from 'vs/platform/files/common/files';
import { ThrottledDelayer } from 'vs/base/common/async';
export class FileWatcher extends Disposable {
private isDisposed: boolean;
private fileChangesDelayer: ThrottledDelayer<void> = this._register(new ThrottledDelayer<void>(50));
private fileChangesBuffer: IDiskFileChange[] = [];
constructor(
private path: string,
private onFileChanges: (changes: IDiskFileChange[]) => void,
private errorLogger: (msg: string) => void,
private verboseLogger: (msg: string) => void,
private verboseLogging: boolean
) {
super();
this.startWatching();
}
private async startWatching(): Promise<void> {
try {
const fileStat = await stat(this.path);
if (this.isDisposed) {
return;
}
// Watch Folder
if (fileStat.isDirectory()) {
watchFolder(this.path, (eventType, path) => {
this.onFileChange({ type: eventType === 'changed' ? FileChangeType.UPDATED : eventType === 'added' ? FileChangeType.ADDED : FileChangeType.DELETED, path });
}, error => this.onError(error));
}
// Watch File
else {
this._register(watchFile(this.path, (eventType, path) => {
this.onFileChange({ type: eventType === 'changed' ? FileChangeType.UPDATED : FileChangeType.DELETED, path });
}, error => this.onError(error)));
}
} catch (error) {
this.onError(error);
}
}
private onFileChange(event: IDiskFileChange): void {
// Add to buffer
this.fileChangesBuffer.push(event);
// Logging
if (this.verboseLogging) {
this.onVerbose(`[File Watcher (node.js)] ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`);
}
// Handle emit through delayer to accommodate for bulk changes and thus reduce spam
this.fileChangesDelayer.trigger(() => {
const fileChanges = this.fileChangesBuffer;
this.fileChangesBuffer = [];
// Event normalization
const normalizedFileChanges = normalizeFileChanges(fileChanges);
// Logging
if (this.verboseLogging) {
normalizedFileChanges.forEach(event => {
this.onVerbose(`[File Watcher (node.js)] >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.path}`);
});
}
// Fire
this.onFileEvents(normalizedFileChanges);
return Promise.resolve();
});
}
private onFileEvents(events: IDiskFileChange[]): void {
if (this.isDisposed) {
return;
}
// Emit through event emitter
if (events.length > 0) {
this.onFileChanges(events);
}
}
private onError(error: string): void {
if (!this.isDisposed) {
this.errorLogger(error);
}
}
private onVerbose(msg: string): void {
if (!this.isDisposed) {
this.verboseLogger(msg);
}
}
dispose(): void {
this.isDisposed = true;
super.dispose();
}
}