@theia/filesystem 1.77.0-next.7 → 1.77.0-next.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/download/file-download-service.d.ts.map +1 -1
- package/lib/browser/download/file-download-service.js +0 -1
- package/lib/browser/download/file-download-service.js.map +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.d.ts +2 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.d.ts.map +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.js +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.js.map +1 -1
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.d.ts +2 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.d.ts.map +1 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.js +37 -0
- package/lib/browser/file-dialog/file-dialog-tree-filters-renderer.spec.js.map +1 -0
- package/lib/browser/file-dialog/file-dialog.d.ts +5 -0
- package/lib/browser/file-dialog/file-dialog.d.ts.map +1 -1
- package/lib/browser/file-dialog/file-dialog.js +6 -2
- package/lib/browser/file-dialog/file-dialog.js.map +1 -1
- package/lib/browser/file-resource.spec.js +2 -1
- package/lib/browser/file-resource.spec.js.map +1 -1
- package/lib/browser/file-service-watcher.spec.js +2 -1
- package/lib/browser/file-service-watcher.spec.js.map +1 -1
- package/lib/browser/file-service.d.ts +1 -1
- package/lib/browser/file-service.js +1 -1
- package/lib/browser/file-service.js.map +1 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.js +1 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.js.map +1 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js +2 -1
- package/lib/browser/file-tree/file-tree-decorator-adapter.spec.js.map +1 -1
- package/lib/browser/file-tree/file-tree-model.js +1 -1
- package/lib/browser/file-tree/file-tree-model.js.map +1 -1
- package/lib/browser/file-tree/file-tree-widget.js +1 -1
- package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
- package/lib/browser/filesystem-frontend-contribution.js +1 -1
- package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
- package/lib/browser/location/location-renderer.js +1 -1
- package/lib/browser/location/location-renderer.js.map +1 -1
- package/lib/browser/location/location-renderer.spec.js +2 -1
- package/lib/browser/location/location-renderer.spec.js.map +1 -1
- package/lib/browser/upload/file-upload-service-impl.js +1 -1
- package/lib/browser/upload/file-upload-service-impl.js.map +1 -1
- package/lib/browser-only/download/file-download-service.js +1 -1
- package/lib/browser-only/download/file-download-service.js.map +1 -1
- package/lib/browser-only/file-search.js +2 -1
- package/lib/browser-only/file-search.js.map +1 -1
- package/lib/browser-only/upload/file-upload-service-impl.js +1 -1
- package/lib/browser-only/upload/file-upload-service-impl.js.map +1 -1
- package/lib/common/files.js +2 -1
- package/lib/common/files.js.map +1 -1
- package/lib/common/files.spec.js +2 -1
- package/lib/common/files.spec.js.map +1 -1
- package/lib/common/filesystem-watcher-protocol.d.ts +4 -0
- package/lib/common/filesystem-watcher-protocol.d.ts.map +1 -1
- package/lib/common/remote-file-system-provider.d.ts +4 -4
- package/lib/common/remote-file-system-provider.d.ts.map +1 -1
- package/lib/common/remote-file-system-provider.js +1 -1
- package/lib/common/remote-file-system-provider.js.map +1 -1
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.js +1 -1
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.js.map +1 -1
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.d.ts +2 -0
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.d.ts.map +1 -0
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.js +48 -0
- package/lib/electron-browser/file-dialog/electron-file-dialog-service.spec.js.map +1 -0
- package/lib/node/disk-file-system-provider.d.ts.map +1 -1
- package/lib/node/disk-file-system-provider.js +8 -5
- package/lib/node/disk-file-system-provider.js.map +1 -1
- package/lib/node/disk-file-system-provider.spec.js +28 -2
- package/lib/node/disk-file-system-provider.spec.js.map +1 -1
- package/lib/node/download/directory-archiver.js +2 -2
- package/lib/node/download/directory-archiver.js.map +1 -1
- package/lib/node/download/directory-archiver.spec.js +5 -4
- package/lib/node/download/directory-archiver.spec.js.map +1 -1
- package/lib/node/download/file-download-cache.js +1 -1
- package/lib/node/download/file-download-cache.js.map +1 -1
- package/lib/node/download/file-download-handler.d.ts.map +1 -1
- package/lib/node/download/file-download-handler.js +4 -5
- package/lib/node/download/file-download-handler.js.map +1 -1
- package/lib/node/file-change-collection.spec.js +2 -1
- package/lib/node/file-change-collection.spec.js.map +1 -1
- package/lib/node/filesystem-backend-module.d.ts +2 -2
- package/lib/node/filesystem-backend-module.d.ts.map +1 -1
- package/lib/node/filesystem-backend-module.js +7 -5
- package/lib/node/filesystem-backend-module.js.map +1 -1
- package/lib/node/filesystem-watcher-service-impl.d.ts +30 -0
- package/lib/node/filesystem-watcher-service-impl.d.ts.map +1 -0
- package/lib/node/filesystem-watcher-service-impl.js +92 -0
- package/lib/node/filesystem-watcher-service-impl.js.map +1 -0
- package/lib/node/filesystem-watcher-service-impl.spec.d.ts +2 -0
- package/lib/node/filesystem-watcher-service-impl.spec.d.ts.map +1 -0
- package/lib/node/filesystem-watcher-service-impl.spec.js +202 -0
- package/lib/node/filesystem-watcher-service-impl.spec.js.map +1 -0
- package/lib/node/filesystem-watcher.d.ts +93 -0
- package/lib/node/filesystem-watcher.d.ts.map +1 -0
- package/lib/node/filesystem-watcher.js +134 -0
- package/lib/node/filesystem-watcher.js.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.d.ts +175 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.js +580 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.js.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts +2 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.js +1284 -0
- package/lib/node/nodejs-watcher/node-directory-watcher.spec.js.map +1 -0
- package/lib/node/nodejs-watcher/watch-request-router.d.ts +63 -0
- package/lib/node/nodejs-watcher/watch-request-router.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/watch-request-router.js +183 -0
- package/lib/node/nodejs-watcher/watch-request-router.js.map +1 -0
- package/lib/node/nodejs-watcher/watcher-host.d.ts +50 -0
- package/lib/node/nodejs-watcher/watcher-host.d.ts.map +1 -0
- package/lib/node/nodejs-watcher/watcher-host.js +99 -0
- package/lib/node/nodejs-watcher/watcher-host.js.map +1 -0
- package/lib/node/parcel-watcher/index.js +4 -3
- package/lib/node/parcel-watcher/index.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +14 -194
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.js +7 -424
- package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js +8 -7
- package/lib/node/parcel-watcher/parcel-filesystem-watcher.spec.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js +8 -7
- package/lib/node/parcel-watcher/parcel-watcher-exclude.spec.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js +7 -6
- package/lib/node/parcel-watcher/parcel-watcher-retry.spec.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-watcher.d.ts +165 -0
- package/lib/node/parcel-watcher/parcel-watcher.d.ts.map +1 -0
- package/lib/node/parcel-watcher/parcel-watcher.js +370 -0
- package/lib/node/parcel-watcher/parcel-watcher.js.map +1 -0
- package/lib/node/test/watcher-test-helper.d.ts +21 -0
- package/lib/node/test/watcher-test-helper.d.ts.map +1 -0
- package/lib/node/test/watcher-test-helper.js +56 -0
- package/lib/node/test/watcher-test-helper.js.map +1 -0
- package/lib/node/upload/node-file-upload-service.spec.js +4 -3
- package/lib/node/upload/node-file-upload-service.spec.js.map +1 -1
- package/package.json +7 -7
- package/src/browser/download/file-download-service.ts +0 -1
- package/src/browser/file-dialog/file-dialog-tree-filters-renderer.spec.ts +43 -0
- package/src/browser/file-dialog/file-dialog-tree-filters-renderer.tsx +3 -1
- package/src/browser/file-dialog/file-dialog.ts +11 -1
- package/src/browser/file-service.ts +3 -3
- package/src/common/filesystem-watcher-protocol.ts +4 -0
- package/src/electron-browser/file-dialog/electron-file-dialog-service.spec.ts +57 -0
- package/src/electron-browser/file-dialog/electron-file-dialog-service.ts +1 -1
- package/src/node/disk-file-system-provider.spec.ts +29 -0
- package/src/node/disk-file-system-provider.ts +5 -2
- package/src/node/download/file-download-cache.ts +1 -1
- package/src/node/download/file-download-handler.ts +0 -1
- package/src/node/filesystem-backend-module.ts +5 -4
- package/src/node/filesystem-watcher-service-impl.spec.ts +272 -0
- package/src/node/filesystem-watcher-service-impl.ts +109 -0
- package/src/node/filesystem-watcher.ts +197 -0
- package/src/node/nodejs-watcher/node-directory-watcher.spec.ts +1594 -0
- package/src/node/nodejs-watcher/node-directory-watcher.ts +664 -0
- package/src/node/nodejs-watcher/watch-request-router.ts +224 -0
- package/src/node/nodejs-watcher/watcher-host.ts +126 -0
- package/src/node/parcel-watcher/index.ts +2 -2
- package/src/node/parcel-watcher/parcel-filesystem-service.ts +14 -500
- package/src/node/parcel-watcher/parcel-filesystem-watcher.spec.ts +4 -4
- package/src/node/parcel-watcher/parcel-watcher-exclude.spec.ts +4 -4
- package/src/node/parcel-watcher/parcel-watcher-retry.spec.ts +3 -3
- package/src/node/parcel-watcher/parcel-watcher.ts +432 -0
- package/src/node/test/watcher-test-helper.ts +58 -0
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
// and https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/workbench/services/workingCopy/common/workingCopyFileOperationParticipant.ts
|
|
25
25
|
|
|
26
26
|
/* eslint-disable max-len */
|
|
27
|
+
/* eslint-disable @stylistic/brace-style, @stylistic/semi-spacing */
|
|
27
28
|
/* eslint-disable @typescript-eslint/no-shadow */
|
|
28
29
|
/* eslint-disable no-null/no-null */
|
|
29
|
-
/* eslint-disable @typescript-eslint/tslint/config */
|
|
30
30
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
31
31
|
|
|
32
32
|
import { injectable, inject, named, postConstruct } from '@theia/core/shared/inversify';
|
|
@@ -201,7 +201,7 @@ export const FileServiceContribution = Symbol('FileServiceContribution');
|
|
|
201
201
|
* });
|
|
202
202
|
*
|
|
203
203
|
* }
|
|
204
|
-
|
|
204
|
+
* ```
|
|
205
205
|
*/
|
|
206
206
|
export interface FileServiceContribution {
|
|
207
207
|
/**
|
|
@@ -515,7 +515,7 @@ export class FileService {
|
|
|
515
515
|
*/
|
|
516
516
|
resolve(resource: URI, options: ResolveMetadataFileOptions): Promise<FileStatWithMetadata>;
|
|
517
517
|
resolve(resource: URI, options?: ResolveFileOptions | undefined): Promise<FileStat>;
|
|
518
|
-
async resolve(resource: any, options?: any) {
|
|
518
|
+
async resolve(resource: any, options?: any): Promise<FileStat> {
|
|
519
519
|
try {
|
|
520
520
|
return await this.doResolveFile(resource, options);
|
|
521
521
|
} catch (error) {
|
|
@@ -89,6 +89,10 @@ export interface FileSystemWatcherClient {
|
|
|
89
89
|
|
|
90
90
|
export interface WatchOptions {
|
|
91
91
|
ignored: string[];
|
|
92
|
+
/**
|
|
93
|
+
* Watch the whole subtree under the given path. Defaults to `true`.
|
|
94
|
+
*/
|
|
95
|
+
recursive?: boolean;
|
|
92
96
|
}
|
|
93
97
|
export interface FileChange {
|
|
94
98
|
uri: string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 Vinay Kaushal and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
|
|
18
|
+
let disableJSDOM = enableJSDOM();
|
|
19
|
+
|
|
20
|
+
import { expect } from 'chai';
|
|
21
|
+
import { FileUri } from '@theia/core/lib/common/file-uri';
|
|
22
|
+
import URI from '@theia/core/lib/common/uri';
|
|
23
|
+
import { OpenFileDialogProps } from '../../browser/file-dialog';
|
|
24
|
+
import { OpenDialogOptions } from '../../electron-common/electron-api';
|
|
25
|
+
import { ElectronFileDialogService } from './electron-file-dialog-service';
|
|
26
|
+
|
|
27
|
+
disableJSDOM();
|
|
28
|
+
|
|
29
|
+
class TestElectronFileDialogService extends ElectronFileDialogService {
|
|
30
|
+
override toOpenDialogOptions(uri: URI, props: OpenFileDialogProps): OpenDialogOptions {
|
|
31
|
+
return super.toOpenDialogOptions(uri, props);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('ElectronFileDialogService', () => {
|
|
36
|
+
|
|
37
|
+
before(() => disableJSDOM = enableJSDOM());
|
|
38
|
+
after(() => disableJSDOM());
|
|
39
|
+
|
|
40
|
+
const uri = FileUri.create('/home/user');
|
|
41
|
+
const filters = { 'Theia Workspace': ['theia-workspace'] };
|
|
42
|
+
|
|
43
|
+
it('adds an All Files filter to the given filters', () => {
|
|
44
|
+
const options = new TestElectronFileDialogService().toOpenDialogOptions(uri, { title: 'Open', canSelectFiles: true, filters });
|
|
45
|
+
expect(options.filters).to.deep.equal([
|
|
46
|
+
{ name: 'Theia Workspace', extensions: ['theia-workspace'] },
|
|
47
|
+
{ name: 'All Files', extensions: ['*'] }
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('omits the All Files filter if it is disabled', () => {
|
|
52
|
+
const options = new TestElectronFileDialogService().toOpenDialogOptions(uri, { title: 'Open', canSelectFiles: true, filters, disableAllFilesFilter: true });
|
|
53
|
+
expect(options.filters).to.deep.equal([
|
|
54
|
+
{ name: 'Theia Workspace', extensions: ['theia-workspace'] }
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -136,7 +136,7 @@ export class ElectronFileDialogService extends DefaultFileDialogService {
|
|
|
136
136
|
result.filters.push({ name: label, extensions: extensions });
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
if (props.canSelectFiles) {
|
|
139
|
+
if (props.canSelectFiles && !props.disableAllFilesFilter) {
|
|
140
140
|
if (filters.length > 0) {
|
|
141
141
|
result.filters.push({ name: 'All Files', extensions: ['*'] });
|
|
142
142
|
}
|
|
@@ -114,6 +114,35 @@ describe('disk-file-system-provider', () => {
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
it('handles errors during temporary cleanup', async () => {
|
|
118
|
+
const tempDirPath = tracked.mkdirSync();
|
|
119
|
+
const provider = fsProvider as unknown as {
|
|
120
|
+
rimrafMove(path: string): Promise<void>;
|
|
121
|
+
rimrafUnlink(path: string): Promise<void>;
|
|
122
|
+
logger: ILogger;
|
|
123
|
+
};
|
|
124
|
+
const originalRimrafUnlink = provider.rimrafUnlink;
|
|
125
|
+
const originalWarn = provider.logger.warn;
|
|
126
|
+
let resolveWarning!: () => void;
|
|
127
|
+
const warningHandled = new Promise<void>(resolve => {
|
|
128
|
+
resolveWarning = resolve;
|
|
129
|
+
});
|
|
130
|
+
provider.rimrafUnlink = async path => {
|
|
131
|
+
await fs.rm(path, { recursive: true, force: true });
|
|
132
|
+
throw new Error('Cleanup failed');
|
|
133
|
+
};
|
|
134
|
+
provider.logger.warn = async () => {
|
|
135
|
+
resolveWarning();
|
|
136
|
+
};
|
|
137
|
+
try {
|
|
138
|
+
await provider.rimrafMove(tempDirPath);
|
|
139
|
+
await warningHandled;
|
|
140
|
+
} finally {
|
|
141
|
+
provider.rimrafUnlink = originalRimrafUnlink;
|
|
142
|
+
provider.logger.warn = originalWarn;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
117
146
|
it('delete is able to delete file', async function (): Promise<void> {
|
|
118
147
|
this.timeout(10000);
|
|
119
148
|
// Place the temp dir under $HOME so the XDG trash dir (~/.local/share/Trash) is on the same
|
|
@@ -541,7 +541,9 @@ export class DiskFileSystemProvider implements Disposable,
|
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
// Delete but do not return as promise
|
|
544
|
-
this.rimrafUnlink(pathInTemp)
|
|
544
|
+
this.rimrafUnlink(pathInTemp).catch(error => {
|
|
545
|
+
this.logger.warn(`Failed to clean up temporary rimraf folder '${pathInTemp}':`, error);
|
|
546
|
+
});
|
|
545
547
|
} catch (error) {
|
|
546
548
|
if (error.code !== 'ENOENT') {
|
|
547
549
|
throw error;
|
|
@@ -833,7 +835,8 @@ export class DiskFileSystemProvider implements Disposable,
|
|
|
833
835
|
};
|
|
834
836
|
watcherService.watchFileChanges(resource.toString(), {
|
|
835
837
|
// Convert from `files.WatchOptions` to internal `watcher-protocol.WatchOptions`:
|
|
836
|
-
ignored: opts.excludes
|
|
838
|
+
ignored: opts.excludes,
|
|
839
|
+
recursive: opts.recursive
|
|
837
840
|
}).then(watcherId => {
|
|
838
841
|
if (handle.disposed) {
|
|
839
842
|
watcherService.unwatchFileChanges(watcherId);
|
|
@@ -39,7 +39,7 @@ export class FileDownloadCache {
|
|
|
39
39
|
downloadInfo.root = encodeURIComponent(downloadInfo.root);
|
|
40
40
|
}
|
|
41
41
|
// expires in 1 minute enough for parallel connections to be connected.
|
|
42
|
-
downloadInfo.expire = Date.now() + (this.expireTimeInMinutes *
|
|
42
|
+
downloadInfo.expire = Date.now() + (this.expireTimeInMinutes * 60 * 1000);
|
|
43
43
|
this.downloads.set(id, downloadInfo);
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -285,7 +285,6 @@ export class MultiFileDownloadHandler extends FileDownloadHandler {
|
|
|
285
285
|
}
|
|
286
286
|
const options: PrepareDownloadOptions = { filePath: '', downloadId, remove: true, root: outputRootPath };
|
|
287
287
|
if (tarPaths.length === 1) {
|
|
288
|
-
// tslint:disable-next-line:whitespace
|
|
289
288
|
const [outputPath,] = tarPaths;
|
|
290
289
|
options.filePath = outputPath;
|
|
291
290
|
await this.prepareDownload(request, response, options);
|
|
@@ -19,7 +19,8 @@ import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
|
|
|
19
19
|
import { ConnectionHandler, RpcConnectionHandler, ILogger } from '@theia/core/lib/common';
|
|
20
20
|
import { FileSystemWatcherServer, FileSystemWatcherService } from '../common/filesystem-watcher-protocol';
|
|
21
21
|
import { FileSystemWatcherServerClient } from './filesystem-watcher-client';
|
|
22
|
-
import {
|
|
22
|
+
import { FileSystemWatcherServiceImpl } from './filesystem-watcher-service-impl';
|
|
23
|
+
import { ParcelFileSystemWatcherServerOptions } from './parcel-watcher/parcel-watcher';
|
|
23
24
|
import { NodeFileUploadService } from './upload/node-file-upload-service';
|
|
24
25
|
import { ParcelWatcherOptions } from './parcel-watcher/parcel-options';
|
|
25
26
|
import { DiskFileSystemProvider } from './disk-file-system-provider';
|
|
@@ -38,11 +39,11 @@ export const WATCHER_VERBOSE = process.argv.includes('--watcher-verbose');
|
|
|
38
39
|
|
|
39
40
|
export const FileSystemWatcherServiceProcessOptions = Symbol('FileSystemWatcherServiceProcessOptions');
|
|
40
41
|
/**
|
|
41
|
-
* Options to control the way the `
|
|
42
|
+
* Options to control the way the `FileSystemWatcherServiceImpl` process is spawned.
|
|
42
43
|
*/
|
|
43
44
|
export interface FileSystemWatcherServiceProcessOptions {
|
|
44
45
|
/**
|
|
45
|
-
* Path to the script that will run the `
|
|
46
|
+
* Path to the script that will run the `FileSystemWatcherServiceImpl` in a new process.
|
|
46
47
|
*/
|
|
47
48
|
entryPoint: string;
|
|
48
49
|
}
|
|
@@ -102,7 +103,7 @@ export function bindFileSystemWatcherServer(bind: interfaces.Bind): void {
|
|
|
102
103
|
export function createParcelFileSystemWatcherService(ctx: interfaces.Context): FileSystemWatcherService {
|
|
103
104
|
const options = ctx.container.get<ParcelFileSystemWatcherServerOptions>(ParcelFileSystemWatcherServerOptions);
|
|
104
105
|
const dispatcher = ctx.container.get<FileSystemWatcherServiceDispatcher>(FileSystemWatcherServiceDispatcher);
|
|
105
|
-
const server = new
|
|
106
|
+
const server = new FileSystemWatcherServiceImpl(options);
|
|
106
107
|
server.setClient(dispatcher);
|
|
107
108
|
return server;
|
|
108
109
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 Ehab Younes and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as assert from 'assert';
|
|
18
|
+
import { wait } from '@theia/core/lib/common/promise-util';
|
|
19
|
+
import * as temp from 'temp';
|
|
20
|
+
import * as fs from '@theia/core/shared/fs-extra';
|
|
21
|
+
import { isWindows } from '@theia/core';
|
|
22
|
+
import { FileUri } from '@theia/core/lib/node';
|
|
23
|
+
import { DirectoryWatcherProvider, NodeDirectoryWatcher } from './nodejs-watcher/node-directory-watcher';
|
|
24
|
+
import { NO_LOGGING, TempDir, WATCHER_TIMINGS as TIMINGS } from './test/watcher-test-helper';
|
|
25
|
+
import { FileSystemWatcher, WatcherProvider } from './filesystem-watcher';
|
|
26
|
+
import {
|
|
27
|
+
FileSystemWatcherServiceImpl, ParcelFileSystemWatcherServerOptions, ParcelFileSystemWatcherService, ParcelWatcher, RecursiveWatcherProvider
|
|
28
|
+
} from './parcel-watcher/parcel-filesystem-service';
|
|
29
|
+
import { WatchOptions } from '../common/filesystem-watcher-protocol';
|
|
30
|
+
|
|
31
|
+
const track = temp.track();
|
|
32
|
+
|
|
33
|
+
const RECURSIVE: Readonly<WatchOptions> = { ignored: [], recursive: true };
|
|
34
|
+
const NON_RECURSIVE: Readonly<WatchOptions> = { ignored: [], recursive: false };
|
|
35
|
+
|
|
36
|
+
/** Exposes what a provider allocated and who serves what, so a test can tell sharing from double-allocation. */
|
|
37
|
+
interface TestProvider extends WatcherProvider {
|
|
38
|
+
readonly allocated: readonly FileSystemWatcher[];
|
|
39
|
+
watcherOf(watcherId: number): FileSystemWatcher | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class TestDirectoryProvider extends DirectoryWatcherProvider implements TestProvider {
|
|
43
|
+
get allocated(): readonly FileSystemWatcher[] {
|
|
44
|
+
return this.activeWatchers;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
watcherOf(watcherId: number): FileSystemWatcher | undefined {
|
|
48
|
+
return this.watcherFor(watcherId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class TestRecursiveProvider extends RecursiveWatcherProvider implements TestProvider {
|
|
53
|
+
get allocated(): readonly FileSystemWatcher[] {
|
|
54
|
+
return this.activeWatchers;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
watcherOf(watcherId: number): FileSystemWatcher | undefined {
|
|
58
|
+
return this.watcherFor(watcherId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isTestProvider(provider: WatcherProvider): provider is TestProvider {
|
|
63
|
+
return provider instanceof TestDirectoryProvider || provider instanceof TestRecursiveProvider;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A temporary directory and a watcher service on it, one per test. */
|
|
67
|
+
class Sandbox extends FileSystemWatcherServiceImpl {
|
|
68
|
+
|
|
69
|
+
/** The directory the requests of this test point into. */
|
|
70
|
+
readonly files = new TempDir(fs.realpathSync.native(temp.mkdirSync('filesystem-watcher-service')));
|
|
71
|
+
|
|
72
|
+
protected readonly requested: number[] = [];
|
|
73
|
+
|
|
74
|
+
constructor() {
|
|
75
|
+
super(NO_LOGGING);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get root(): string {
|
|
79
|
+
return this.files.root;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
watch(fsPath: string, options: WatchOptions): Promise<number> {
|
|
83
|
+
return this.watchFileChanges(1, FileUri.create(fsPath).toString(), options)
|
|
84
|
+
.then(watcherId => (this.requested.push(watcherId), watcherId));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The watchers currently allocated, one per distinct key, across both providers. */
|
|
88
|
+
get allocated(): readonly FileSystemWatcher[] {
|
|
89
|
+
return this.testProviders.flatMap(provider => provider.allocated);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected get testProviders(): TestProvider[] {
|
|
93
|
+
return this.providers.filter(isTestProvider);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The watcher serving a request, asked of whichever provider holds it. */
|
|
97
|
+
watcherOf(watcherId: number): FileSystemWatcher | undefined {
|
|
98
|
+
return this.testProviders.map(provider => provider.watcherOf(watcherId)).find(Boolean);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async release(): Promise<void> {
|
|
102
|
+
const outstanding = this.requested.splice(0).filter(watcherId => this.watcherOf(watcherId));
|
|
103
|
+
const allocated = this.allocated;
|
|
104
|
+
await Promise.all(outstanding.map(watcherId => this.unwatchFileChanges(watcherId)));
|
|
105
|
+
await Promise.all(allocated.map(watcher => watcher.whenDisposed));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected override createProviders(options: ParcelFileSystemWatcherServerOptions): WatcherProvider[] {
|
|
109
|
+
return [
|
|
110
|
+
new TestRecursiveProvider(options, this.maybeClient, TIMINGS.deferredDisposalTimeout),
|
|
111
|
+
new TestDirectoryProvider(options, this.maybeClient, TIMINGS)
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
describe('filesystem-watcher-service', function (): void {
|
|
117
|
+
|
|
118
|
+
this.timeout(20000);
|
|
119
|
+
|
|
120
|
+
let box: Sandbox;
|
|
121
|
+
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
box = new Sandbox();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
afterEach(async () => {
|
|
127
|
+
await box.release();
|
|
128
|
+
track.cleanupSync();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('routing', () => {
|
|
132
|
+
|
|
133
|
+
it('keeps the deprecated service name usable by adopters', () => {
|
|
134
|
+
assert.strictEqual(ParcelFileSystemWatcherService, FileSystemWatcherServiceImpl);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('serves a recursive request, and one that does not say, with the parcel watcher', async () => {
|
|
138
|
+
|
|
139
|
+
const recursive = await box.watch(box.root, RECURSIVE);
|
|
140
|
+
const unspecified = await box.watch(box.files.mkdir('other'), { ignored: [] });
|
|
141
|
+
|
|
142
|
+
assert.ok(box.watcherOf(recursive) instanceof ParcelWatcher);
|
|
143
|
+
assert.ok(box.watcherOf(unspecified) instanceof ParcelWatcher);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('serves a non-recursive request with a directory watcher', async () => {
|
|
147
|
+
|
|
148
|
+
const watcherId = await box.watch(box.root, NON_RECURSIVE);
|
|
149
|
+
|
|
150
|
+
assert.ok(box.watcherOf(watcherId) instanceof NodeDirectoryWatcher);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('keeps a recursive and a non-recursive request on the same path apart', async () => {
|
|
154
|
+
|
|
155
|
+
const recursive = await box.watch(box.root, RECURSIVE);
|
|
156
|
+
const nonRecursive = await box.watch(box.root, NON_RECURSIVE);
|
|
157
|
+
|
|
158
|
+
assert.notStrictEqual(box.watcherOf(recursive), box.watcherOf(nonRecursive));
|
|
159
|
+
assert.strictEqual(box.allocated.length, 2);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('sharing', () => {
|
|
164
|
+
|
|
165
|
+
it('shares one watcher between a directory and the files inside it', async () => {
|
|
166
|
+
|
|
167
|
+
const directory = await box.watch(box.root, NON_RECURSIVE);
|
|
168
|
+
const first = await box.watch(box.files.write('a.txt'), NON_RECURSIVE);
|
|
169
|
+
const second = await box.watch(box.files.write('b.txt'), NON_RECURSIVE);
|
|
170
|
+
|
|
171
|
+
assert.strictEqual(box.allocated.length, 1);
|
|
172
|
+
assert.strictEqual(box.watcherOf(directory), box.watcherOf(first));
|
|
173
|
+
assert.strictEqual(box.watcherOf(first), box.watcherOf(second));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('shares one watcher between requests with different excludes', async () => {
|
|
177
|
+
|
|
178
|
+
await box.watch(box.root, { ignored: ['**/node_modules'], recursive: false });
|
|
179
|
+
await box.watch(box.root, NON_RECURSIVE);
|
|
180
|
+
|
|
181
|
+
assert.strictEqual(box.allocated.length, 1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('shares one watcher between a directory and a symbolic link to it', async () => {
|
|
185
|
+
const real = box.files.mkdir('real');
|
|
186
|
+
fs.symlinkSync(real, box.files.path('link'), isWindows ? 'junction' : 'dir');
|
|
187
|
+
|
|
188
|
+
const direct = await box.watch(real, NON_RECURSIVE);
|
|
189
|
+
const linked = await box.watch(box.files.path('link'), NON_RECURSIVE);
|
|
190
|
+
|
|
191
|
+
assert.strictEqual(box.allocated.length, 1);
|
|
192
|
+
assert.strictEqual(box.watcherOf(direct), box.watcherOf(linked));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('shares one watcher between concurrent requests for the same directory', async () => {
|
|
196
|
+
const [first, second] = [box.files.write('a.txt'), box.files.write('b.txt')];
|
|
197
|
+
|
|
198
|
+
const [one, two] = await Promise.all([box.watch(first, NON_RECURSIVE), box.watch(second, NON_RECURSIVE)]);
|
|
199
|
+
|
|
200
|
+
assert.strictEqual(box.allocated.length, 1);
|
|
201
|
+
assert.strictEqual(box.watcherOf(one), box.watcherOf(two));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('moves a watcher to the parent once its path turns out to be a file', async () => {
|
|
205
|
+
const pending = await box.watch(box.files.path('later.txt'), NON_RECURSIVE);
|
|
206
|
+
box.files.write('later.txt');
|
|
207
|
+
await (box.watcherOf(pending) as NodeDirectoryWatcher).whenStarted;
|
|
208
|
+
|
|
209
|
+
const directory = await box.watch(box.root, NON_RECURSIVE);
|
|
210
|
+
|
|
211
|
+
assert.strictEqual(box.allocated.length, 1);
|
|
212
|
+
assert.strictEqual(box.watcherOf(pending), box.watcherOf(directory));
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('merges two watchers whose paths turn out to be files in one directory', async () => {
|
|
216
|
+
const [one, two] = await Promise.all([
|
|
217
|
+
box.watch(box.files.path('a.txt'), NON_RECURSIVE),
|
|
218
|
+
box.watch(box.files.path('b.txt'), NON_RECURSIVE)
|
|
219
|
+
]);
|
|
220
|
+
box.files.write('a.txt');
|
|
221
|
+
box.files.write('b.txt');
|
|
222
|
+
await Promise.all([one, two].map(watcherId => (box.watcherOf(watcherId) as NodeDirectoryWatcher).whenStarted));
|
|
223
|
+
|
|
224
|
+
assert.strictEqual(box.allocated.length, 1);
|
|
225
|
+
assert.strictEqual(box.watcherOf(one), box.watcherOf(two));
|
|
226
|
+
|
|
227
|
+
// The handles have to follow the merge, or the moved request would never be released.
|
|
228
|
+
const survivor = box.watcherOf(one) as NodeDirectoryWatcher;
|
|
229
|
+
await Promise.all([one, two].map(watcherId => box.unwatchFileChanges(watcherId)));
|
|
230
|
+
await survivor.whenDisposed;
|
|
231
|
+
assert.strictEqual(box.allocated.length, 0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('does not attach a request to a watcher that is already disposed', async () => {
|
|
235
|
+
const first = await box.watch(box.root, NON_RECURSIVE);
|
|
236
|
+
const disposed = box.watcherOf(first) as NodeDirectoryWatcher;
|
|
237
|
+
disposed.dispose();
|
|
238
|
+
|
|
239
|
+
const second = await box.watch(box.root, NON_RECURSIVE);
|
|
240
|
+
|
|
241
|
+
assert.notStrictEqual(box.watcherOf(second), disposed);
|
|
242
|
+
assert.strictEqual(box.allocated.length, 1);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('releasing', () => {
|
|
247
|
+
|
|
248
|
+
it('disposes a directory watcher once its last request is unwatched', async () => {
|
|
249
|
+
const first = await box.watch(box.root, NON_RECURSIVE);
|
|
250
|
+
const second = await box.watch(box.root, NON_RECURSIVE);
|
|
251
|
+
const watcher = box.watcherOf(first) as NodeDirectoryWatcher;
|
|
252
|
+
|
|
253
|
+
await box.unwatchFileChanges(first);
|
|
254
|
+
await wait(TIMINGS.deferredDisposalTimeout * 2);
|
|
255
|
+
assert.strictEqual(watcher.isDisposed, false, 'the remaining request must keep the watcher alive');
|
|
256
|
+
|
|
257
|
+
await box.unwatchFileChanges(second);
|
|
258
|
+
await watcher.whenDisposed;
|
|
259
|
+
assert.strictEqual(box.allocated.length, 0);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('releases the client reference of a recursive watcher', async () => {
|
|
263
|
+
const watcherId = await box.watch(box.root, RECURSIVE);
|
|
264
|
+
const watcher = box.watcherOf(watcherId) as ParcelWatcher;
|
|
265
|
+
|
|
266
|
+
await box.unwatchFileChanges(watcherId);
|
|
267
|
+
await watcher.whenDisposed;
|
|
268
|
+
|
|
269
|
+
assert.strictEqual(box.allocated.length, 0);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2017-2018 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { FileUri } from '@theia/core/lib/common/file-uri';
|
|
18
|
+
import { FileSystemWatcherService, FileSystemWatcherServiceClient, WatchOptions } from '../common/filesystem-watcher-protocol';
|
|
19
|
+
import { ResolvedWatchOptions, WatcherLogger, WatcherProvider } from './filesystem-watcher';
|
|
20
|
+
import { DirectoryWatcherProvider } from './nodejs-watcher/node-directory-watcher';
|
|
21
|
+
import { ParcelFileSystemWatcherServerOptions, RecursiveWatcherProvider } from './parcel-watcher/parcel-watcher';
|
|
22
|
+
|
|
23
|
+
/** Routes each request to the first provider that can handle it. The provider picks the watcher that serves it. */
|
|
24
|
+
export class FileSystemWatcherServiceImpl implements FileSystemWatcherService {
|
|
25
|
+
|
|
26
|
+
protected client: FileSystemWatcherServiceClient | undefined;
|
|
27
|
+
|
|
28
|
+
protected watcherId = 0;
|
|
29
|
+
|
|
30
|
+
protected readonly logger: WatcherLogger;
|
|
31
|
+
|
|
32
|
+
/** Checked in order. The first one whose `canHandle` returns `true` serves the request. */
|
|
33
|
+
protected readonly providers: readonly WatcherProvider[];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `this.client` is undefined until someone sets it.
|
|
37
|
+
*/
|
|
38
|
+
protected readonly maybeClient: FileSystemWatcherServiceClient = {
|
|
39
|
+
onDidFilesChanged: event => this.client?.onDidFilesChanged(event),
|
|
40
|
+
onError: event => this.client?.onError(event),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
constructor(options?: Partial<ParcelFileSystemWatcherServerOptions>) {
|
|
44
|
+
const resolved: ParcelFileSystemWatcherServerOptions = {
|
|
45
|
+
parcelOptions: {},
|
|
46
|
+
verbose: false,
|
|
47
|
+
info: (message, ...args) => console.info(message, ...args),
|
|
48
|
+
error: (message, ...args) => console.error(message, ...args),
|
|
49
|
+
...options
|
|
50
|
+
};
|
|
51
|
+
this.logger = resolved;
|
|
52
|
+
this.providers = this.createProviders(resolved);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setClient(client: FileSystemWatcherServiceClient | undefined): void {
|
|
56
|
+
this.client = client;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Requests resolving to the same target share a watcher. */
|
|
60
|
+
async watchFileChanges(clientId: number, uri: string, options?: WatchOptions): Promise<number> {
|
|
61
|
+
const resolvedOptions = this.resolveWatchOptions(options);
|
|
62
|
+
const provider = this.providerFor(resolvedOptions);
|
|
63
|
+
const watcherId = this.watcherId++;
|
|
64
|
+
await provider.watch(watcherId, { clientId, path: FileUri.fsPath(uri), ignored: resolvedOptions.ignored });
|
|
65
|
+
return watcherId;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async unwatchFileChanges(watcherId: number): Promise<void> {
|
|
69
|
+
if (!this.providers.some(provider => provider.unwatch(watcherId))) {
|
|
70
|
+
console.warn(`tried to de-allocate a disposed watcher: watcherId=${watcherId}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** `options` keeps the parcel shape that adopters already bind. Only the recursive provider reads `parcelOptions`. */
|
|
75
|
+
protected createProviders(options: ParcelFileSystemWatcherServerOptions): WatcherProvider[] {
|
|
76
|
+
return [
|
|
77
|
+
new RecursiveWatcherProvider(options, this.maybeClient),
|
|
78
|
+
new DirectoryWatcherProvider(options, this.maybeClient)
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected providerFor(options: ResolvedWatchOptions): WatcherProvider {
|
|
83
|
+
const provider = this.providers.find(candidate => candidate.canHandle(options));
|
|
84
|
+
if (!provider) {
|
|
85
|
+
throw new Error(`No watcher provider takes options: ${JSON.stringify(options)}`);
|
|
86
|
+
}
|
|
87
|
+
return provider;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Return fully qualified options. Watchers created before `recursive` existed were always recursive.
|
|
92
|
+
*/
|
|
93
|
+
protected resolveWatchOptions(options?: WatchOptions): ResolvedWatchOptions {
|
|
94
|
+
return {
|
|
95
|
+
ignored: options?.ignored ?? [],
|
|
96
|
+
recursive: options?.recursive ?? true
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected debug(message: string, ...params: unknown[]): void {
|
|
101
|
+
if (this.logger.verbose) {
|
|
102
|
+
this.logger.info(message, ...params);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
dispose(): void {
|
|
107
|
+
// Singletons shouldn't be disposed...
|
|
108
|
+
}
|
|
109
|
+
}
|