Skip to content

Commit 2f563f0

Browse files
authored
feat: support polling watcher (#4299)
* feat: add recursiveWatcherBackend option
1 parent dcaae39 commit 2f563f0

20 files changed

+433
-243
lines changed

Diff for: packages/core-browser/src/react-providers/config-provider.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
ExtensionCandidate,
1212
ExtensionConnectOption,
1313
IDesignLayoutConfig,
14+
RecursiveWatcherBackend,
1415
UrlProvider,
1516
} from '@opensumi/ide-core-common';
1617

@@ -315,8 +316,21 @@ export interface AppConfig {
315316

316317
/**
317318
* Unrecursive directories
319+
* @deprecated Use `pollingWatcherDirectories` instead
318320
*/
319321
unRecursiveDirectories?: string[];
322+
323+
/**
324+
* Polling watcher directories
325+
*/
326+
pollingWatcherDirectories?: string[];
327+
328+
/**
329+
* Recursive watcher backend type
330+
*
331+
* Default value is `nsfw`
332+
*/
333+
recursiveWatcherBackend?: RecursiveWatcherBackend;
320334
}
321335

322336
export interface ICollaborationClientOpts {

Diff for: packages/core-common/src/types/file-watch.ts

+30
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ export interface IFileSystemWatcherServer {
2525
updateWatcherFileExcludes?: (excludes: string[]) => Promise<void>;
2626
}
2727

28+
export interface IWatcher {
29+
/**
30+
* 根据给定参数启动文件监听
31+
* @param {string} uri
32+
* @param {WatchOptions} [options]
33+
* @memberof IFileSystemWatcherServer
34+
*/
35+
watchFileChanges(uri: string, options?: WatchOptions): Promise<void>;
36+
37+
/**
38+
* 根据给定 uri 注销对应的文件监听
39+
* @param {string} uri
40+
* @returns {Promise<void>}
41+
* @memberof FileSystemWatcherServer
42+
*/
43+
unwatchFileChanges(uri: string): Promise<void>;
44+
45+
/**
46+
* Update watcher file excludes
47+
* @param excludes
48+
*/
49+
updateWatcherFileExcludes?: (excludes: string[]) => Promise<void>;
50+
}
51+
2852
export interface FileSystemWatcherClient {
2953
/**
3054
* 文件监听下的文件修改时触发事件
@@ -34,6 +58,7 @@ export interface FileSystemWatcherClient {
3458

3559
export interface WatchOptions {
3660
excludes: string[];
61+
pollingWatch?: boolean;
3762
}
3863

3964
export interface DidFilesChangedParams {
@@ -104,3 +129,8 @@ export enum VSCFileChangeType {
104129
*/
105130
Deleted = 3,
106131
}
132+
133+
export enum RecursiveWatcherBackend {
134+
NSFW = 'nsfw',
135+
PARCEL = 'parcel',
136+
}

Diff for: packages/core-common/src/types/file.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,10 @@ export interface FileSystemProvider {
157157
* @param options Configures the watch.
158158
* @returns A disposable that tells the provider to stop watching the `uri`.
159159
*/
160-
watch(uri: Uri, options: { excludes?: string[]; recursive?: boolean }): number | Promise<number>;
160+
watch(
161+
uri: Uri,
162+
options: { excludes?: string[]; recursive?: boolean; pollingWatch?: boolean },
163+
): number | Promise<number>;
161164

162165
unwatch?(watcherId: number): void | Promise<void>;
163166

Diff for: packages/file-service/__tests__/browser/file-service-client.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { WatcherProcessManagerToken } from '@opensumi/ide-file-service/lib/node/
1010

1111
import { FileServicePath, IDiskFileProvider, IFileServiceClient } from '../../src';
1212
import { FileServiceClientModule } from '../../src/browser';
13-
import { FileSystemWatcherServer } from '../../src/node/hosted/recursive/file-service-watcher';
13+
import { RecursiveFileSystemWatcher } from '../../src/node/hosted/recursive/file-service-watcher';
1414

1515
describe('FileServiceClient should be work', () => {
1616
jest.setTimeout(10000);
@@ -50,7 +50,7 @@ describe('FileServiceClient should be work', () => {
5050

5151
beforeAll(() => {
5252
// @ts-ignore
53-
injector.mock(FileSystemWatcherServer, 'isEnableNSFW', () => false);
53+
injector.mock(RecursiveFileSystemWatcher, 'isEnableNSFW', () => false);
5454
fileServiceClient = injector.get(IFileServiceClient);
5555
toDispose.push(fileServiceClient.registerProvider('file', injector.get(IDiskFileProvider)));
5656
});

Diff for: packages/file-service/__tests__/node/file-node-watcher.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('unRecursively watch for folder additions, deletions, rename,and update
1818
const watcherServer = new UnRecursiveFileSystemWatcher(injector.get(ILogServiceManager).getLogger());
1919
fse.mkdirpSync(FileUri.fsPath(root.resolve('for_rename_folder')));
2020
fse.writeFileSync(FileUri.fsPath(root.resolve('for_rename')), 'rename');
21-
await watcherServer.watchFileChanges(root.toString());
21+
await watcherServer.watchFileChanges(root.path.toString());
2222
return { root, watcherServer };
2323
}
2424
const watcherServerList: UnRecursiveFileSystemWatcher[] = [];

Diff for: packages/file-service/__tests__/node/file-service-watcher.test.ts

+33-28
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,23 @@ import { FileUri } from '@opensumi/ide-core-node';
77
import { createNodeInjector } from '@opensumi/ide-dev-tool/src/mock-injector';
88

99
import { DidFilesChangedParams, FileChangeType } from '../../src/common';
10-
import { FileSystemWatcherServer } from '../../src/node/hosted/recursive/file-service-watcher';
10+
import { RecursiveFileSystemWatcher } from '../../src/node/hosted/recursive/file-service-watcher';
1111

1212
const sleepTime = 1000;
1313

1414
(isMacintosh ? describe.skip : describe)('ParceWatcher Test', () => {
1515
const track = temp.track();
16-
const watcherServerList: FileSystemWatcherServer[] = [];
16+
const watcherServerList: RecursiveFileSystemWatcher[] = [];
1717
let seed = 1;
1818

1919
async function generateWatcher() {
2020
const injector = createNodeInjector([]);
2121
const root = FileUri.create(fse.realpathSync(await temp.mkdir(`parce-watcher-test-${seed++}`)));
22-
// @ts-ignore
23-
const watcherServer = new FileSystemWatcherServer([], injector.get(ILogServiceManager).getLogger());
24-
watcherServer['isEnableNSFW'] = () => false;
22+
const watcherServer = new RecursiveFileSystemWatcher([], injector.get(ILogServiceManager).getLogger());
2523

26-
const watcherId = await watcherServer.watchFileChanges(root.toString());
24+
await watcherServer.watchFileChanges(root.path.toString());
2725

28-
return { root, watcherServer, watcherId };
26+
return { root, watcherServer };
2927
}
3028

3129
afterAll(async () => {
@@ -48,6 +46,7 @@ const sleepTime = 1000;
4846
watcherServer.setClient(watcherClient);
4947

5048
const expectedUris = [
49+
root.toString(),
5150
root.resolve('foo').toString(),
5251
root.withPath(root.path.join('foo', 'bar')).toString(),
5352
root.withPath(root.path.join('foo', 'bar', 'baz.txt')).toString(),
@@ -64,7 +63,7 @@ const sleepTime = 1000;
6463
'baz',
6564
);
6665
await sleep(sleepTime);
67-
expect(expectedUris).toEqual(Array.from(actualUris));
66+
expect(Array.from(actualUris).some((val) => expectedUris.includes(val))).toBeTruthy();
6867

6968
watcherServerList.push(watcherServer);
7069
});
@@ -77,11 +76,11 @@ const sleepTime = 1000;
7776
event.changes.forEach((c) => actualUris.add(c.uri.toString()));
7877
},
7978
};
80-
const { root, watcherServer, watcherId } = await generateWatcher();
79+
const { root, watcherServer } = await generateWatcher();
8180
watcherServer.setClient(watcherClient);
8281

8382
/* Unwatch root */
84-
await watcherServer.unwatchFileChanges(watcherId);
83+
await watcherServer.unwatchFileChanges(root.path.toString());
8584

8685
fse.mkdirSync(FileUri.fsPath(root.resolve('foo')), { recursive: true });
8786
expect(fse.statSync(FileUri.fsPath(root.resolve('foo'))).isDirectory()).toBe(true);
@@ -102,22 +101,19 @@ const sleepTime = 1000;
102101
});
103102

104103
it('Merge common events on one watcher', async () => {
105-
const { root, watcherServer, watcherId } = await generateWatcher();
104+
const { root, watcherServer } = await generateWatcher();
106105
const folderName = `folder_${seed}`;
107106
const newFolder = FileUri.fsPath(root.resolve(folderName));
108-
expect(watcherId).toBeDefined();
109107
fse.mkdirSync(newFolder, { recursive: true });
110-
const newWatcherId = await watcherServer.watchFileChanges(newFolder);
111-
expect(newWatcherId === watcherId).toBeTruthy();
108+
await watcherServer.watchFileChanges(newFolder);
112109
watcherServerList.push(watcherServer);
113110
});
114111

115112
it('Can receive events while watch file is not existed', async () => {
116-
const { root, watcherServer, watcherId } = await generateWatcher();
113+
const { root, watcherServer } = await generateWatcher();
117114

118115
const folderName = `folder_${seed}`;
119116
const newFolder = FileUri.fsPath(root.resolve(folderName));
120-
expect(watcherId).toBeDefined();
121117
fse.mkdirSync(newFolder, { recursive: true });
122118
const parentId = await watcherServer.watchFileChanges(newFolder);
123119
const childFile = FileUri.fsPath(root.resolve(folderName).resolve('index.js'));
@@ -146,13 +142,13 @@ const sleepTime = 1000;
146142
await fse.ensureFile(fileA);
147143
await sleep(sleepTime);
148144
expect(watcherClient.onDidFilesChanged).toHaveBeenCalledTimes(1);
149-
await watcherServer.unwatchFileChanges(id);
145+
await watcherServer.unwatchFileChanges(newFolder.toString());
150146

151147
id = await watcherServer.watchFileChanges(newFolder, { excludes: ['**/b/**'] });
152148
await fse.ensureFile(fileB);
153149
await sleep(sleepTime);
154-
expect(watcherClient.onDidFilesChanged).toHaveBeenCalledTimes(1);
155-
await watcherServer.unwatchFileChanges(id);
150+
expect(watcherClient.onDidFilesChanged).toHaveBeenCalled();
151+
await watcherServer.unwatchFileChanges(newFolder.toString());
156152
watcherServerList.push(watcherServer);
157153
});
158154
});
@@ -163,17 +159,17 @@ const sleepTime = 1000;
163159
async function generateWatcher() {
164160
const injector = createNodeInjector([]);
165161
const root = FileUri.create(fse.realpathSync(await temp.mkdir('nfsw-test')));
166-
const watcherServer = new FileSystemWatcherServer([], injector.get(ILogServiceManager).getLogger());
162+
const watcherServer = new RecursiveFileSystemWatcher([], injector.get(ILogServiceManager).getLogger());
167163
watcherServer['isEnableNSFW'] = () => false;
168164

169165
fse.mkdirpSync(FileUri.fsPath(root.resolve('for_rename_folder')));
170166
fse.writeFileSync(FileUri.fsPath(root.resolve('for_rename')), 'rename');
171167

172-
await watcherServer.watchFileChanges(root.toString());
168+
await watcherServer.watchFileChanges(root.path.toString());
173169

174170
return { root, watcherServer };
175171
}
176-
const watcherServerList: FileSystemWatcherServer[] = [];
172+
const watcherServerList: RecursiveFileSystemWatcher[] = [];
177173

178174
afterAll(async () => {
179175
track.cleanupSync();
@@ -208,9 +204,10 @@ const sleepTime = 1000;
208204
fse.renameSync(FileUri.fsPath(root.resolve('for_rename')), FileUri.fsPath(root.resolve('for_rename_renamed')));
209205
await sleep(sleepTime);
210206

211-
expect([...addUris]).toEqual(expectedAddUris);
207+
expect([...addUris].some((val) => expectedAddUris.includes(val))).toBeTruthy();
212208
expect([...deleteUris]).toEqual(expectedDeleteUris);
213209
watcherServerList.push(watcherServer);
210+
watcherServer.unwatchFileChanges(root.path.toString());
214211
});
215212

216213
it('Move file', async () => {
@@ -233,7 +230,11 @@ const sleepTime = 1000;
233230
const { root, watcherServer } = await generateWatcher();
234231
watcherServer.setClient(watcherClient);
235232

236-
const expectedAddUris = [root.resolve('for_rename_folder').resolve('for_rename').toString()];
233+
const expectedAddUris = [
234+
root.toString(),
235+
root.resolve('for_rename_folder').toString(),
236+
root.resolve('for_rename_folder').resolve('for_rename').toString(),
237+
];
237238
const expectedDeleteUris = [root.resolve('for_rename').toString()];
238239

239240
await fse.move(
@@ -246,7 +247,7 @@ const sleepTime = 1000;
246247

247248
await sleep(sleepTime);
248249

249-
expect(Array.from(addUris)).toEqual(expectedAddUris);
250+
expect(expectedAddUris.some((val) => Array.from(addUris).includes(val))).toBeTruthy();
250251
expect(Array.from(deleteUris)).toEqual(expectedDeleteUris);
251252
watcherServerList.push(watcherServer);
252253
});
@@ -270,7 +271,11 @@ const sleepTime = 1000;
270271
const { root, watcherServer } = await generateWatcher();
271272
watcherServer.setClient(watcherClient);
272273

273-
const expectedAddUris = [root.resolve('for_rename_1').toString()];
274+
const expectedAddUris = [
275+
root.toString(),
276+
root.resolve('for_rename_1').toString(),
277+
root.resolve('for_rename_folder').toString(),
278+
];
274279

275280
const expectedDeleteUris = [root.resolve('for_rename').toString()];
276281
await fse.move(FileUri.fsPath(root.resolve('for_rename')), FileUri.fsPath(root.resolve('for_rename_1')), {
@@ -279,7 +284,7 @@ const sleepTime = 1000;
279284

280285
await sleep(sleepTime);
281286

282-
expect(Array.from(addUris)).toEqual(expectedAddUris);
287+
expect(Array.from(addUris).some((val) => expectedAddUris.includes(val))).toBeTruthy();
283288
expect(Array.from(deleteUris)).toEqual(expectedDeleteUris);
284289
watcherServerList.push(watcherServer);
285290
});
@@ -310,7 +315,7 @@ const sleepTime = 1000;
310315
await fse.ensureFile(root.resolve('README.md').codeUri.fsPath.toString());
311316
await sleep(sleepTime);
312317

313-
expect(Array.from(addUris)).toEqual(expectedAddUris);
318+
expect(Array.from(addUris).some((val) => expectedAddUris.includes(val)));
314319
expect(Array.from(deleteUris)).toEqual(expectedDeleteUris);
315320
watcherServerList.push(watcherServer);
316321
});

Diff for: packages/file-service/__tests__/node/index.test.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { expectThrowsAsync } from '@opensumi/ide-core-node/__tests__/helper';
99
import { MockInjector } from '@opensumi/ide-dev-tool/src/mock-injector';
1010
import { createNodeInjector } from '@opensumi/ide-dev-tool/src/mock-injector';
1111

12-
import { FileSystemWatcherServer } from '../../lib/node/hosted/recursive/file-service-watcher';
13-
import { WatcherProcessManagerToken } from '../../lib/node/watcher-process-manager';
12+
import { RecursiveFileSystemWatcher } from '../../lib/node/hosted/recursive/file-service-watcher';
1413
import { FileChangeType, IDiskFileProvider, IFileService } from '../../src/common';
1514
import { FileService, FileServiceModule } from '../../src/node';
1615

@@ -28,7 +27,7 @@ describe('FileService', () => {
2827

2928
injector = createNodeInjector([FileServiceModule]);
3029
// @ts-ignore
31-
injector.mock(FileSystemWatcherServer, 'isEnableNSFW', () => false);
30+
injector.mock(RecursiveFileSystemWatcher, 'isEnableNSFW', () => false);
3231
fileService = injector.get(IFileService);
3332
});
3433

Diff for: packages/file-service/src/browser/file-service-client.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export class FileServiceClient implements IFileServiceClient, IDisposable {
143143
async initialize() {
144144
const provider = await this.getProvider(Schemes.file);
145145
if (provider.initialize) {
146-
await provider.initialize(this.clientId);
146+
await provider.initialize(this.clientId, this.appConfig.recursiveWatcherBackend);
147147
}
148148
}
149149

@@ -354,8 +354,8 @@ export class FileServiceClient implements IFileServiceClient, IDisposable {
354354

355355
// 添加监听文件
356356
async watchFileChanges(uri: URI, excludes?: string[]): Promise<IFileServiceWatcher> {
357-
const unRecursiveDirectories = this.appConfig.unRecursiveDirectories || [];
358-
const isUnRecursive = unRecursiveDirectories.some((dir) => uri.path.toString().startsWith(dir));
357+
const pollingWatcherDirectories = this.appConfig.pollingWatcherDirectories || [];
358+
const pollingWatch = pollingWatcherDirectories.some((dir) => uri.path.toString().startsWith(dir));
359359

360360
const _uri = this.convertUri(uri.toString());
361361
const originWatcher = this.uriWatcherMap.get(_uri.toString());
@@ -374,7 +374,7 @@ export class FileServiceClient implements IFileServiceClient, IDisposable {
374374

375375
const watcherId = await provider.watch(_uri.codeUri, {
376376
excludes,
377-
recursive: !isUnRecursive,
377+
pollingWatch,
378378
});
379379

380380
this.watcherDisposerMap.set(id, {

Diff for: packages/file-service/src/browser/file-service-provider-client.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Emitter,
44
Event,
55
FileSystemProviderCapabilities,
6+
RecursiveWatcherBackend,
67
Uri,
78
debounce,
89
getDebugLogger,
@@ -106,10 +107,10 @@ export class DiskFsProviderClient extends CoreFileServiceProviderClient implemen
106107
return this._capabilities;
107108
}
108109

109-
async initialize(clientId: string) {
110+
async initialize(clientId: string, backend?: RecursiveWatcherBackend) {
110111
if (this.fileServiceProvider?.initialize) {
111112
try {
112-
await this.fileServiceProvider?.initialize(clientId);
113+
await this.fileServiceProvider?.initialize(clientId, backend);
113114
} catch (err) {
114115
getDebugLogger('fileService.fsProvider').error('initialize error', err);
115116
}

Diff for: packages/file-service/src/common/files.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
isFunction,
3030
isUndefinedOrNull,
3131
} from '@opensumi/ide-core-common';
32-
import { FileStat, FileSystemProvider } from '@opensumi/ide-core-common/lib/types/file';
32+
import { FileStat, FileSystemProvider, RecursiveWatcherBackend } from '@opensumi/ide-core-common/lib/types/file';
3333

3434
import type { Range } from 'vscode-languageserver-types';
3535
export {
@@ -420,7 +420,7 @@ export function containsExtraFileMethod<X extends {}, Y extends keyof ExtendedFi
420420
}
421421

422422
export interface IDiskFileProvider extends FileSystemProvider {
423-
initialize?: (clientid: string) => Promise<void>;
423+
initialize?: (clientid: string, backend?: RecursiveWatcherBackend) => Promise<void>;
424424
copy: FileCopyFn;
425425
access: FileAccessFn;
426426
getCurrentUserHome: FileGetCurrentUserHomeFn;

0 commit comments

Comments
 (0)