@theia/filesystem 1.55.1 → 1.57.0-next.112
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/README.md +2 -1
- package/lib/browser/breadcrumbs/filepath-breadcrumbs-container.d.ts +1 -2
- package/lib/browser/breadcrumbs/filepath-breadcrumbs-container.d.ts.map +1 -1
- package/lib/browser/breadcrumbs/filepath-breadcrumbs-container.js +0 -4
- package/lib/browser/breadcrumbs/filepath-breadcrumbs-container.js.map +1 -1
- package/lib/browser/filesystem-frontend-module.d.ts.map +1 -1
- package/lib/browser/filesystem-frontend-module.js +4 -0
- package/lib/browser/filesystem-frontend-module.js.map +1 -1
- package/lib/browser/filesystem-preferences.d.ts +1 -0
- package/lib/browser/filesystem-preferences.d.ts.map +1 -1
- package/lib/browser/filesystem-preferences.js +6 -0
- package/lib/browser/filesystem-preferences.js.map +1 -1
- package/lib/browser/vscode-file-service-contribution.d.ts +25 -0
- package/lib/browser/vscode-file-service-contribution.d.ts.map +1 -0
- package/lib/browser/vscode-file-service-contribution.js +99 -0
- package/lib/browser/vscode-file-service-contribution.js.map +1 -0
- package/lib/browser-only/browser-only-filesystem-frontend-module.js +7 -7
- package/lib/browser-only/browser-only-filesystem-frontend-module.js.map +1 -1
- package/lib/browser-only/opfs-filesystem-initialization.d.ts +11 -0
- package/lib/browser-only/opfs-filesystem-initialization.d.ts.map +1 -0
- package/lib/browser-only/opfs-filesystem-initialization.js +33 -0
- package/lib/browser-only/opfs-filesystem-initialization.js.map +1 -0
- package/lib/browser-only/opfs-filesystem-provider.d.ts +31 -0
- package/lib/browser-only/opfs-filesystem-provider.d.ts.map +1 -0
- package/lib/browser-only/opfs-filesystem-provider.js +323 -0
- package/lib/browser-only/opfs-filesystem-provider.js.map +1 -0
- package/lib/common/remote-file-system-provider.d.ts +3 -3
- package/lib/common/remote-file-system-provider.d.ts.map +1 -1
- package/lib/common/remote-file-system-provider.js +28 -29
- package/lib/common/remote-file-system-provider.js.map +1 -1
- package/lib/node/download/file-download-endpoint.d.ts +0 -1
- package/lib/node/download/file-download-endpoint.d.ts.map +1 -1
- package/lib/node/download/file-download-handler.d.ts +0 -1
- package/lib/node/download/file-download-handler.d.ts.map +1 -1
- package/lib/node/node-file-upload-service.d.ts +0 -1
- package/lib/node/node-file-upload-service.d.ts.map +1 -1
- package/package.json +4 -5
- package/src/browser/breadcrumbs/filepath-breadcrumbs-container.ts +1 -4
- package/src/browser/filesystem-frontend-module.ts +4 -0
- package/src/browser/filesystem-preferences.ts +7 -0
- package/src/browser/vscode-file-service-contribution.ts +93 -0
- package/src/browser-only/browser-only-filesystem-frontend-module.ts +7 -7
- package/src/browser-only/opfs-filesystem-initialization.ts +36 -0
- package/src/browser-only/opfs-filesystem-provider.ts +346 -0
- package/src/common/download/README.md +8 -4
- package/src/common/remote-file-system-provider.ts +32 -34
- package/lib/browser-only/browserfs-filesystem-initialization.d.ts +0 -13
- package/lib/browser-only/browserfs-filesystem-initialization.d.ts.map +0 -1
- package/lib/browser-only/browserfs-filesystem-initialization.js +0 -55
- package/lib/browser-only/browserfs-filesystem-initialization.js.map +0 -1
- package/lib/browser-only/browserfs-filesystem-provider.d.ts +0 -46
- package/lib/browser-only/browserfs-filesystem-provider.d.ts.map +0 -1
- package/lib/browser-only/browserfs-filesystem-provider.js +0 -440
- package/lib/browser-only/browserfs-filesystem-provider.js.map +0 -1
- package/src/browser-only/browserfs-filesystem-initialization.ts +0 -61
- package/src/browser-only/browserfs-filesystem-provider.ts +0 -462
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/filesystem",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.57.0-next.112+f4778c273",
|
|
4
4
|
"description": "Theia - FileSystem Extension",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@theia/core": "1.
|
|
6
|
+
"@theia/core": "1.57.0-next.112+f4778c273",
|
|
7
7
|
"@types/body-parser": "^1.17.0",
|
|
8
8
|
"@types/multer": "^1.4.7",
|
|
9
9
|
"@types/tar-fs": "^1.16.1",
|
|
10
10
|
"async-mutex": "^0.3.1",
|
|
11
11
|
"body-parser": "^1.18.3",
|
|
12
|
-
"browserfs": "^1.4.3",
|
|
13
12
|
"http-status-codes": "^1.3.0",
|
|
14
13
|
"minimatch": "^5.1.0",
|
|
15
14
|
"multer": "1.4.4-lts.1",
|
|
@@ -72,10 +71,10 @@
|
|
|
72
71
|
"watch": "theiaext watch"
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|
|
75
|
-
"@theia/ext-scripts": "1.
|
|
74
|
+
"@theia/ext-scripts": "1.58.0"
|
|
76
75
|
},
|
|
77
76
|
"nyc": {
|
|
78
77
|
"extends": "../../configs/nyc.json"
|
|
79
78
|
},
|
|
80
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "f4778c2737bb75613f0e1f99da8996bad91f6e17"
|
|
81
80
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { Container, interfaces, injectable, inject } from '@theia/core/shared/inversify';
|
|
18
|
-
import { TreeProps, ContextMenuRenderer, TreeNode,
|
|
18
|
+
import { TreeProps, ContextMenuRenderer, TreeNode, open, NodeProps, defaultTreeProps } from '@theia/core/lib/browser';
|
|
19
19
|
import { FileTreeModel, FileStatNode, createFileTreeContainer, FileTreeWidget } from '../file-tree';
|
|
20
20
|
|
|
21
21
|
const BREADCRUMBS_FILETREE_CLASS = 'theia-FilepathBreadcrumbFileTree';
|
|
@@ -35,9 +35,6 @@ export function createFileTreeBreadcrumbsWidget(parent: interfaces.Container): B
|
|
|
35
35
|
@injectable()
|
|
36
36
|
export class BreadcrumbsFileTreeWidget extends FileTreeWidget {
|
|
37
37
|
|
|
38
|
-
@inject(OpenerService)
|
|
39
|
-
protected readonly openerService: OpenerService;
|
|
40
|
-
|
|
41
38
|
constructor(
|
|
42
39
|
@inject(TreeProps) props: TreeProps,
|
|
43
40
|
@inject(FileTreeModel) override readonly model: FileTreeModel,
|
|
@@ -33,6 +33,7 @@ import { FilepathBreadcrumbsContribution } from './breadcrumbs/filepath-breadcru
|
|
|
33
33
|
import { BreadcrumbsFileTreeWidget, createFileTreeBreadcrumbsWidget } from './breadcrumbs/filepath-breadcrumbs-container';
|
|
34
34
|
import { FilesystemSaveableService } from './filesystem-saveable-service';
|
|
35
35
|
import { SaveableService } from '@theia/core/lib/browser/saveable-service';
|
|
36
|
+
import { VSCodeFileServiceContribution, VSCodeFileSystemProvider } from './vscode-file-service-contribution';
|
|
36
37
|
|
|
37
38
|
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
38
39
|
bindFileSystemPreferences(bind);
|
|
@@ -46,6 +47,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
|
46
47
|
bind(RemoteFileSystemProvider).toSelf().inSingletonScope();
|
|
47
48
|
bind(RemoteFileServiceContribution).toSelf().inSingletonScope();
|
|
48
49
|
bind(FileServiceContribution).toService(RemoteFileServiceContribution);
|
|
50
|
+
bind(VSCodeFileSystemProvider).toSelf().inSingletonScope();
|
|
51
|
+
bind(VSCodeFileServiceContribution).toSelf().inSingletonScope();
|
|
52
|
+
bind(FileServiceContribution).toService(VSCodeFileServiceContribution);
|
|
49
53
|
|
|
50
54
|
bind(FileSystemWatcherErrorHandler).toSelf().inSingletonScope();
|
|
51
55
|
|
|
@@ -95,6 +95,12 @@ export const filesystemPreferenceSchema: PreferenceSchema = {
|
|
|
95
95
|
description: nls.localizeByDefault('When enabled, will trim trailing whitespace when saving a file.'),
|
|
96
96
|
scope: 'language-overridable'
|
|
97
97
|
},
|
|
98
|
+
'files.insertFinalNewline': {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
default: false,
|
|
101
|
+
description: nls.localizeByDefault('When enabled, insert a final new line at the end of the file when saving it.'),
|
|
102
|
+
scope: 'language-overridable'
|
|
103
|
+
},
|
|
98
104
|
'files.maxConcurrentUploads': {
|
|
99
105
|
type: 'integer',
|
|
100
106
|
default: 1,
|
|
@@ -116,6 +122,7 @@ export interface FileSystemConfiguration {
|
|
|
116
122
|
'files.participants.timeout': number
|
|
117
123
|
'files.maxFileSizeMB': number
|
|
118
124
|
'files.trimTrailingWhitespace': boolean
|
|
125
|
+
'files.insertFinalNewline': boolean
|
|
119
126
|
'files.maxConcurrentUploads': number
|
|
120
127
|
}
|
|
121
128
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 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 { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
+
import { FileServiceContribution, FileService } from './file-service';
|
|
19
|
+
import {
|
|
20
|
+
FileChange, FileDeleteOptions, FileOverwriteOptions, FilePermission, FileSystemProvider, FileSystemProviderCapabilities, FileType, FileWriteOptions, Stat, WatchOptions
|
|
21
|
+
} from '../common/files';
|
|
22
|
+
import { Event, URI, Disposable, Emitter } from '@theia/core';
|
|
23
|
+
import { JsonSchemaDataStore } from '@theia/core/lib/browser/json-schema-store';
|
|
24
|
+
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
|
|
25
|
+
|
|
26
|
+
@injectable()
|
|
27
|
+
export class VSCodeFileSystemProvider implements FileSystemProvider {
|
|
28
|
+
readonly capabilities = FileSystemProviderCapabilities.Readonly + FileSystemProviderCapabilities.FileReadWrite;
|
|
29
|
+
readonly onDidChangeCapabilities = Event.None;
|
|
30
|
+
protected readonly onDidChangeFileEmitter = new Emitter<readonly FileChange[]>();
|
|
31
|
+
readonly onDidChangeFile = this.onDidChangeFileEmitter.event;
|
|
32
|
+
readonly onFileWatchError = Event.None;
|
|
33
|
+
|
|
34
|
+
@inject(JsonSchemaDataStore)
|
|
35
|
+
protected readonly store: JsonSchemaDataStore;
|
|
36
|
+
|
|
37
|
+
watch(resource: URI, opts: WatchOptions): Disposable {
|
|
38
|
+
return Disposable.NULL;
|
|
39
|
+
}
|
|
40
|
+
async stat(resource: URI): Promise<Stat> {
|
|
41
|
+
if (this.store.hasSchema(resource)) {
|
|
42
|
+
const currentTime = Date.now();
|
|
43
|
+
return {
|
|
44
|
+
type: FileType.File,
|
|
45
|
+
permissions: FilePermission.Readonly,
|
|
46
|
+
mtime: currentTime,
|
|
47
|
+
ctime: currentTime,
|
|
48
|
+
size: 0
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
throw new Error('Not Found!');
|
|
52
|
+
}
|
|
53
|
+
mkdir(resource: URI): Promise<void> {
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
}
|
|
56
|
+
readdir(resource: URI): Promise<[string, FileType][]> {
|
|
57
|
+
return Promise.resolve([]);
|
|
58
|
+
}
|
|
59
|
+
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
|
|
60
|
+
return Promise.resolve();
|
|
61
|
+
}
|
|
62
|
+
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
}
|
|
65
|
+
async readFile(resource: URI): Promise<Uint8Array> {
|
|
66
|
+
if (resource.scheme !== 'vscode') {
|
|
67
|
+
throw new Error('Not Supported!');
|
|
68
|
+
}
|
|
69
|
+
let content: string | undefined;
|
|
70
|
+
if (resource.authority === 'schemas') {
|
|
71
|
+
content = this.store.getSchema(resource);
|
|
72
|
+
}
|
|
73
|
+
if (typeof content === 'string') {
|
|
74
|
+
return BinaryBuffer.fromString(content).buffer;
|
|
75
|
+
}
|
|
76
|
+
throw new Error('Not Found!');
|
|
77
|
+
}
|
|
78
|
+
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
|
|
79
|
+
throw new Error('Not Supported!');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@injectable()
|
|
84
|
+
export class VSCodeFileServiceContribution implements FileServiceContribution {
|
|
85
|
+
|
|
86
|
+
@inject(VSCodeFileSystemProvider)
|
|
87
|
+
protected readonly provider: VSCodeFileSystemProvider;
|
|
88
|
+
|
|
89
|
+
registerFileSystemProviders(service: FileService): void {
|
|
90
|
+
service.registerProvider('vscode', this.provider);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
}
|
|
@@ -16,19 +16,19 @@
|
|
|
16
16
|
|
|
17
17
|
import { ContainerModule } from '@theia/core/shared/inversify';
|
|
18
18
|
import { FileSystemProvider } from '../common/files';
|
|
19
|
-
import {
|
|
19
|
+
import { OPFSFileSystemProvider } from './opfs-filesystem-provider';
|
|
20
20
|
import { RemoteFileSystemProvider, RemoteFileSystemServer } from '../common/remote-file-system-provider';
|
|
21
|
-
import {
|
|
21
|
+
import { OPFSInitialization, DefaultOPFSInitialization } from './opfs-filesystem-initialization';
|
|
22
22
|
import { BrowserOnlyFileSystemProviderServer } from './browser-only-filesystem-provider-server';
|
|
23
23
|
|
|
24
24
|
export default new ContainerModule((bind, _unbind, isBound, rebind) => {
|
|
25
|
-
bind(
|
|
26
|
-
bind(
|
|
27
|
-
bind(
|
|
25
|
+
bind(DefaultOPFSInitialization).toSelf();
|
|
26
|
+
bind(OPFSFileSystemProvider).toSelf();
|
|
27
|
+
bind(OPFSInitialization).toService(DefaultOPFSInitialization);
|
|
28
28
|
if (isBound(FileSystemProvider)) {
|
|
29
|
-
rebind(FileSystemProvider).to(
|
|
29
|
+
rebind(FileSystemProvider).to(OPFSFileSystemProvider).inSingletonScope();
|
|
30
30
|
} else {
|
|
31
|
-
bind(FileSystemProvider).to(
|
|
31
|
+
bind(FileSystemProvider).to(OPFSFileSystemProvider).inSingletonScope();
|
|
32
32
|
}
|
|
33
33
|
if (isBound(RemoteFileSystemProvider)) {
|
|
34
34
|
rebind(RemoteFileSystemServer).to(BrowserOnlyFileSystemProviderServer).inSingletonScope();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2023 EclipseSource 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 type { OPFSFileSystemProvider } from './opfs-filesystem-provider';
|
|
18
|
+
import { injectable } from '@theia/core/shared/inversify';
|
|
19
|
+
|
|
20
|
+
export const OPFSInitialization = Symbol('OPFSInitialization');
|
|
21
|
+
export interface OPFSInitialization {
|
|
22
|
+
getRootDirectory(): Promise<FileSystemDirectoryHandle>
|
|
23
|
+
initializeFS(provider: OPFSFileSystemProvider): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@injectable()
|
|
27
|
+
export class DefaultOPFSInitialization implements OPFSInitialization {
|
|
28
|
+
|
|
29
|
+
getRootDirectory(): Promise<FileSystemDirectoryHandle> {
|
|
30
|
+
return navigator.storage.getDirectory();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async initializeFS(provider: OPFSFileSystemProvider): Promise<void> {
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource 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 { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import {
|
|
19
|
+
FileChange, FileChangeType, FileDeleteOptions,
|
|
20
|
+
FileOverwriteOptions, FileSystemProviderCapabilities,
|
|
21
|
+
FileSystemProviderError,
|
|
22
|
+
FileSystemProviderErrorCode,
|
|
23
|
+
FileSystemProviderWithFileReadWriteCapability,
|
|
24
|
+
FileType, FileWriteOptions, Stat, WatchOptions, createFileSystemProviderError
|
|
25
|
+
} from '../common/files';
|
|
26
|
+
import { Emitter, Event, URI, Disposable, Path } from '@theia/core';
|
|
27
|
+
import { OPFSInitialization } from './opfs-filesystem-initialization';
|
|
28
|
+
|
|
29
|
+
/** Options to be used when traversing the file system handles */
|
|
30
|
+
interface CreateFileSystemHandleOptions {
|
|
31
|
+
isDirectory?: boolean;
|
|
32
|
+
create?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@injectable()
|
|
36
|
+
export class OPFSFileSystemProvider implements FileSystemProviderWithFileReadWriteCapability {
|
|
37
|
+
capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite;
|
|
38
|
+
onDidChangeCapabilities: Event<void> = Event.None;
|
|
39
|
+
|
|
40
|
+
private readonly onDidChangeFileEmitter = new Emitter<readonly FileChange[]>();
|
|
41
|
+
readonly onDidChangeFile = this.onDidChangeFileEmitter.event;
|
|
42
|
+
onFileWatchError: Event<void> = Event.None;
|
|
43
|
+
|
|
44
|
+
@inject(OPFSInitialization)
|
|
45
|
+
protected readonly initialization: OPFSInitialization;
|
|
46
|
+
|
|
47
|
+
private directoryHandle: FileSystemDirectoryHandle;
|
|
48
|
+
private initialized: Promise<true>;
|
|
49
|
+
|
|
50
|
+
@postConstruct()
|
|
51
|
+
protected init(): void {
|
|
52
|
+
const setup = async (): Promise<true> => {
|
|
53
|
+
this.directoryHandle = await this.initialization.getRootDirectory();
|
|
54
|
+
await this.initialization.initializeFS(new Proxy(this, {
|
|
55
|
+
get(target, prop, receiver): unknown {
|
|
56
|
+
if (prop === 'initialized') {
|
|
57
|
+
return Promise.resolve(true);
|
|
58
|
+
}
|
|
59
|
+
return Reflect.get(target, prop, receiver);
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
this.initialized = setup();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
watch(_resource: URI, _opts: WatchOptions): Disposable {
|
|
68
|
+
return Disposable.NULL;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async exists(resource: URI): Promise<boolean> {
|
|
72
|
+
try {
|
|
73
|
+
await this.initialized;
|
|
74
|
+
await this.toFileSystemHandle(resource);
|
|
75
|
+
return true;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async stat(resource: URI): Promise<Stat> {
|
|
82
|
+
try {
|
|
83
|
+
await this.initialized;
|
|
84
|
+
|
|
85
|
+
const handle = await this.toFileSystemHandle(resource);
|
|
86
|
+
|
|
87
|
+
if (handle.kind === 'file') {
|
|
88
|
+
const fileHandle = handle as FileSystemFileHandle;
|
|
89
|
+
const file = await fileHandle.getFile();
|
|
90
|
+
return {
|
|
91
|
+
type: FileType.File,
|
|
92
|
+
ctime: file.lastModified,
|
|
93
|
+
mtime: file.lastModified,
|
|
94
|
+
size: file.size
|
|
95
|
+
};
|
|
96
|
+
} else if (handle.kind === 'directory') {
|
|
97
|
+
return {
|
|
98
|
+
type: FileType.Directory,
|
|
99
|
+
ctime: 0,
|
|
100
|
+
mtime: 0,
|
|
101
|
+
size: 0
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw createFileSystemProviderError('Unknown file handle error', FileSystemProviderErrorCode.Unknown);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw toFileSystemProviderError(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async mkdir(resource: URI): Promise<void> {
|
|
112
|
+
await this.initialized;
|
|
113
|
+
try {
|
|
114
|
+
await this.toFileSystemHandle(resource, { create: true, isDirectory: true });
|
|
115
|
+
this.onDidChangeFileEmitter.fire([{ resource, type: FileChangeType.ADDED }]);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
throw toFileSystemProviderError(error, true);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async readdir(resource: URI): Promise<[string, FileType][]> {
|
|
122
|
+
await this.initialized;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// Get the directory handle from the directoryHandle
|
|
126
|
+
const directoryHandle = await this.toFileSystemHandle(resource, { create: false, isDirectory: true }) as FileSystemDirectoryHandle;
|
|
127
|
+
|
|
128
|
+
const result: [string, FileType][] = [];
|
|
129
|
+
|
|
130
|
+
// Iterate through the entries in the directory (files and subdirectories)
|
|
131
|
+
for await (const [name, handle] of directoryHandle.entries()) {
|
|
132
|
+
// Determine the type of the entry (file or directory)
|
|
133
|
+
if (handle.kind === 'file') {
|
|
134
|
+
result.push([name, FileType.File]);
|
|
135
|
+
} else if (handle.kind === 'directory') {
|
|
136
|
+
result.push([name, FileType.Directory]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
throw toFileSystemProviderError(error, true);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async delete(resource: URI, _opts: FileDeleteOptions): Promise<void> {
|
|
147
|
+
await this.initialized;
|
|
148
|
+
try {
|
|
149
|
+
const parentURI = resource.parent;
|
|
150
|
+
const parentHandle = await this.toFileSystemHandle(parentURI, { create: false, isDirectory: true });
|
|
151
|
+
if (parentHandle.kind !== 'directory') {
|
|
152
|
+
throw createFileSystemProviderError(new Error('Parent is not a directory'), FileSystemProviderErrorCode.FileNotADirectory);
|
|
153
|
+
}
|
|
154
|
+
const name = resource.path.base;
|
|
155
|
+
return (parentHandle as FileSystemDirectoryHandle).removeEntry(name, { recursive: _opts.recursive });
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw toFileSystemProviderError(error);
|
|
158
|
+
} finally {
|
|
159
|
+
this.onDidChangeFileEmitter.fire([{ resource, type: FileChangeType.DELETED }]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
|
|
164
|
+
await this.initialized;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const fromHandle = await this.toFileSystemHandle(from);
|
|
168
|
+
// Check whether the source is a file or directory
|
|
169
|
+
if (fromHandle.kind === 'directory') {
|
|
170
|
+
// Create the new directory and get the handle
|
|
171
|
+
await this.mkdir(to);
|
|
172
|
+
const toHandle = await this.toFileSystemHandle(to) as FileSystemDirectoryHandle;
|
|
173
|
+
await copyDirectoryContents(fromHandle as FileSystemDirectoryHandle, toHandle);
|
|
174
|
+
|
|
175
|
+
// Delete the old directory
|
|
176
|
+
await this.delete(from, { recursive: true, useTrash: false });
|
|
177
|
+
} else {
|
|
178
|
+
const content = await this.readFile(from);
|
|
179
|
+
await this.writeFile(to, content, { create: true, overwrite: opts.overwrite });
|
|
180
|
+
await this.delete(from, { recursive: true, useTrash: false });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.onDidChangeFileEmitter.fire([{ resource: to, type: FileChangeType.ADDED }]);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw toFileSystemProviderError(error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async readFile(resource: URI): Promise<Uint8Array> {
|
|
190
|
+
await this.initialized;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
// Get the file handle from the directoryHandle
|
|
194
|
+
const fileHandle = await this.toFileSystemHandle(resource, { create: false, isDirectory: false }) as FileSystemFileHandle;
|
|
195
|
+
|
|
196
|
+
// Get the file itself (which includes the content)
|
|
197
|
+
const file = await fileHandle.getFile();
|
|
198
|
+
|
|
199
|
+
// Read the file as an ArrayBuffer and convert it to Uint8Array
|
|
200
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
201
|
+
return new Uint8Array(arrayBuffer);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw toFileSystemProviderError(error, false);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
|
|
208
|
+
await this.initialized;
|
|
209
|
+
let writeableHandle: FileSystemWritableFileStream | undefined = undefined;
|
|
210
|
+
try {
|
|
211
|
+
// Validate target unless { create: true, overwrite: true }
|
|
212
|
+
if (!opts.create || !opts.overwrite) {
|
|
213
|
+
const fileExists = await this.stat(resource).then(() => true, () => false);
|
|
214
|
+
if (fileExists) {
|
|
215
|
+
if (!opts.overwrite) {
|
|
216
|
+
throw createFileSystemProviderError('File already exists', FileSystemProviderErrorCode.FileExists);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
if (!opts.create) {
|
|
220
|
+
throw createFileSystemProviderError('File does not exist', FileSystemProviderErrorCode.FileNotFound);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const handle = await this.toFileSystemHandle(resource, { create: true, isDirectory: false }) as FileSystemFileHandle;
|
|
226
|
+
|
|
227
|
+
// Open
|
|
228
|
+
writeableHandle = await handle?.createWritable();
|
|
229
|
+
|
|
230
|
+
// Write content at once
|
|
231
|
+
await writeableHandle?.write(content);
|
|
232
|
+
|
|
233
|
+
this.onDidChangeFileEmitter.fire([{ resource: resource, type: FileChangeType.UPDATED }]);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw toFileSystemProviderError(error, false);
|
|
236
|
+
} finally {
|
|
237
|
+
if (typeof writeableHandle !== 'undefined') {
|
|
238
|
+
await writeableHandle.close();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Returns the FileSystemHandle for the given resource given by a URI.
|
|
245
|
+
* @param resource URI/path of the resource
|
|
246
|
+
* @param options Options for the creation of the handle while traversing the path
|
|
247
|
+
* @returns FileSystemHandle for the given resource
|
|
248
|
+
*/
|
|
249
|
+
private async toFileSystemHandle(resource: URI, options?: CreateFileSystemHandleOptions): Promise<FileSystemHandle> {
|
|
250
|
+
const pathParts = resource.path.toString().split(Path.separator).filter(Boolean);
|
|
251
|
+
|
|
252
|
+
return recursiveFileSystemHandle(this.directoryHandle, pathParts, options);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// #region Helper functions
|
|
257
|
+
async function recursiveFileSystemHandle(handle: FileSystemHandle, pathParts: string[], options?: CreateFileSystemHandleOptions): Promise<FileSystemHandle> {
|
|
258
|
+
// We reached the end of the path, this happens only when not creating
|
|
259
|
+
if (pathParts.length === 0) {
|
|
260
|
+
return handle;
|
|
261
|
+
}
|
|
262
|
+
// If there are parts left, the handle must be a directory
|
|
263
|
+
if (handle.kind !== 'directory') {
|
|
264
|
+
throw createFileSystemProviderError('Not a directory', FileSystemProviderErrorCode.FileNotADirectory);
|
|
265
|
+
}
|
|
266
|
+
const dirHandle = handle as FileSystemDirectoryHandle;
|
|
267
|
+
// We need to create it and thus we need to stop early to create the file or directory
|
|
268
|
+
if (pathParts.length === 1 && options?.create) {
|
|
269
|
+
if (options?.isDirectory) {
|
|
270
|
+
return dirHandle.getDirectoryHandle(pathParts[0], { create: options.create });
|
|
271
|
+
} else {
|
|
272
|
+
return dirHandle.getFileHandle(pathParts[0], { create: options.create });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Continue to resolve the path
|
|
277
|
+
const part = pathParts.shift()!;
|
|
278
|
+
for await (const entry of dirHandle.entries()) {
|
|
279
|
+
// Check the entry name in the current directory
|
|
280
|
+
if (entry[0] === part) {
|
|
281
|
+
return recursiveFileSystemHandle(entry[1], pathParts, options);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// If we haven't found the part, we need to create it along the way
|
|
286
|
+
if (options?.create) {
|
|
287
|
+
const newHandle = await dirHandle.getDirectoryHandle(part, { create: true });
|
|
288
|
+
return recursiveFileSystemHandle(newHandle, pathParts, options);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
throw createFileSystemProviderError('File not found', FileSystemProviderErrorCode.FileNotFound);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Function to copy directory contents recursively
|
|
295
|
+
async function copyDirectoryContents(sourceHandle: FileSystemDirectoryHandle, destinationHandle: FileSystemDirectoryHandle): Promise<void> {
|
|
296
|
+
for await (const [name, handle] of sourceHandle.entries()) {
|
|
297
|
+
if (handle.kind === 'file') {
|
|
298
|
+
const file = await (handle as FileSystemFileHandle).getFile();
|
|
299
|
+
const newFileHandle = await destinationHandle.getFileHandle(name, { create: true });
|
|
300
|
+
const writable = await newFileHandle.createWritable();
|
|
301
|
+
try {
|
|
302
|
+
await writable.write(await file.arrayBuffer());
|
|
303
|
+
} finally {
|
|
304
|
+
await writable.close();
|
|
305
|
+
}
|
|
306
|
+
} else if (handle.kind === 'directory') {
|
|
307
|
+
const newSubDirHandle = await destinationHandle.getDirectoryHandle(name, { create: true });
|
|
308
|
+
await copyDirectoryContents(handle as FileSystemDirectoryHandle, newSubDirHandle);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function toFileSystemProviderError(error: DOMException, is_dir?: boolean): FileSystemProviderError {
|
|
314
|
+
if (error instanceof FileSystemProviderError) {
|
|
315
|
+
return error; // avoid double conversion
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let code: FileSystemProviderErrorCode;
|
|
319
|
+
switch (error.name) {
|
|
320
|
+
case 'NotFoundError':
|
|
321
|
+
code = FileSystemProviderErrorCode.FileNotFound;
|
|
322
|
+
break;
|
|
323
|
+
case 'InvalidModificationError':
|
|
324
|
+
code = FileSystemProviderErrorCode.FileExists;
|
|
325
|
+
break;
|
|
326
|
+
case 'NotAllowedError':
|
|
327
|
+
code = FileSystemProviderErrorCode.NoPermissions;
|
|
328
|
+
break;
|
|
329
|
+
case 'TypeMismatchError':
|
|
330
|
+
if (!is_dir) {
|
|
331
|
+
code = FileSystemProviderErrorCode.FileIsADirectory;
|
|
332
|
+
} else {
|
|
333
|
+
code = FileSystemProviderErrorCode.FileNotADirectory;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
break;
|
|
337
|
+
case 'QuotaExceededError':
|
|
338
|
+
code = FileSystemProviderErrorCode.FileTooLarge;
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
code = FileSystemProviderErrorCode.Unknown;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return createFileSystemProviderError(error, code);
|
|
345
|
+
}
|
|
346
|
+
// #endregion
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Provides the file download contribution to the `Files` navigator.
|
|
4
4
|
|
|
5
5
|
Supports single and multi file downloads.
|
|
6
|
+
|
|
6
7
|
1. A single file will be downloaded as is.
|
|
7
8
|
2. Folders will be downloaded az tar archives.
|
|
8
9
|
3. When downloading multiple files, the name of the closest common parent directory will be used for the archive.
|
|
@@ -10,10 +11,11 @@ Supports single and multi file downloads.
|
|
|
10
11
|
|
|
11
12
|
### REST API
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
- To download a single file or folder use the following endpoint: `GET /files/?uri=/encoded/file/uri/to/the/resource`.
|
|
15
|
+
- Example: `curl -X GET http://localhost:3000/files/?uri=file:///Users/akos.kitta/git/theia/package.json`.
|
|
16
|
+
|
|
17
|
+
- To download multiple files (from the same folder) use the `PUT /files/` endpoint with the `application/json` content type header and the following body format:
|
|
15
18
|
|
|
16
|
-
- To download multiple files (from the same folder) use the `PUT /files/` endpoint with the `application/json` content type header and the following body format:
|
|
17
19
|
```json
|
|
18
20
|
{
|
|
19
21
|
"uri": [
|
|
@@ -22,10 +24,12 @@ Supports single and multi file downloads.
|
|
|
22
24
|
]
|
|
23
25
|
}
|
|
24
26
|
```
|
|
27
|
+
|
|
25
28
|
```
|
|
26
29
|
curl -X PUT -H "Content-Type: application/json" -d '{ "uris": ["file:///Users/akos.kitta/git/theia/package.json", "file:///Users/akos.kitta/git/theia/README.md"] }' http://localhost:3000/files/
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
## License
|
|
33
|
+
|
|
30
34
|
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
|
31
|
-
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
|
35
|
+
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|