mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
Update nsfw
and watcher service (#133826)
* 🆙 `vscode-nsfw@2.1.5` and tweak watcher service
* update again
This commit is contained in:
parent
8c6e828a8e
commit
5991e3c4b9
|
@ -78,7 +78,7 @@
|
||||||
"sudo-prompt": "9.2.1",
|
"sudo-prompt": "9.2.1",
|
||||||
"tas-client-umd": "0.1.4",
|
"tas-client-umd": "0.1.4",
|
||||||
"v8-inspect-profiler": "^0.0.22",
|
"v8-inspect-profiler": "^0.0.22",
|
||||||
"vscode-nsfw": "2.1.4",
|
"vscode-nsfw": "2.1.6",
|
||||||
"vscode-oniguruma": "1.5.1",
|
"vscode-oniguruma": "1.5.1",
|
||||||
"vscode-proxy-agent": "^0.11.0",
|
"vscode-proxy-agent": "^0.11.0",
|
||||||
"vscode-regexpp": "^3.1.0",
|
"vscode-regexpp": "^3.1.0",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"node-pty": "0.11.0-beta7",
|
"node-pty": "0.11.0-beta7",
|
||||||
"spdlog": "^0.13.0",
|
"spdlog": "^0.13.0",
|
||||||
"tas-client-umd": "0.1.4",
|
"tas-client-umd": "0.1.4",
|
||||||
"vscode-nsfw": "2.1.4",
|
"vscode-nsfw": "2.1.6",
|
||||||
"vscode-oniguruma": "1.5.1",
|
"vscode-oniguruma": "1.5.1",
|
||||||
"vscode-proxy-agent": "^0.11.0",
|
"vscode-proxy-agent": "^0.11.0",
|
||||||
"vscode-regexpp": "^3.1.0",
|
"vscode-regexpp": "^3.1.0",
|
||||||
|
|
|
@ -556,10 +556,10 @@ universalify@^0.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
vscode-nsfw@2.1.4:
|
vscode-nsfw@2.1.6:
|
||||||
version "2.1.4"
|
version "2.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-2.1.4.tgz#8965413e41a7099e36813921d3b468c12d1b6e59"
|
resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-2.1.6.tgz#51148fad0e7ff3b72fe0b3500903ab24af00f308"
|
||||||
integrity sha512-EsYWpACwojUjLgJFPhtnk/omId+oyuHpctWQiCLblf53eOxzYC7HT30NljEigl/sFmuTZM0IEMrsonj3GKDYPw==
|
integrity sha512-3RCPQ1/yBvwJuaUG6bJMOcmlRKuDHFn2xx04u8hWYOkNQODK7LncP5qXwU/NNN32/Vgp/cDy4P1o5HoGAHFOfg==
|
||||||
dependencies:
|
dependencies:
|
||||||
node-addon-api "*"
|
node-addon-api "*"
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import * as nsfw from 'vscode-nsfw';
|
import * as nsfw from 'vscode-nsfw';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async';
|
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||||
import { Emitter } from 'vs/base/common/event';
|
import { Emitter } from 'vs/base/common/event';
|
||||||
|
@ -52,8 +52,6 @@ interface IWatcher extends IDisposable {
|
||||||
|
|
||||||
export class NsfwWatcherService extends Disposable implements IWatcherService {
|
export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
|
|
||||||
private static readonly FS_EVENT_DELAY = 50; // aggregate and only emit events when changes have stopped for this duration (in ms)
|
|
||||||
|
|
||||||
private static readonly MAX_RESTARTS = 5; // number of restarts we allow before giving up in case of unexpected shutdown
|
private static readonly MAX_RESTARTS = 5; // number of restarts we allow before giving up in case of unexpected shutdown
|
||||||
|
|
||||||
private static readonly MAP_NSFW_ACTION_TO_FILE_CHANGE = new Map<number, number>(
|
private static readonly MAP_NSFW_ACTION_TO_FILE_CHANGE = new Map<number, number>(
|
||||||
|
@ -134,9 +132,6 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
private startWatching(request: IWatchRequest, restarts = 0): void {
|
private startWatching(request: IWatchRequest, restarts = 0): void {
|
||||||
const cts = new CancellationTokenSource();
|
const cts = new CancellationTokenSource();
|
||||||
|
|
||||||
let undeliveredFileEvents: IDiskFileChange[] = [];
|
|
||||||
const fileEventDelayer = new ThrottledDelayer<void>(NsfwWatcherService.FS_EVENT_DELAY);
|
|
||||||
|
|
||||||
let nsfwPromiseResolve: (watcher: nsfw.NSFW) => void;
|
let nsfwPromiseResolve: (watcher: nsfw.NSFW) => void;
|
||||||
const instance = new Promise<nsfw.NSFW>(resolve => nsfwPromiseResolve = resolve);
|
const instance = new Promise<nsfw.NSFW>(resolve => nsfwPromiseResolve = resolve);
|
||||||
|
|
||||||
|
@ -149,7 +144,6 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
token: cts.token,
|
token: cts.token,
|
||||||
dispose: () => {
|
dispose: () => {
|
||||||
cts.dispose(true);
|
cts.dispose(true);
|
||||||
fileEventDelayer.dispose();
|
|
||||||
instance.then(instance => instance.stop());
|
instance.then(instance => instance.stop());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -158,7 +152,9 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
// Path checks for symbolic links / wrong casing
|
// Path checks for symbolic links / wrong casing
|
||||||
const { realBasePathDiffers, realBasePathLength } = this.checkRequest(request);
|
const { realBasePathDiffers, realBasePathLength } = this.checkRequest(request);
|
||||||
|
|
||||||
const onFileEvent = (path: string, type: FileChangeType) => {
|
let undeliveredFileEvents: IDiskFileChange[] = [];
|
||||||
|
|
||||||
|
const onRawFileEvent = (path: string, type: FileChangeType) => {
|
||||||
if (!this.isPathIgnored(path, watcher.ignored)) {
|
if (!this.isPathIgnored(path, watcher.ignored)) {
|
||||||
undeliveredFileEvents.push({ type, path });
|
undeliveredFileEvents.push({ type, path });
|
||||||
} else if (this.verboseLogging) {
|
} else if (this.verboseLogging) {
|
||||||
|
@ -167,12 +163,13 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
};
|
};
|
||||||
|
|
||||||
nsfw(request.path, events => {
|
nsfw(request.path, events => {
|
||||||
for (const event of events) {
|
if (watcher.token.isCancellationRequested) {
|
||||||
if (watcher.token.isCancellationRequested) {
|
return; // return early when disposed
|
||||||
break; // return early when disposed
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Logging
|
for (const event of events) {
|
||||||
|
|
||||||
|
// Log the raw event before normalization or checking for ignore patterns
|
||||||
if (this.verboseLogging) {
|
if (this.verboseLogging) {
|
||||||
const logPath = event.action === nsfw.actions.RENAMED ? `${join(event.directory, event.oldFile || '')} -> ${event.newFile}` : join(event.directory, event.file || '');
|
const logPath = event.action === nsfw.actions.RENAMED ? `${join(event.directory, event.oldFile || '')} -> ${event.newFile}` : join(event.directory, event.file || '');
|
||||||
this.log(`${event.action === nsfw.actions.CREATED ? '[CREATED]' : event.action === nsfw.actions.DELETED ? '[DELETED]' : event.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`);
|
this.log(`${event.action === nsfw.actions.CREATED ? '[CREATED]' : event.action === nsfw.actions.DELETED ? '[DELETED]' : event.action === nsfw.actions.MODIFIED ? '[CHANGED]' : '[RENAMED]'} ${logPath}`);
|
||||||
|
@ -180,30 +177,23 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
|
|
||||||
// Rename: convert into DELETE & ADD
|
// Rename: convert into DELETE & ADD
|
||||||
if (event.action === nsfw.actions.RENAMED) {
|
if (event.action === nsfw.actions.RENAMED) {
|
||||||
onFileEvent(join(event.directory, event.oldFile || ''), FileChangeType.DELETED); // Rename fires when a file's name changes within a single directory
|
onRawFileEvent(join(event.directory, event.oldFile || ''), FileChangeType.DELETED); // Rename fires when a file's name changes within a single directory
|
||||||
onFileEvent(join(event.newDirectory || event.directory, event.newFile || ''), FileChangeType.ADDED);
|
onRawFileEvent(join(event.newDirectory || event.directory, event.newFile || ''), FileChangeType.ADDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created, modified, deleted: taks as is
|
// Created, modified, deleted: take as is
|
||||||
else {
|
else {
|
||||||
onFileEvent(join(event.directory, event.file || ''), NsfwWatcherService.MAP_NSFW_ACTION_TO_FILE_CHANGE.get(event.action)!);
|
onRawFileEvent(join(event.directory, event.file || ''), NsfwWatcherService.MAP_NSFW_ACTION_TO_FILE_CHANGE.get(event.action)!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send events delayed and normalized
|
// Reset undelivered events array
|
||||||
fileEventDelayer.trigger(async () => {
|
const undeliveredFileEventsToEmit = undeliveredFileEvents;
|
||||||
if (watcher.token.isCancellationRequested) {
|
undeliveredFileEvents = [];
|
||||||
return; // return early when disposed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remember as delivered
|
// Broadcast to clients normalized
|
||||||
const events = undeliveredFileEvents;
|
const normalizedEvents = normalizeFileChanges(this.normalizeEvents(undeliveredFileEventsToEmit, request, realBasePathDiffers, realBasePathLength));
|
||||||
undeliveredFileEvents = [];
|
this.emitEvents(normalizedEvents);
|
||||||
|
|
||||||
// Broadcast to clients normalized
|
|
||||||
const normalizedEvents = normalizeFileChanges(this.normalizeEvents(events, request, realBasePathDiffers, realBasePathLength));
|
|
||||||
this.emitEvents(normalizedEvents);
|
|
||||||
});
|
|
||||||
}, this.getOptions(watcher)).then(async nsfwWatcher => {
|
}, this.getOptions(watcher)).then(async nsfwWatcher => {
|
||||||
|
|
||||||
// Begin watching unless disposed already
|
// Begin watching unless disposed already
|
||||||
|
@ -230,9 +220,9 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// The default delay of NSFW is 500 but we already do some
|
// The default delay of NSFW is 500 but we want to
|
||||||
// debouncing in our code so we reduce the delay slightly
|
// react a bit faster than that.
|
||||||
debounceMS: 100
|
debounceMS: 250
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,36 +299,33 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
// See https://github.com/microsoft/vscode/issues/7950
|
// See https://github.com/microsoft/vscode/issues/7950
|
||||||
if (msg.indexOf('Inotify limit reached') !== -1) {
|
if (msg.indexOf('Inotify limit reached') !== -1) {
|
||||||
if (!this.enospcErrorLogged) {
|
if (!this.enospcErrorLogged) {
|
||||||
this.enospcErrorLogged = true; // only log this error once to protect against log spam
|
|
||||||
this.error('Inotify limit reached (ENOSPC)', watcher);
|
this.error('Inotify limit reached (ENOSPC)', watcher);
|
||||||
|
|
||||||
|
this.enospcErrorLogged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specially handle this error that indicates the watcher
|
// Any other error is unexpected and we should try to
|
||||||
// has stopped and we need to restart it.
|
// restart the watcher as a result to get into healthy
|
||||||
else if (msg.indexOf('Service shutdown unexpectedly') !== -1) {
|
// state again.
|
||||||
const handled = this.onUnexpectedShutdown(watcher);
|
|
||||||
if (!handled) {
|
|
||||||
this.error('Watcher service shutdown unexpectedly (ESHUTDOWN)', watcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log any other error
|
|
||||||
else {
|
else {
|
||||||
this.error(msg, watcher);
|
const handled = this.onUnexpectedError(msg, watcher);
|
||||||
|
if (!handled) {
|
||||||
|
this.error(`Unexpected error: ${msg} (ESHUTDOWN)`, watcher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onUnexpectedShutdown(watcher?: IWatcher): boolean {
|
private onUnexpectedError(error: string, watcher?: IWatcher): boolean {
|
||||||
if (!watcher || watcher.restarts >= NsfwWatcherService.MAX_RESTARTS) {
|
if (!watcher || watcher.restarts >= NsfwWatcherService.MAX_RESTARTS) {
|
||||||
return false; // we need a watcher that has not been restarted 5 times already
|
return false; // we need a watcher that has not been restarted MAX_RESTARTS times already
|
||||||
}
|
}
|
||||||
|
|
||||||
let handled = false;
|
let handled = false;
|
||||||
|
|
||||||
// Just try to restart watcher now if the path still exists
|
// Just try to restart watcher now if the path still exists
|
||||||
if (existsSync(watcher.request.path)) {
|
if (existsSync(watcher.request.path)) {
|
||||||
this.warn('Watcher service shutdown unexpectedly and will be restarted', watcher);
|
this.warn(`Watcher will be restarted due to unexpected error: ${error}`, watcher);
|
||||||
this.restartWatching(watcher);
|
this.restartWatching(watcher);
|
||||||
|
|
||||||
handled = true;
|
handled = true;
|
||||||
|
@ -354,7 +341,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWatchedPathDeleted(watcher: IWatcher): boolean {
|
private onWatchedPathDeleted(watcher: IWatcher): boolean {
|
||||||
this.warn('Watcher service shutdown unexpectedly because watched path got deleted', watcher);
|
this.warn('Watcher shutdown because watched path got deleted', watcher);
|
||||||
|
|
||||||
// Send a manual event given we know the root got deleted
|
// Send a manual event given we know the root got deleted
|
||||||
this.emitEvents([{ path: watcher.request.path, type: FileChangeType.DELETED }]);
|
this.emitEvents([{ path: watcher.request.path, type: FileChangeType.DELETED }]);
|
||||||
|
@ -368,7 +355,7 @@ export class NsfwWatcherService extends Disposable implements IWatcherService {
|
||||||
|
|
||||||
// Watcher path came back! Restart watching...
|
// Watcher path came back! Restart watching...
|
||||||
if (path === watcher.request.path && (type === 'added' || type === 'changed')) {
|
if (path === watcher.request.path && (type === 'added' || type === 'changed')) {
|
||||||
this.warn('Watcher service restarts for watched path got created again', watcher);
|
this.warn('Watcher restarts because watched path got created again', watcher);
|
||||||
|
|
||||||
// Stop watching that parent folder
|
// Stop watching that parent folder
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { tmpdir } from 'os';
|
import { tmpdir } from 'os';
|
||||||
|
import { timeout } from 'vs/base/common/async';
|
||||||
import { join } from 'vs/base/common/path';
|
import { join } from 'vs/base/common/path';
|
||||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||||
import { Promises } from 'vs/base/node/pfs';
|
import { Promises } from 'vs/base/node/pfs';
|
||||||
|
@ -38,25 +39,31 @@ flakySuite('Recursive Watcher', () => {
|
||||||
protected override getOptions(watcher: any) {
|
protected override getOptions(watcher: any) {
|
||||||
return {
|
return {
|
||||||
...super.getOptions(watcher),
|
...super.getOptions(watcher),
|
||||||
|
debounceMS: 1
|
||||||
// required to let tests pass properly. with a smaller value
|
|
||||||
// somehow `fsevents` on macOS seems to report events from
|
|
||||||
// the past so that the event listening will assume wrongly
|
|
||||||
debounceMS: 500
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let testDir: string;
|
let testDir: string;
|
||||||
let service: TestNsfwWatcherService;
|
let service: TestNsfwWatcherService;
|
||||||
let enableLogging = false;
|
|
||||||
|
let loggingEnabled = false;
|
||||||
|
|
||||||
|
function enableLogging(enable: boolean) {
|
||||||
|
loggingEnabled = enable;
|
||||||
|
service?.setVerboseLogging(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
enableLogging(false);
|
||||||
|
|
||||||
setup(async () => {
|
setup(async () => {
|
||||||
service = new TestNsfwWatcherService();
|
service = new TestNsfwWatcherService();
|
||||||
|
|
||||||
if (enableLogging) {
|
service.onDidLogMessage(e => {
|
||||||
service.onDidLogMessage(e => console.log(`[recursive watcher test message] ${e.message}`));
|
if (loggingEnabled) {
|
||||||
}
|
console.log(`[recursive watcher test message] ${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'filewatcher');
|
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'filewatcher');
|
||||||
|
|
||||||
|
@ -120,7 +127,7 @@ flakySuite('Recursive Watcher', () => {
|
||||||
await Promises.rename(newFolderPath, renamedFolderPath);
|
await Promises.rename(newFolderPath, renamedFolderPath);
|
||||||
await changeFuture;
|
await changeFuture;
|
||||||
|
|
||||||
// TODO: case rename is currently broken on macOS (https://github.com/Axosoft/nsfw/issues/146)
|
// Case rename is currently broken on macOS (https://github.com/Axosoft/nsfw/issues/146)
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
|
|
||||||
// Rename file (same name, different case)
|
// Rename file (same name, different case)
|
||||||
|
@ -175,6 +182,7 @@ flakySuite('Recursive Watcher', () => {
|
||||||
await changeFuture;
|
await changeFuture;
|
||||||
|
|
||||||
// Change file
|
// Change file
|
||||||
|
await timeout(1100); // nsfw cannot distinguish a create from a change when time period from create to change is <1s
|
||||||
changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.UPDATED);
|
changeFuture = awaitEvent(service, copiedFilepath, FileChangeType.UPDATED);
|
||||||
await Promises.writeFile(copiedFilepath, 'Hello Change');
|
await Promises.writeFile(copiedFilepath, 'Hello Change');
|
||||||
await changeFuture;
|
await changeFuture;
|
||||||
|
|
|
@ -10408,10 +10408,10 @@ vscode-nls-dev@^3.3.1:
|
||||||
xml2js "^0.4.19"
|
xml2js "^0.4.19"
|
||||||
yargs "^13.2.4"
|
yargs "^13.2.4"
|
||||||
|
|
||||||
vscode-nsfw@2.1.4:
|
vscode-nsfw@2.1.6:
|
||||||
version "2.1.4"
|
version "2.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-2.1.4.tgz#8965413e41a7099e36813921d3b468c12d1b6e59"
|
resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-2.1.6.tgz#51148fad0e7ff3b72fe0b3500903ab24af00f308"
|
||||||
integrity sha512-EsYWpACwojUjLgJFPhtnk/omId+oyuHpctWQiCLblf53eOxzYC7HT30NljEigl/sFmuTZM0IEMrsonj3GKDYPw==
|
integrity sha512-3RCPQ1/yBvwJuaUG6bJMOcmlRKuDHFn2xx04u8hWYOkNQODK7LncP5qXwU/NNN32/Vgp/cDy4P1o5HoGAHFOfg==
|
||||||
dependencies:
|
dependencies:
|
||||||
node-addon-api "*"
|
node-addon-api "*"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue