@theia/filesystem 1.65.0-next.55 → 1.65.0
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-command-contribution.d.ts +1 -1
- package/lib/browser/download/file-download-command-contribution.d.ts.map +1 -1
- package/lib/browser/download/file-download-command-contribution.js +3 -3
- package/lib/browser/download/file-download-command-contribution.js.map +1 -1
- package/lib/browser/download/file-download-frontend-module.d.ts.map +1 -1
- package/lib/browser/download/file-download-frontend-module.js +2 -1
- package/lib/browser/download/file-download-frontend-module.js.map +1 -1
- package/lib/browser/download/file-download-service.d.ts +2 -10
- package/lib/browser/download/file-download-service.d.ts.map +1 -1
- package/lib/browser/download/file-download-service.js +8 -7
- package/lib/browser/download/file-download-service.js.map +1 -1
- package/lib/browser/file-tree/file-tree-widget.d.ts +1 -1
- package/lib/browser/file-tree/file-tree-widget.d.ts.map +1 -1
- package/lib/browser/file-tree/file-tree-widget.js +3 -3
- package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
- package/lib/browser/filesystem-frontend-contribution.d.ts +2 -2
- package/lib/browser/filesystem-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/filesystem-frontend-contribution.js +3 -3
- package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
- package/lib/browser/filesystem-frontend-module.d.ts.map +1 -1
- package/lib/browser/filesystem-frontend-module.js +3 -2
- package/lib/browser/filesystem-frontend-module.js.map +1 -1
- package/lib/browser/{file-upload-service.d.ts → upload/file-upload-service-impl.d.ts} +16 -52
- package/lib/browser/upload/file-upload-service-impl.d.ts.map +1 -0
- package/lib/browser/{file-upload-service.js → upload/file-upload-service-impl.js} +27 -27
- package/lib/browser/upload/file-upload-service-impl.js.map +1 -0
- package/lib/browser-only/browser-only-filesystem-frontend-module.d.ts.map +1 -1
- package/lib/browser-only/browser-only-filesystem-frontend-module.js +8 -0
- package/lib/browser-only/browser-only-filesystem-frontend-module.js.map +1 -1
- package/lib/browser-only/download/file-download-command-contribution.d.ts +15 -0
- package/lib/browser-only/download/file-download-command-contribution.d.ts.map +1 -0
- package/lib/browser-only/download/file-download-command-contribution.js +55 -0
- package/lib/browser-only/download/file-download-command-contribution.js.map +1 -0
- package/lib/browser-only/download/file-download-frontend-module.d.ts +4 -0
- package/lib/browser-only/download/file-download-frontend-module.d.ts.map +1 -0
- package/lib/browser-only/download/file-download-frontend-module.js +27 -0
- package/lib/browser-only/download/file-download-frontend-module.js.map +1 -0
- package/lib/browser-only/download/file-download-service.d.ts +86 -0
- package/lib/browser-only/download/file-download-service.d.ts.map +1 -0
- package/lib/browser-only/download/file-download-service.js +551 -0
- package/lib/browser-only/download/file-download-service.js.map +1 -0
- package/lib/browser-only/file-search.d.ts +38 -0
- package/lib/browser-only/file-search.d.ts.map +1 -0
- package/lib/browser-only/file-search.js +153 -0
- package/lib/browser-only/file-search.js.map +1 -0
- package/lib/browser-only/opfs-filesystem-initialization.d.ts +4 -2
- package/lib/browser-only/opfs-filesystem-initialization.d.ts.map +1 -1
- package/lib/browser-only/opfs-filesystem-initialization.js +4 -1
- package/lib/browser-only/opfs-filesystem-initialization.js.map +1 -1
- package/lib/browser-only/opfs-filesystem-provider.d.ts +89 -12
- package/lib/browser-only/opfs-filesystem-provider.d.ts.map +1 -1
- package/lib/browser-only/opfs-filesystem-provider.js +345 -181
- package/lib/browser-only/opfs-filesystem-provider.js.map +1 -1
- package/lib/browser-only/upload/file-upload-service-impl.d.ts +67 -0
- package/lib/browser-only/upload/file-upload-service-impl.d.ts.map +1 -0
- package/lib/browser-only/upload/file-upload-service-impl.js +328 -0
- package/lib/browser-only/upload/file-upload-service-impl.js.map +1 -0
- package/lib/common/download/file-download.d.ts +17 -0
- package/lib/common/download/file-download.d.ts.map +1 -0
- package/lib/common/download/{file-download-data.js → file-download.js} +3 -2
- package/lib/common/download/file-download.js.map +1 -0
- package/lib/common/files.d.ts +8 -1
- package/lib/common/files.d.ts.map +1 -1
- package/lib/common/files.js +35 -1
- package/lib/common/files.js.map +1 -1
- package/lib/common/io.js +7 -1
- package/lib/common/io.js.map +1 -1
- package/lib/common/upload/file-upload.d.ts +45 -0
- package/lib/common/upload/file-upload.d.ts.map +1 -0
- package/{src/common/download/file-download-data.ts → lib/common/upload/file-upload.js} +6 -13
- package/lib/common/upload/file-upload.js.map +1 -0
- package/lib/node/disk-file-system-provider.d.ts.map +1 -1
- package/lib/node/disk-file-system-provider.js +2 -4
- package/lib/node/disk-file-system-provider.js.map +1 -1
- package/lib/node/download/file-download-handler.js +2 -2
- package/lib/node/download/file-download-handler.js.map +1 -1
- package/lib/node/filesystem-backend-module.js +1 -1
- package/lib/node/filesystem-backend-module.js.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +2 -2
- package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
- package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
- package/lib/node/upload/node-file-upload-service.d.ts.map +1 -0
- package/lib/node/{node-file-upload-service.js → upload/node-file-upload-service.js} +1 -1
- package/lib/node/upload/node-file-upload-service.js.map +1 -0
- package/package.json +10 -5
- package/src/browser/download/file-download-command-contribution.ts +1 -1
- package/src/browser/download/file-download-frontend-module.ts +3 -2
- package/src/browser/download/file-download-service.ts +7 -12
- package/src/browser/file-tree/file-tree-widget.tsx +1 -1
- package/src/browser/filesystem-frontend-contribution.ts +2 -2
- package/src/browser/filesystem-frontend-module.ts +3 -2
- package/src/browser/{file-upload-service.ts → upload/file-upload-service-impl.ts} +31 -72
- package/src/browser-only/browser-only-filesystem-frontend-module.ts +10 -0
- package/src/browser-only/download/file-download-command-contribution.ts +56 -0
- package/src/browser-only/download/file-download-frontend-module.ts +26 -0
- package/src/browser-only/download/file-download-service.ts +726 -0
- package/src/browser-only/file-search.ts +170 -0
- package/src/browser-only/opfs-filesystem-initialization.ts +7 -4
- package/src/browser-only/opfs-filesystem-provider.ts +402 -189
- package/src/browser-only/upload/file-upload-service-impl.ts +408 -0
- package/src/common/download/file-download.ts +40 -0
- package/src/common/files.ts +42 -1
- package/src/common/io.ts +6 -1
- package/src/common/upload/file-upload.ts +65 -0
- package/src/node/disk-file-system-provider.ts +3 -4
- package/src/node/download/file-download-handler.ts +1 -1
- package/src/node/filesystem-backend-module.ts +1 -1
- package/src/node/parcel-watcher/parcel-filesystem-service.ts +2 -2
- package/src/node/{node-file-upload-service.ts → upload/node-file-upload-service.ts} +1 -1
- package/lib/browser/file-upload-service.d.ts.map +0 -1
- package/lib/browser/file-upload-service.js.map +0 -1
- package/lib/common/download/file-download-data.d.ts +0 -7
- package/lib/common/download/file-download-data.d.ts.map +0 -1
- package/lib/common/download/file-download-data.js.map +0 -1
- package/lib/node/node-file-upload-service.d.ts.map +0 -1
- package/lib/node/node-file-upload-service.js.map +0 -1
- /package/lib/node/{node-file-upload-service.d.ts → upload/node-file-upload-service.d.ts} +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 Maksim Kachurin
|
|
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 { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import URI from '@theia/core/lib/common/uri';
|
|
19
|
+
import { CancellationTokenSource, CancellationToken, checkCancelled, isCancelled } from '@theia/core/lib/common/cancellation';
|
|
20
|
+
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
21
|
+
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
22
|
+
import { Progress } from '@theia/core/lib/common/message-service-protocol';
|
|
23
|
+
import throttle = require('@theia/core/shared/lodash.throttle');
|
|
24
|
+
import { Semaphore } from 'async-mutex';
|
|
25
|
+
import { FileService } from '../../browser/file-service';
|
|
26
|
+
import { ConfirmDialog, Dialog } from '@theia/core/lib/browser';
|
|
27
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
28
|
+
import { Emitter, Event } from '@theia/core/lib/common/event';
|
|
29
|
+
import { FileSystemPreferences } from '../../common/filesystem-preferences';
|
|
30
|
+
import { fileToStream } from '@theia/core/lib/common/stream';
|
|
31
|
+
import { minimatch } from 'minimatch';
|
|
32
|
+
|
|
33
|
+
import type { FileUploadService } from '../../common/upload/file-upload';
|
|
34
|
+
|
|
35
|
+
interface UploadState {
|
|
36
|
+
uploaded?: boolean;
|
|
37
|
+
failed?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface UploadFilesParams {
|
|
41
|
+
source: FileUploadService.Source,
|
|
42
|
+
progress: Progress,
|
|
43
|
+
token: CancellationToken,
|
|
44
|
+
onDidUpload?: (uri: string) => void,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@injectable()
|
|
48
|
+
export class FileUploadServiceImpl implements FileUploadService {
|
|
49
|
+
|
|
50
|
+
static TARGET = 'target';
|
|
51
|
+
static UPLOAD = 'upload';
|
|
52
|
+
|
|
53
|
+
protected readonly onDidUploadEmitter = new Emitter<string[]>();
|
|
54
|
+
protected uploadForm: FileUploadService.Form;
|
|
55
|
+
protected deferredUpload?: Deferred<FileUploadService.UploadResult>;
|
|
56
|
+
|
|
57
|
+
@inject(FileSystemPreferences)
|
|
58
|
+
protected fileSystemPreferences: FileSystemPreferences;
|
|
59
|
+
|
|
60
|
+
@inject(FileService)
|
|
61
|
+
protected fileService: FileService;
|
|
62
|
+
|
|
63
|
+
@inject(MessageService)
|
|
64
|
+
protected readonly messageService: MessageService;
|
|
65
|
+
|
|
66
|
+
private readonly ignorePatterns: string[] = [];
|
|
67
|
+
|
|
68
|
+
get onDidUpload(): Event<string[]> {
|
|
69
|
+
return this.onDidUploadEmitter.event;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get maxConcurrentUploads(): number {
|
|
73
|
+
const maxConcurrentUploads = this.fileSystemPreferences['files.maxConcurrentUploads'];
|
|
74
|
+
return maxConcurrentUploads > 0 ? maxConcurrentUploads : Infinity;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@postConstruct()
|
|
78
|
+
protected init(): void {
|
|
79
|
+
this.uploadForm = this.createUploadForm();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected createUploadForm(): FileUploadService.Form {
|
|
83
|
+
const targetInput = document.createElement('input');
|
|
84
|
+
targetInput.type = 'text';
|
|
85
|
+
targetInput.spellcheck = false;
|
|
86
|
+
targetInput.name = FileUploadServiceImpl.TARGET;
|
|
87
|
+
targetInput.classList.add('theia-input');
|
|
88
|
+
|
|
89
|
+
const fileInput = document.createElement('input');
|
|
90
|
+
fileInput.type = 'file';
|
|
91
|
+
fileInput.classList.add('theia-input');
|
|
92
|
+
fileInput.name = FileUploadServiceImpl.UPLOAD;
|
|
93
|
+
fileInput.multiple = true;
|
|
94
|
+
|
|
95
|
+
const form = document.createElement('form');
|
|
96
|
+
form.style.display = 'none';
|
|
97
|
+
form.enctype = 'multipart/form-data';
|
|
98
|
+
form.append(targetInput);
|
|
99
|
+
form.append(fileInput);
|
|
100
|
+
|
|
101
|
+
document.body.appendChild(form);
|
|
102
|
+
|
|
103
|
+
fileInput.addEventListener('change', () => {
|
|
104
|
+
if (this.deferredUpload && fileInput.value) {
|
|
105
|
+
const source: FileUploadService.Source = new FormData(form);
|
|
106
|
+
// clean up for reuse
|
|
107
|
+
fileInput.value = '';
|
|
108
|
+
const targetUri = new URI(<string>source.get(FileUploadServiceImpl.TARGET));
|
|
109
|
+
const { resolve, reject } = this.deferredUpload;
|
|
110
|
+
this.deferredUpload = undefined;
|
|
111
|
+
const { onDidUpload } = this.uploadForm;
|
|
112
|
+
this.withProgress(
|
|
113
|
+
(progress, token) => this.uploadFiles(targetUri, { source, progress, token, onDidUpload })
|
|
114
|
+
).then(resolve, reject);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return { targetInput, fileInput };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async upload(targetUri: string | URI, params: FileUploadService.UploadParams): Promise<FileUploadService.UploadResult> {
|
|
122
|
+
const { source, onDidUpload } = params || {};
|
|
123
|
+
|
|
124
|
+
if (source) {
|
|
125
|
+
return this.withProgress(
|
|
126
|
+
(progress, token) => this.uploadFiles(
|
|
127
|
+
typeof targetUri === 'string' ? new URI(targetUri) : targetUri,
|
|
128
|
+
{ source, progress, token, onDidUpload }
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
this.deferredUpload = new Deferred<FileUploadService.UploadResult>();
|
|
133
|
+
this.uploadForm.targetInput.value = String(targetUri);
|
|
134
|
+
this.uploadForm.fileInput.click();
|
|
135
|
+
this.uploadForm.onDidUpload = onDidUpload;
|
|
136
|
+
return this.deferredUpload.promise;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected async withProgress<T>(
|
|
140
|
+
cb: (progress: Progress, token: CancellationToken) => Promise<T>
|
|
141
|
+
): Promise<T> {
|
|
142
|
+
const cancellationSource = new CancellationTokenSource();
|
|
143
|
+
const { token } = cancellationSource;
|
|
144
|
+
const text = nls.localize('theia/filesystem/uploadFiles', 'Saving Files');
|
|
145
|
+
const progress = await this.messageService.showProgress(
|
|
146
|
+
{ text, options: { cancelable: true } },
|
|
147
|
+
() => cancellationSource.cancel()
|
|
148
|
+
);
|
|
149
|
+
try {
|
|
150
|
+
return await cb(progress, token);
|
|
151
|
+
} finally {
|
|
152
|
+
progress.cancel();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
protected async confirmOverwrite(fileUri: URI): Promise<boolean> {
|
|
157
|
+
const dialog = new ConfirmDialog({
|
|
158
|
+
title: nls.localizeByDefault('Replace'),
|
|
159
|
+
msg: nls.localizeByDefault("A file or folder with the name '{0}' already exists in the destination folder. Do you want to replace it?", fileUri.path.base),
|
|
160
|
+
ok: nls.localizeByDefault('Replace'),
|
|
161
|
+
cancel: Dialog.CANCEL
|
|
162
|
+
});
|
|
163
|
+
return !!await dialog.open();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Upload all files to the filesystem
|
|
168
|
+
*/
|
|
169
|
+
protected async uploadFiles(targetUri: URI, params: UploadFilesParams): Promise<FileUploadService.UploadResult> {
|
|
170
|
+
const status = new Map<URI, UploadState>();
|
|
171
|
+
|
|
172
|
+
const report = throttle(() => {
|
|
173
|
+
const list = Array.from(status.values());
|
|
174
|
+
const total = list.length;
|
|
175
|
+
const done = list.filter(item => item.uploaded).length;
|
|
176
|
+
|
|
177
|
+
params.progress.report({
|
|
178
|
+
message: nls.localize('theia/filesystem/processedOutOf', 'Processed {0} out of {1}', done, total),
|
|
179
|
+
work: { total, done }
|
|
180
|
+
});
|
|
181
|
+
}, 100);
|
|
182
|
+
|
|
183
|
+
const uploads: Promise<void>[] = [];
|
|
184
|
+
const uploadSemaphore = new Semaphore(this.maxConcurrentUploads);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const files = await this.enumerateFiles(targetUri, params.source, params.token);
|
|
188
|
+
|
|
189
|
+
for (const { file, uri } of files) {
|
|
190
|
+
checkCancelled(params.token);
|
|
191
|
+
|
|
192
|
+
// Check exists and confirm overwrite before adding to queue
|
|
193
|
+
if (await this.fileService.exists(uri) && !await this.confirmOverwrite(uri)) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
status.set(uri, {
|
|
198
|
+
uploaded: false
|
|
199
|
+
});
|
|
200
|
+
report();
|
|
201
|
+
|
|
202
|
+
uploads.push(uploadSemaphore.runExclusive(async () => {
|
|
203
|
+
const entry = status.get(uri);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
checkCancelled(params.token);
|
|
207
|
+
|
|
208
|
+
await this.uploadFile(file, uri);
|
|
209
|
+
|
|
210
|
+
if (entry) {
|
|
211
|
+
entry.uploaded = true;
|
|
212
|
+
report();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (params.onDidUpload) {
|
|
216
|
+
params.onDidUpload(uri.toString(true));
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (entry) {
|
|
220
|
+
entry.failed = true;
|
|
221
|
+
report();
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
checkCancelled(params.token);
|
|
229
|
+
await Promise.all(uploads);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
uploadSemaphore.cancel();
|
|
232
|
+
|
|
233
|
+
if (!isCancelled(error)) {
|
|
234
|
+
this.messageService.error(nls.localize('theia/filesystem/uploadFailed', 'An error occurred while saving a file. {0}', error.message));
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const uploaded = Array.from(status.keys()).map(uri => uri.toString(true));
|
|
240
|
+
|
|
241
|
+
this.onDidUploadEmitter.fire(uploaded);
|
|
242
|
+
|
|
243
|
+
return { uploaded };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Upload (write) a file directly to the filesystem
|
|
248
|
+
*/
|
|
249
|
+
protected async uploadFile(
|
|
250
|
+
file: File,
|
|
251
|
+
targetUri: URI
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
await this.fileService.writeFile(targetUri, fileToStream(file));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Normalize sources into an array of { file, uri } objects
|
|
258
|
+
*/
|
|
259
|
+
protected async enumerateFiles(targetUri: URI, source: FileUploadService.Source, token: CancellationToken): Promise<{ file: File; uri: URI }[]> {
|
|
260
|
+
checkCancelled(token);
|
|
261
|
+
|
|
262
|
+
if (source instanceof FormData) {
|
|
263
|
+
// Handle FormData declaratively
|
|
264
|
+
const files = source.getAll(FileUploadServiceImpl.UPLOAD)
|
|
265
|
+
.filter((entry): entry is File => entry instanceof File)
|
|
266
|
+
.filter(entry => this.shouldIncludeFile(entry.name))
|
|
267
|
+
.map(entry => ({
|
|
268
|
+
file: entry,
|
|
269
|
+
uri: targetUri.resolve(entry.name)
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
return files;
|
|
273
|
+
} else if (source instanceof DataTransfer) {
|
|
274
|
+
// Use WebKit Entries for folder traversal
|
|
275
|
+
if (source.items && this.supportsWebKitEntries()) {
|
|
276
|
+
// Collect all files first
|
|
277
|
+
const allFiles: { file: File; uri: URI }[] = [];
|
|
278
|
+
const items = Array.from(source.items);
|
|
279
|
+
const entries = items.map(item => item.webkitGetAsEntry()).filter((entry): entry is WebKitEntry => !!entry);
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < entries.length; i++) {
|
|
282
|
+
const entry = entries[i];
|
|
283
|
+
const filesFromEntry = await this.traverseEntry(targetUri, entry!, token);
|
|
284
|
+
|
|
285
|
+
allFiles.push(...filesFromEntry);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return allFiles;
|
|
289
|
+
} else {
|
|
290
|
+
// Fall back to flat file list
|
|
291
|
+
return Array.from(source.files)
|
|
292
|
+
.filter((file): file is File => !!file)
|
|
293
|
+
.filter(file => this.shouldIncludeFile(file.name))
|
|
294
|
+
.map(file => ({
|
|
295
|
+
file,
|
|
296
|
+
uri: targetUri.resolve(file.name)
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
// Handle CustomDataTransfer declaratively
|
|
301
|
+
const files = await Promise.all(
|
|
302
|
+
Array.from(source)
|
|
303
|
+
.map(async ([, item]) => {
|
|
304
|
+
const fileData = item.asFile();
|
|
305
|
+
if (fileData && this.shouldIncludeFile(fileData.name)) {
|
|
306
|
+
const data = await fileData.data();
|
|
307
|
+
return {
|
|
308
|
+
file: new File([data as BlobPart], fileData.name, { type: 'application/octet-stream' }),
|
|
309
|
+
uri: targetUri.resolve(fileData.name)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
313
|
+
})
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
return files.filter(Boolean) as { file: File; uri: URI }[];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Traverse WebKit Entries (files and folders)
|
|
322
|
+
*/
|
|
323
|
+
protected async traverseEntry(
|
|
324
|
+
base: URI,
|
|
325
|
+
entry: WebKitEntry,
|
|
326
|
+
token: CancellationToken
|
|
327
|
+
): Promise<{ file: File; uri: URI }[]> {
|
|
328
|
+
if (!entry) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Skip system entries
|
|
333
|
+
if (!this.shouldIncludeFile(entry.name)) {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// directory
|
|
338
|
+
if (entry.isDirectory) {
|
|
339
|
+
const dir = entry as WebKitDirectoryEntry;
|
|
340
|
+
const newBase = base.resolve(dir.name);
|
|
341
|
+
|
|
342
|
+
const entries = await this.readAllEntries(dir, token);
|
|
343
|
+
checkCancelled(token);
|
|
344
|
+
|
|
345
|
+
const chunks = await Promise.all(
|
|
346
|
+
entries.map(sub => this.traverseEntry(newBase, sub, token))
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
return chunks.flat();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// file
|
|
353
|
+
const fileEntry = entry as WebKitFileEntry;
|
|
354
|
+
const file = await this.readFileEntry(fileEntry, token);
|
|
355
|
+
checkCancelled(token);
|
|
356
|
+
|
|
357
|
+
return [{ file, uri: base.resolve(entry.name) }];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Read all entries from a WebKit directory entry
|
|
362
|
+
*/
|
|
363
|
+
protected async readAllEntries(
|
|
364
|
+
dir: WebKitDirectoryEntry,
|
|
365
|
+
token: CancellationToken
|
|
366
|
+
): Promise<WebKitEntry[]> {
|
|
367
|
+
const reader = dir.createReader();
|
|
368
|
+
const out: WebKitEntry[] = [];
|
|
369
|
+
|
|
370
|
+
while (true) {
|
|
371
|
+
checkCancelled(token);
|
|
372
|
+
|
|
373
|
+
const batch = await new Promise<WebKitEntry[]>((resolve, reject) =>
|
|
374
|
+
reader.readEntries(resolve, reject)
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
if (!batch.length) {break; }
|
|
378
|
+
out.push(...batch);
|
|
379
|
+
|
|
380
|
+
// yield to the event loop to keep UI responsive
|
|
381
|
+
await Promise.resolve();
|
|
382
|
+
}
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Read a file from a WebKit file entry
|
|
388
|
+
*/
|
|
389
|
+
protected async readFileEntry(
|
|
390
|
+
fileEntry: WebKitFileEntry,
|
|
391
|
+
token: CancellationToken
|
|
392
|
+
): Promise<File> {
|
|
393
|
+
checkCancelled(token);
|
|
394
|
+
try {
|
|
395
|
+
return await new Promise<File>((resolve, reject) => fileEntry.file(resolve, reject));
|
|
396
|
+
} catch (err) {
|
|
397
|
+
throw err;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
protected supportsWebKitEntries(): boolean {
|
|
402
|
+
return typeof DataTransferItem.prototype.webkitGetAsEntry === 'function';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
protected shouldIncludeFile(path: string): boolean {
|
|
406
|
+
return !this.ignorePatterns.some((pattern: string) => minimatch(path, pattern));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 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 { isObject, type URI } from '@theia/core/lib/common';
|
|
18
|
+
|
|
19
|
+
export interface FileDownloadData {
|
|
20
|
+
readonly uris: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export namespace FileDownloadData {
|
|
24
|
+
export function is(arg: unknown): arg is FileDownloadData {
|
|
25
|
+
return isObject(arg) && 'uris' in arg;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export namespace FileDownloadService {
|
|
30
|
+
export interface DownloadOptions {
|
|
31
|
+
// `true` if the download link has to be copied to the clipboard. This will not trigger the actual download. Defaults to `false`.
|
|
32
|
+
readonly copyLink?: boolean;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const FileDownloadService = Symbol('FileDownloadService');
|
|
37
|
+
|
|
38
|
+
export interface FileDownloadService {
|
|
39
|
+
download(uris: URI[], options?: FileDownloadService.DownloadOptions): Promise<void>;
|
|
40
|
+
}
|
package/src/common/files.ts
CHANGED
|
@@ -970,8 +970,9 @@ export function etag(stat: { mtime: number | undefined, size: number | undefined
|
|
|
970
970
|
|
|
971
971
|
return stat.mtime.toString(29) + stat.size.toString(31);
|
|
972
972
|
}
|
|
973
|
+
|
|
973
974
|
/**
|
|
974
|
-
* Helper
|
|
975
|
+
* Helper class for formatting and parsing byte sizes.
|
|
975
976
|
*/
|
|
976
977
|
export class BinarySize {
|
|
977
978
|
static readonly KB = 1024;
|
|
@@ -979,6 +980,9 @@ export class BinarySize {
|
|
|
979
980
|
static readonly GB = BinarySize.MB * BinarySize.KB;
|
|
980
981
|
static readonly TB = BinarySize.GB * BinarySize.KB;
|
|
981
982
|
|
|
983
|
+
/**
|
|
984
|
+
* Formats a byte size into a human readable string (e.g., "1.5MB", "2.3GB").
|
|
985
|
+
*/
|
|
982
986
|
static formatSize(size: number): string {
|
|
983
987
|
if (size < BinarySize.KB) {
|
|
984
988
|
return size + 'B';
|
|
@@ -994,4 +998,41 @@ export class BinarySize {
|
|
|
994
998
|
}
|
|
995
999
|
return (size / BinarySize.TB).toFixed(2) + 'TB';
|
|
996
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Parses a human readable string (e.g., "1.5MB", "2.3GB") and returns the size in bytes
|
|
1004
|
+
*/
|
|
1005
|
+
static parseSize(sizeInput: string | number | undefined): number {
|
|
1006
|
+
if (typeof sizeInput === 'number') {
|
|
1007
|
+
return Math.round(sizeInput);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
if (!sizeInput) {
|
|
1011
|
+
return 0;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const trimmed = sizeInput.trim().toUpperCase();
|
|
1015
|
+
const match = /^(\d+(?:\.\d+)?)([BKMG])?$/.exec(trimmed);
|
|
1016
|
+
|
|
1017
|
+
// If the format is invalid, return 0
|
|
1018
|
+
if (!match) {
|
|
1019
|
+
return 0;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const value = parseFloat(match[1]);
|
|
1023
|
+
const unit = match[2];
|
|
1024
|
+
|
|
1025
|
+
switch (unit) {
|
|
1026
|
+
case 'K':
|
|
1027
|
+
return Math.round(value * BinarySize.KB);
|
|
1028
|
+
case 'M':
|
|
1029
|
+
return Math.round(value * BinarySize.MB);
|
|
1030
|
+
case 'G':
|
|
1031
|
+
return Math.round(value * BinarySize.GB);
|
|
1032
|
+
case 'T':
|
|
1033
|
+
return Math.round(value * BinarySize.TB);
|
|
1034
|
+
default:
|
|
1035
|
+
return Math.round(value);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
997
1038
|
}
|
package/src/common/io.ts
CHANGED
|
@@ -75,7 +75,12 @@ async function doReadFileIntoStream<T>(provider: FileSystemProviderWithOpenReadW
|
|
|
75
75
|
const handle = await provider.open(resource, { create: false });
|
|
76
76
|
|
|
77
77
|
// Check for cancellation
|
|
78
|
-
|
|
78
|
+
try {
|
|
79
|
+
throwIfCancelled(token);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
await provider.close(handle);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
79
84
|
|
|
80
85
|
try {
|
|
81
86
|
let totalBytesRead = 0;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2019 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 URI from '@theia/core/lib/common/uri';
|
|
18
|
+
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
|
19
|
+
import { Progress } from '@theia/core/lib/common/message-service-protocol';
|
|
20
|
+
import { Event } from '@theia/core/lib/common/event';
|
|
21
|
+
|
|
22
|
+
export type CustomDataTransfer = Iterable<readonly [string, CustomDataTransferItem]>;
|
|
23
|
+
|
|
24
|
+
export interface CustomDataTransferItem {
|
|
25
|
+
asFile(): {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
data(): Promise<Uint8Array>;
|
|
29
|
+
} | undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FileUploadService {
|
|
33
|
+
upload(targetUri: string | URI, params?: FileUploadService.UploadParams): Promise<FileUploadService.UploadResult>;
|
|
34
|
+
readonly onDidUpload: Event<string[]>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export namespace FileUploadService {
|
|
38
|
+
export type Source = FormData | DataTransfer | CustomDataTransfer;
|
|
39
|
+
export interface UploadEntry {
|
|
40
|
+
file: File
|
|
41
|
+
uri: URI
|
|
42
|
+
}
|
|
43
|
+
export interface Context {
|
|
44
|
+
progress: Progress
|
|
45
|
+
token: CancellationToken
|
|
46
|
+
accept: (entry: UploadEntry) => Promise<void>
|
|
47
|
+
}
|
|
48
|
+
export interface Form {
|
|
49
|
+
targetInput: HTMLInputElement
|
|
50
|
+
fileInput: HTMLInputElement
|
|
51
|
+
onDidUpload?: (uri: string) => void
|
|
52
|
+
}
|
|
53
|
+
export interface UploadParams {
|
|
54
|
+
source?: FileUploadService.Source,
|
|
55
|
+
progress?: Progress,
|
|
56
|
+
token?: CancellationToken,
|
|
57
|
+
onDidUpload?: (uri: string) => void,
|
|
58
|
+
leaveInTemp?: boolean
|
|
59
|
+
}
|
|
60
|
+
export interface UploadResult {
|
|
61
|
+
uploaded: string[]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const FileUploadService = Symbol('FileUploadService');
|
|
@@ -286,14 +286,13 @@ export class DiskFileSystemProvider implements Disposable,
|
|
|
286
286
|
// Validate target unless { create: true, overwrite: true }
|
|
287
287
|
if (!opts.create || !opts.overwrite) {
|
|
288
288
|
const fileExists = await promisify(exists)(filePath);
|
|
289
|
+
|
|
289
290
|
if (fileExists) {
|
|
290
291
|
if (!opts.overwrite) {
|
|
291
292
|
throw createFileSystemProviderError('File already exists', FileSystemProviderErrorCode.FileExists);
|
|
292
293
|
}
|
|
293
|
-
} else {
|
|
294
|
-
|
|
295
|
-
throw createFileSystemProviderError('File does not exist', FileSystemProviderErrorCode.FileNotFound);
|
|
296
|
-
}
|
|
294
|
+
} else if (!opts.create) {
|
|
295
|
+
throw createFileSystemProviderError('File does not exist', FileSystemProviderErrorCode.FileNotFound);
|
|
297
296
|
}
|
|
298
297
|
}
|
|
299
298
|
|
|
@@ -26,7 +26,7 @@ import { isEmpty } from '@theia/core/lib/common/objects';
|
|
|
26
26
|
import { ILogger } from '@theia/core/lib/common/logger';
|
|
27
27
|
import { FileUri } from '@theia/core/lib/common/file-uri';
|
|
28
28
|
import { DirectoryArchiver } from './directory-archiver';
|
|
29
|
-
import { FileDownloadData } from '../../common/download/file-download
|
|
29
|
+
import { FileDownloadData } from '../../common/download/file-download';
|
|
30
30
|
import { FileDownloadCache, DownloadStorageItem } from './file-download-cache';
|
|
31
31
|
|
|
32
32
|
interface PrepareDownloadOptions {
|
|
@@ -20,7 +20,7 @@ import { ConnectionHandler, RpcConnectionHandler, ILogger } from '@theia/core/li
|
|
|
20
20
|
import { FileSystemWatcherServer, FileSystemWatcherService } from '../common/filesystem-watcher-protocol';
|
|
21
21
|
import { FileSystemWatcherServerClient } from './filesystem-watcher-client';
|
|
22
22
|
import { ParcelFileSystemWatcherService, ParcelFileSystemWatcherServerOptions } from './parcel-watcher/parcel-filesystem-service';
|
|
23
|
-
import { NodeFileUploadService } from './node-file-upload-service';
|
|
23
|
+
import { NodeFileUploadService } from './upload/node-file-upload-service';
|
|
24
24
|
import { ParcelWatcherOptions } from './parcel-watcher/parcel-options';
|
|
25
25
|
import { DiskFileSystemProvider } from './disk-file-system-provider';
|
|
26
26
|
import {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import path = require('path');
|
|
18
18
|
import { promises as fsp } from 'fs';
|
|
19
|
-
import {
|
|
19
|
+
import { Minimatch } from 'minimatch';
|
|
20
20
|
import { FileUri } from '@theia/core/lib/common/file-uri';
|
|
21
21
|
import {
|
|
22
22
|
FileChangeType, FileSystemWatcherService, FileSystemWatcherServiceClient, WatchOptions
|
|
@@ -26,7 +26,7 @@ import { Deferred, timeout } from '@theia/core/lib/common/promise-util';
|
|
|
26
26
|
import { subscribe, Options, AsyncSubscription, Event } from '@theia/core/shared/@parcel/watcher';
|
|
27
27
|
|
|
28
28
|
export interface ParcelWatcherOptions {
|
|
29
|
-
ignored:
|
|
29
|
+
ignored: Minimatch[]
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const ParcelFileSystemWatcherServerOptions = Symbol('ParcelFileSystemWatcherServerOptions');
|
|
@@ -21,7 +21,7 @@ import express = require('@theia/core/shared/express');
|
|
|
21
21
|
import fs = require('@theia/core/shared/fs-extra');
|
|
22
22
|
import { BackendApplicationContribution, FileUri } from '@theia/core/lib/node';
|
|
23
23
|
import { injectable } from '@theia/core/shared/inversify';
|
|
24
|
-
import { HTTP_FILE_UPLOAD_PATH } from '
|
|
24
|
+
import { HTTP_FILE_UPLOAD_PATH } from '../../common/file-upload';
|
|
25
25
|
|
|
26
26
|
@injectable()
|
|
27
27
|
export class NodeFileUploadService implements BackendApplicationContribution {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-upload-service.d.ts","sourceRoot":"","sources":["../../src/browser/file-upload-service.ts"],"names":[],"mappings":"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"}
|