Skip to content

Commit 5292f76

Browse files
committed
add (optional) copy function microsoft#47475
1 parent e524652 commit 5292f76

7 files changed

Lines changed: 122 additions & 68 deletions

File tree

src/vs/platform/files/common/files.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -192,31 +192,35 @@ export interface IWatchOptions {
192192
exclude?: string[];
193193
}
194194

195-
export interface IFileSystemProviderBase {
195+
export enum FileSystemProviderCapabilities {
196+
FileReadWrite = 0b1,
197+
FileOpenReadWriteClose = 0b10,
198+
FileFolderCopy = 0b100
199+
}
200+
201+
export interface IFileSystemProvider {
202+
203+
readonly capabilities: FileSystemProviderCapabilities;
204+
196205
onDidChangeFile: Event<IFileChange[]>;
197206
watch(resource: URI, opts: IWatchOptions): IDisposable;
207+
198208
stat(resource: URI): TPromise<IStat>;
199-
rename(from: URI, to: URI, opts: { flags: FileOpenFlags }): TPromise<IStat>;
200209
mkdir(resource: URI): TPromise<IStat>;
201210
readdir(resource: URI): TPromise<[string, IStat][]>;
202211
delete(resource: URI): TPromise<void>;
203-
}
204212

205-
export interface ISimpleReadWriteProvider {
206-
_type: 'simple';
207-
readFile(resource: URI, opts: { flags: FileOpenFlags }): TPromise<Uint8Array>;
208-
writeFile(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise<void>;
209-
}
213+
rename(from: URI, to: URI, opts: { flags: FileOpenFlags }): TPromise<IStat>;
214+
copy?(from: URI, to: URI, opts: { flags: FileOpenFlags }): TPromise<IStat>;
210215

211-
export interface IReadWriteProvider {
212-
_type: 'chunked';
213-
open(resource: URI, opts: { flags: FileOpenFlags }): TPromise<number>;
214-
close(fd: number): TPromise<void>;
215-
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
216-
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
217-
}
216+
readFile?(resource: URI, opts: { flags: FileOpenFlags }): TPromise<Uint8Array>;
217+
writeFile?(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise<void>;
218218

219-
export type IFileSystemProvider = (IFileSystemProviderBase & ISimpleReadWriteProvider) | (IFileSystemProviderBase & IReadWriteProvider);
219+
open?(resource: URI, opts: { flags: FileOpenFlags }): TPromise<number>;
220+
close?(fd: number): TPromise<void>;
221+
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
222+
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
223+
}
220224

221225
export enum FileOperation {
222226
CREATE,

src/vs/vscode.proposed.d.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -297,15 +297,21 @@ declare module 'vscode' {
297297
/**
298298
* Rename a file or folder.
299299
*
300-
* @param oldUri The exiting file or folder
301-
* @param newUri The target location
300+
* @param oldUri The existing file or folder.
301+
* @param newUri The target location.
302302
* @param token A cancellation token.
303303
*/
304304
rename(oldUri: Uri, newUri: Uri, options: { flags: FileOpenFlags }, token: CancellationToken): FileStat2 | Thenable<FileStat2>;
305305

306-
// todo@remote
307-
// helps with performance bigly
308-
// copy?(from: Uri, to: Uri): FileStat2 | Thenable<FileStat2>;
306+
/**
307+
* Copy files or folders. Implementing this function is optional but it will speedup
308+
* the copy operation.
309+
*
310+
* @param uri The existing file or folder.
311+
* @param target The target location.
312+
* @param token A cancellation token.
313+
*/
314+
copy?(uri: Uri, target: Uri, options: { flags: FileOpenFlags }, token: CancellationToken): FileStat2 | Thenable<FileStat2>;
309315

310316
// todo@remote
311317
// ? useTrash, expose trash

src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event';
88
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
99
import URI from 'vs/base/common/uri';
1010
import { TPromise } from 'vs/base/common/winjs.base';
11-
import { FileOpenFlags, IFileChange, IFileService, IFileSystemProviderBase, ISimpleReadWriteProvider, IStat, IWatchOptions, FileError } from 'vs/platform/files/common/files';
11+
import { FileOpenFlags, IFileChange, IFileService, IStat, IWatchOptions, FileError, FileSystemProviderCapabilities, IFileSystemProvider } from 'vs/platform/files/common/files';
1212
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
1313
import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../node/extHost.protocol';
1414

@@ -30,9 +30,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
3030
this._fileProvider.clear();
3131
}
3232

33-
$registerFileSystemProvider(handle: number, scheme: string): void {
34-
this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, handle, this._proxy));
33+
$registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void {
34+
this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, capabilities, handle, this._proxy));
3535
}
36+
3637
$unregisterProvider(handle: number): void {
3738
dispose(this._fileProvider.get(handle));
3839
this._fileProvider.delete(handle);
@@ -43,21 +44,22 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
4344
}
4445
}
4546

46-
class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemProviderBase {
47-
48-
_type: 'simple' = 'simple';
47+
class RemoteFileSystemProvider implements IFileSystemProvider {
4948

5049
private readonly _onDidChange = new Emitter<IFileChange[]>();
5150
private readonly _registrations: IDisposable[];
5251

5352
readonly onDidChangeFile: Event<IFileChange[]> = this._onDidChange.event;
53+
readonly capabilities: FileSystemProviderCapabilities;
5454

5555
constructor(
5656
fileService: IFileService,
5757
scheme: string,
58+
capabilities: FileSystemProviderCapabilities,
5859
private readonly _handle: number,
5960
private readonly _proxy: ExtHostFileSystemShape
6061
) {
62+
this.capabilities = capabilities;
6163
this._registrations = [fileService.registerProvider(scheme, this)];
6264
}
6365

@@ -91,29 +93,37 @@ class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemP
9193
throw err;
9294
});
9395
}
96+
9497
readFile(resource: URI, opts: { flags: FileOpenFlags }): TPromise<Uint8Array, any> {
9598
return this._proxy.$readFile(this._handle, resource, opts.flags).then(encoded => {
9699
return Buffer.from(encoded, 'base64');
97100
});
98101
}
102+
99103
writeFile(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise<void, any> {
100104
let encoded = Buffer.isBuffer(content)
101105
? content.toString('base64')
102106
: Buffer.from(content.buffer, content.byteOffset, content.byteLength).toString('base64');
103107
return this._proxy.$writeFile(this._handle, resource, encoded, opts.flags);
104108
}
109+
105110
delete(resource: URI): TPromise<void, any> {
106111
return this._proxy.$delete(this._handle, resource);
107112
}
108-
rename(resource: URI, target: URI, opts: { flags: FileOpenFlags }): TPromise<IStat, any> {
109-
return this._proxy.$rename(this._handle, resource, target, opts.flags);
110-
}
113+
111114
mkdir(resource: URI): TPromise<IStat, any> {
112115
return this._proxy.$mkdir(this._handle, resource);
113116
}
117+
114118
readdir(resource: URI): TPromise<[string, IStat][], any> {
115119
return this._proxy.$readdir(this._handle, resource);
116120
}
117121

122+
rename(resource: URI, target: URI, opts: { flags: FileOpenFlags }): TPromise<IStat, any> {
123+
return this._proxy.$rename(this._handle, resource, target, opts.flags);
124+
}
118125

126+
copy(resource: URI, target: URI, opts: { flags: FileOpenFlags }): TPromise<IStat, any> {
127+
return this._proxy.$copy(this._handle, resource, target, opts.flags);
128+
}
119129
}

src/vs/workbench/api/node/extHost.protocol.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { ITreeItem } from 'vs/workbench/common/views';
4242
import { ThemeColor } from 'vs/platform/theme/common/themeService';
4343
import { IDisposable } from 'vs/base/common/lifecycle';
4444
import { SerializedError } from 'vs/base/common/errors';
45-
import { IStat, FileChangeType, FileOpenFlags, IWatchOptions } from 'vs/platform/files/common/files';
45+
import { IStat, FileChangeType, FileOpenFlags, IWatchOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
4646
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
4747
import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
4848
import { ISingleEditOperation } from 'vs/editor/common/model';
@@ -382,7 +382,7 @@ export interface IFileChangeDto {
382382
}
383383

384384
export interface MainThreadFileSystemShape extends IDisposable {
385-
$registerFileSystemProvider(handle: number, scheme: string): void;
385+
$registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void;
386386
$unregisterProvider(handle: number): void;
387387
$onFileSystemChange(handle: number, resource: IFileChangeDto[]): void;
388388
}
@@ -572,6 +572,7 @@ export interface ExtHostFileSystemShape {
572572
$readFile(handle: number, resource: UriComponents, flags: FileOpenFlags): TPromise<string>;
573573
$writeFile(handle: number, resource: UriComponents, base64Encoded: string, flags: FileOpenFlags): TPromise<void>;
574574
$rename(handle: number, resource: UriComponents, target: UriComponents, flags: FileOpenFlags): TPromise<IStat>;
575+
$copy(handle: number, resource: UriComponents, target: UriComponents, flags: FileOpenFlags): TPromise<IStat>;
575576
$mkdir(handle: number, resource: UriComponents): TPromise<IStat>;
576577
$readdir(handle: number, resource: UriComponents): TPromise<[string, IStat][]>;
577578
$delete(handle: number, resource: UriComponents): TPromise<void>;

src/vs/workbench/api/node/extHostFileSystem.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,13 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
208208
this._linkProvider.add(scheme);
209209
this._usedSchemes.add(scheme);
210210
this._fsProvider.set(handle, provider);
211-
this._proxy.$registerFileSystemProvider(handle, scheme);
211+
212+
let capabilites = files.FileSystemProviderCapabilities.FileReadWrite;
213+
if (typeof provider.copy === 'function') {
214+
capabilites += files.FileSystemProviderCapabilities.FileFolderCopy;
215+
}
216+
217+
this._proxy.$registerFileSystemProvider(handle, scheme, capabilites);
212218

213219
const subscription = provider.onDidChangeFile(event => {
214220
let newEvent = event.map(e => {
@@ -244,34 +250,46 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
244250
$stat(handle: number, resource: UriComponents): TPromise<files.IStat, any> {
245251
return asWinJsPromise(token => this._fsProvider.get(handle).stat(URI.revive(resource), token));
246252
}
253+
247254
$readdir(handle: number, resource: UriComponents): TPromise<[string, files.IStat][], any> {
248255
return asWinJsPromise(token => this._fsProvider.get(handle).readDirectory(URI.revive(resource), token));
249256
}
257+
250258
$readFile(handle: number, resource: UriComponents, flags: files.FileOpenFlags): TPromise<string> {
251259
return asWinJsPromise(token => {
252260
return this._fsProvider.get(handle).readFile(URI.revive(resource), { flags }, token);
253261
}).then(data => {
254262
return Buffer.isBuffer(data) ? data.toString('base64') : Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64');
255263
});
256264
}
265+
257266
$writeFile(handle: number, resource: UriComponents, base64Content: string, flags: files.FileOpenFlags): TPromise<void, any> {
258267
return asWinJsPromise(token => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), { flags }, token));
259268
}
269+
260270
$delete(handle: number, resource: UriComponents): TPromise<void, any> {
261271
return asWinJsPromise(token => this._fsProvider.get(handle).delete(URI.revive(resource), token));
262272
}
273+
263274
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, flags: files.FileOpenFlags): TPromise<files.IStat, any> {
264275
return asWinJsPromise(token => this._fsProvider.get(handle).rename(URI.revive(oldUri), URI.revive(newUri), { flags }, token));
265276
}
277+
278+
$copy(handle: number, oldUri: UriComponents, newUri: UriComponents, flags: files.FileOpenFlags): TPromise<files.IStat, any> {
279+
return asWinJsPromise(token => this._fsProvider.get(handle).copy(URI.revive(oldUri), URI.revive(newUri), { flags }, token));
280+
}
281+
266282
$mkdir(handle: number, resource: UriComponents): TPromise<files.IStat, any> {
267283
return asWinJsPromise(token => this._fsProvider.get(handle).createDirectory(URI.revive(resource), token));
268284
}
285+
269286
$watch(handle: number, session: number, resource: UriComponents, opts: files.IWatchOptions): void {
270287
asWinJsPromise(token => {
271288
let subscription = this._fsProvider.get(handle).watch(URI.revive(resource), opts);
272289
this._watches.set(session, subscription);
273290
});
274291
}
292+
275293
$unwatch(handle: number, session: number): void {
276294
let subscription = this._watches.get(session);
277295
if (subscription) {

src/vs/workbench/services/files/electron-browser/remoteFileService.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res
1616
import { localize } from 'vs/nls';
1717
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1818
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
19-
import { FileChangesEvent, FileError, FileOpenFlags, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileType2, IContent, ICreateFileOptions, IFileStat, IFileSystemProvider, IFilesConfiguration, IResolveContentOptions, IResolveFileOptions, IResolveFileResult, IStat, IStreamContent, ITextSnapshot, IUpdateContentOptions, StringSnapshot } from 'vs/platform/files/common/files';
19+
import { FileChangesEvent, FileError, FileOpenFlags, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileType2, IContent, ICreateFileOptions, IFileStat, IFileSystemProvider, IFilesConfiguration, IResolveContentOptions, IResolveFileOptions, IResolveFileResult, IStat, IStreamContent, ITextSnapshot, IUpdateContentOptions, StringSnapshot, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
2020
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
2121
import { INotificationService } from 'vs/platform/notification/common/notification';
2222
import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -522,31 +522,39 @@ export class RemoteFileService extends FileService {
522522
return super.copyFile(source, target, overwrite);
523523
}
524524

525-
const prepare = overwrite
526-
? this.del(target).then(undefined, err => { /*ignore*/ })
527-
: TPromise.as(null);
525+
return this._withProvider(target).then(provider => {
528526

529-
return prepare.then(() => {
530-
// todo@ben, can only copy text files
531-
// https://github.com/Microsoft/vscode/issues/41543
532-
return this.resolveContent(source, { acceptTextOnly: true }).then(content => {
533-
return this._withProvider(target).then(provider => {
534-
return this._writeFile(
535-
provider, target,
536-
new StringSnapshot(content.value),
537-
{ encoding: content.encoding },
538-
FileOpenFlags.Create | FileOpenFlags.Write
539-
).then(fileStat => {
540-
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat));
541-
return fileStat;
527+
if (source.scheme === target.scheme && (provider.capabilities & FileSystemProviderCapabilities.FileFolderCopy)) {
528+
// good: provider supports copy withing scheme
529+
return provider.copy(source, target, { flags: 0 }).then(stat => toIFileStat(provider, [target, stat]));
530+
}
531+
532+
const prepare = overwrite
533+
? this.del(target).then(undefined, err => { /*ignore*/ })
534+
: TPromise.as(null);
535+
536+
return prepare.then(() => {
537+
// todo@ben, can only copy text files
538+
// https://github.com/Microsoft/vscode/issues/41543
539+
return this.resolveContent(source, { acceptTextOnly: true }).then(content => {
540+
return this._withProvider(target).then(provider => {
541+
return this._writeFile(
542+
provider, target,
543+
new StringSnapshot(content.value),
544+
{ encoding: content.encoding },
545+
FileOpenFlags.Create | FileOpenFlags.Write
546+
).then(fileStat => {
547+
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat));
548+
return fileStat;
549+
});
550+
}, err => {
551+
if (err instanceof Error && err.name === 'ENOPRO') {
552+
// file scheme
553+
return super.updateContent(target, content.value, { encoding: content.encoding });
554+
} else {
555+
return TPromise.wrapError(err);
556+
}
542557
});
543-
}, err => {
544-
if (err instanceof Error && err.name === 'ENOPRO') {
545-
// file scheme
546-
return super.updateContent(target, content.value, { encoding: content.encoding });
547-
} else {
548-
return TPromise.wrapError(err);
549-
}
550558
});
551559
});
552560
});

src/vs/workbench/services/files/electron-browser/streams.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@
77
import { Readable, Writable } from 'stream';
88
import { UTF8 } from 'vs/base/node/encoding';
99
import URI from 'vs/base/common/uri';
10-
import { IFileSystemProvider, ITextSnapshot, ISimpleReadWriteProvider, IReadWriteProvider, FileOpenFlags } from 'vs/platform/files/common/files';
10+
import { IFileSystemProvider, ITextSnapshot, FileSystemProviderCapabilities, FileOpenFlags } from 'vs/platform/files/common/files';
11+
import { illegalArgument } from 'vs/base/common/errors';
1112

1213
export function createWritableOfProvider(provider: IFileSystemProvider, resource: URI, flags: FileOpenFlags): Writable {
13-
switch (provider._type) {
14-
case 'simple': return createSimpleWritable(provider, resource, flags);
15-
case 'chunked': return createWritable(provider, resource, flags);
14+
if (provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose) {
15+
return createWritable(provider, resource, flags);
16+
} else if (provider.capabilities & FileSystemProviderCapabilities.FileReadWrite) {
17+
return createSimpleWritable(provider, resource, flags);
18+
} else {
19+
throw illegalArgument();
1620
}
1721
}
1822

19-
function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI, flags: FileOpenFlags): Writable {
23+
function createSimpleWritable(provider: IFileSystemProvider, resource: URI, flags: FileOpenFlags): Writable {
2024
return new class extends Writable {
2125
_chunks: Buffer[] = [];
2226
constructor(opts?) {
@@ -37,7 +41,7 @@ function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI,
3741
};
3842
}
3943

40-
function createWritable(provider: IReadWriteProvider, resource: URI, flags: FileOpenFlags): Writable {
44+
function createWritable(provider: IFileSystemProvider, resource: URI, flags: FileOpenFlags): Writable {
4145
return new class extends Writable {
4246
_fd: number;
4347
_pos: number;
@@ -67,13 +71,16 @@ function createWritable(provider: IReadWriteProvider, resource: URI, flags: File
6771
}
6872

6973
export function createReadableOfProvider(provider: IFileSystemProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
70-
switch (provider._type) {
71-
case 'simple': return createSimpleReadable(provider, resource, position, flags);
72-
case 'chunked': return createReadable(provider, resource, position, flags);
74+
if (provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose) {
75+
return createReadable(provider, resource, position, flags);
76+
} else if (provider.capabilities & FileSystemProviderCapabilities.FileReadWrite) {
77+
return createSimpleReadable(provider, resource, position, flags);
78+
} else {
79+
throw illegalArgument();
7380
}
7481
}
7582

76-
function createReadable(provider: IReadWriteProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
83+
function createReadable(provider: IFileSystemProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
7784
return new class extends Readable {
7885
_fd: number;
7986
_pos: number = position;
@@ -117,7 +124,7 @@ function createReadable(provider: IReadWriteProvider, resource: URI, position: n
117124
};
118125
}
119126

120-
function createSimpleReadable(provider: ISimpleReadWriteProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
127+
function createSimpleReadable(provider: IFileSystemProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
121128
return new class extends Readable {
122129
_readOperation: Thenable<any>;
123130
_read(size?: number): void {

0 commit comments

Comments
 (0)