@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.
Files changed (117) hide show
  1. package/lib/browser/download/file-download-command-contribution.d.ts +1 -1
  2. package/lib/browser/download/file-download-command-contribution.d.ts.map +1 -1
  3. package/lib/browser/download/file-download-command-contribution.js +3 -3
  4. package/lib/browser/download/file-download-command-contribution.js.map +1 -1
  5. package/lib/browser/download/file-download-frontend-module.d.ts.map +1 -1
  6. package/lib/browser/download/file-download-frontend-module.js +2 -1
  7. package/lib/browser/download/file-download-frontend-module.js.map +1 -1
  8. package/lib/browser/download/file-download-service.d.ts +2 -10
  9. package/lib/browser/download/file-download-service.d.ts.map +1 -1
  10. package/lib/browser/download/file-download-service.js +8 -7
  11. package/lib/browser/download/file-download-service.js.map +1 -1
  12. package/lib/browser/file-tree/file-tree-widget.d.ts +1 -1
  13. package/lib/browser/file-tree/file-tree-widget.d.ts.map +1 -1
  14. package/lib/browser/file-tree/file-tree-widget.js +3 -3
  15. package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
  16. package/lib/browser/filesystem-frontend-contribution.d.ts +2 -2
  17. package/lib/browser/filesystem-frontend-contribution.d.ts.map +1 -1
  18. package/lib/browser/filesystem-frontend-contribution.js +3 -3
  19. package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
  20. package/lib/browser/filesystem-frontend-module.d.ts.map +1 -1
  21. package/lib/browser/filesystem-frontend-module.js +3 -2
  22. package/lib/browser/filesystem-frontend-module.js.map +1 -1
  23. package/lib/browser/{file-upload-service.d.ts → upload/file-upload-service-impl.d.ts} +16 -52
  24. package/lib/browser/upload/file-upload-service-impl.d.ts.map +1 -0
  25. package/lib/browser/{file-upload-service.js → upload/file-upload-service-impl.js} +27 -27
  26. package/lib/browser/upload/file-upload-service-impl.js.map +1 -0
  27. package/lib/browser-only/browser-only-filesystem-frontend-module.d.ts.map +1 -1
  28. package/lib/browser-only/browser-only-filesystem-frontend-module.js +8 -0
  29. package/lib/browser-only/browser-only-filesystem-frontend-module.js.map +1 -1
  30. package/lib/browser-only/download/file-download-command-contribution.d.ts +15 -0
  31. package/lib/browser-only/download/file-download-command-contribution.d.ts.map +1 -0
  32. package/lib/browser-only/download/file-download-command-contribution.js +55 -0
  33. package/lib/browser-only/download/file-download-command-contribution.js.map +1 -0
  34. package/lib/browser-only/download/file-download-frontend-module.d.ts +4 -0
  35. package/lib/browser-only/download/file-download-frontend-module.d.ts.map +1 -0
  36. package/lib/browser-only/download/file-download-frontend-module.js +27 -0
  37. package/lib/browser-only/download/file-download-frontend-module.js.map +1 -0
  38. package/lib/browser-only/download/file-download-service.d.ts +86 -0
  39. package/lib/browser-only/download/file-download-service.d.ts.map +1 -0
  40. package/lib/browser-only/download/file-download-service.js +551 -0
  41. package/lib/browser-only/download/file-download-service.js.map +1 -0
  42. package/lib/browser-only/file-search.d.ts +38 -0
  43. package/lib/browser-only/file-search.d.ts.map +1 -0
  44. package/lib/browser-only/file-search.js +153 -0
  45. package/lib/browser-only/file-search.js.map +1 -0
  46. package/lib/browser-only/opfs-filesystem-initialization.d.ts +4 -2
  47. package/lib/browser-only/opfs-filesystem-initialization.d.ts.map +1 -1
  48. package/lib/browser-only/opfs-filesystem-initialization.js +4 -1
  49. package/lib/browser-only/opfs-filesystem-initialization.js.map +1 -1
  50. package/lib/browser-only/opfs-filesystem-provider.d.ts +89 -12
  51. package/lib/browser-only/opfs-filesystem-provider.d.ts.map +1 -1
  52. package/lib/browser-only/opfs-filesystem-provider.js +345 -181
  53. package/lib/browser-only/opfs-filesystem-provider.js.map +1 -1
  54. package/lib/browser-only/upload/file-upload-service-impl.d.ts +67 -0
  55. package/lib/browser-only/upload/file-upload-service-impl.d.ts.map +1 -0
  56. package/lib/browser-only/upload/file-upload-service-impl.js +328 -0
  57. package/lib/browser-only/upload/file-upload-service-impl.js.map +1 -0
  58. package/lib/common/download/file-download.d.ts +17 -0
  59. package/lib/common/download/file-download.d.ts.map +1 -0
  60. package/lib/common/download/{file-download-data.js → file-download.js} +3 -2
  61. package/lib/common/download/file-download.js.map +1 -0
  62. package/lib/common/files.d.ts +8 -1
  63. package/lib/common/files.d.ts.map +1 -1
  64. package/lib/common/files.js +35 -1
  65. package/lib/common/files.js.map +1 -1
  66. package/lib/common/io.js +7 -1
  67. package/lib/common/io.js.map +1 -1
  68. package/lib/common/upload/file-upload.d.ts +45 -0
  69. package/lib/common/upload/file-upload.d.ts.map +1 -0
  70. package/{src/common/download/file-download-data.ts → lib/common/upload/file-upload.js} +6 -13
  71. package/lib/common/upload/file-upload.js.map +1 -0
  72. package/lib/node/disk-file-system-provider.d.ts.map +1 -1
  73. package/lib/node/disk-file-system-provider.js +2 -4
  74. package/lib/node/disk-file-system-provider.js.map +1 -1
  75. package/lib/node/download/file-download-handler.js +2 -2
  76. package/lib/node/download/file-download-handler.js.map +1 -1
  77. package/lib/node/filesystem-backend-module.js +1 -1
  78. package/lib/node/filesystem-backend-module.js.map +1 -1
  79. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +2 -2
  80. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
  81. package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
  82. package/lib/node/upload/node-file-upload-service.d.ts.map +1 -0
  83. package/lib/node/{node-file-upload-service.js → upload/node-file-upload-service.js} +1 -1
  84. package/lib/node/upload/node-file-upload-service.js.map +1 -0
  85. package/package.json +10 -5
  86. package/src/browser/download/file-download-command-contribution.ts +1 -1
  87. package/src/browser/download/file-download-frontend-module.ts +3 -2
  88. package/src/browser/download/file-download-service.ts +7 -12
  89. package/src/browser/file-tree/file-tree-widget.tsx +1 -1
  90. package/src/browser/filesystem-frontend-contribution.ts +2 -2
  91. package/src/browser/filesystem-frontend-module.ts +3 -2
  92. package/src/browser/{file-upload-service.ts → upload/file-upload-service-impl.ts} +31 -72
  93. package/src/browser-only/browser-only-filesystem-frontend-module.ts +10 -0
  94. package/src/browser-only/download/file-download-command-contribution.ts +56 -0
  95. package/src/browser-only/download/file-download-frontend-module.ts +26 -0
  96. package/src/browser-only/download/file-download-service.ts +726 -0
  97. package/src/browser-only/file-search.ts +170 -0
  98. package/src/browser-only/opfs-filesystem-initialization.ts +7 -4
  99. package/src/browser-only/opfs-filesystem-provider.ts +402 -189
  100. package/src/browser-only/upload/file-upload-service-impl.ts +408 -0
  101. package/src/common/download/file-download.ts +40 -0
  102. package/src/common/files.ts +42 -1
  103. package/src/common/io.ts +6 -1
  104. package/src/common/upload/file-upload.ts +65 -0
  105. package/src/node/disk-file-system-provider.ts +3 -4
  106. package/src/node/download/file-download-handler.ts +1 -1
  107. package/src/node/filesystem-backend-module.ts +1 -1
  108. package/src/node/parcel-watcher/parcel-filesystem-service.ts +2 -2
  109. package/src/node/{node-file-upload-service.ts → upload/node-file-upload-service.ts} +1 -1
  110. package/lib/browser/file-upload-service.d.ts.map +0 -1
  111. package/lib/browser/file-upload-service.js.map +0 -1
  112. package/lib/common/download/file-download-data.d.ts +0 -7
  113. package/lib/common/download/file-download-data.d.ts.map +0 -1
  114. package/lib/common/download/file-download-data.js.map +0 -1
  115. package/lib/node/node-file-upload-service.d.ts.map +0 -1
  116. package/lib/node/node-file-upload-service.js.map +0 -1
  117. /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
+ }
@@ -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 to format a raw byte size into a human readable label.
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
- throwIfCancelled(token);
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
- if (!opts.create) {
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-data';
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 { IMinimatch, Minimatch } from 'minimatch';
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: IMinimatch[]
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 '../common/file-upload';
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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"}