提交 5292f76f 编写于 作者: J Johannes Rieken

add (optional) copy function #47475

上级 e5246522
......@@ -192,31 +192,35 @@ export interface IWatchOptions {
exclude?: string[];
}
export interface IFileSystemProviderBase {
export enum FileSystemProviderCapabilities {
FileReadWrite = 0b1,
FileOpenReadWriteClose = 0b10,
FileFolderCopy = 0b100
}
export interface IFileSystemProvider {
readonly capabilities: FileSystemProviderCapabilities;
onDidChangeFile: Event<IFileChange[]>;
watch(resource: URI, opts: IWatchOptions): IDisposable;
stat(resource: URI): TPromise<IStat>;
rename(from: URI, to: URI, opts: { flags: FileOpenFlags }): TPromise<IStat>;
mkdir(resource: URI): TPromise<IStat>;
readdir(resource: URI): TPromise<[string, IStat][]>;
delete(resource: URI): TPromise<void>;
}
export interface ISimpleReadWriteProvider {
_type: 'simple';
readFile(resource: URI, opts: { flags: FileOpenFlags }): TPromise<Uint8Array>;
writeFile(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise<void>;
}
rename(from: URI, to: URI, opts: { flags: FileOpenFlags }): TPromise<IStat>;
copy?(from: URI, to: URI, opts: { flags: FileOpenFlags }): TPromise<IStat>;
export interface IReadWriteProvider {
_type: 'chunked';
open(resource: URI, opts: { flags: FileOpenFlags }): TPromise<number>;
close(fd: number): TPromise<void>;
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
}
readFile?(resource: URI, opts: { flags: FileOpenFlags }): TPromise<Uint8Array>;
writeFile?(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise<void>;
export type IFileSystemProvider = (IFileSystemProviderBase & ISimpleReadWriteProvider) | (IFileSystemProviderBase & IReadWriteProvider);
open?(resource: URI, opts: { flags: FileOpenFlags }): TPromise<number>;
close?(fd: number): TPromise<void>;
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): TPromise<number>;
}
export enum FileOperation {
CREATE,
......
......@@ -297,15 +297,21 @@ declare module 'vscode' {
/**
* Rename a file or folder.
*
* @param oldUri The exiting file or folder
* @param newUri The target location
* @param oldUri The existing file or folder.
* @param newUri The target location.
* @param token A cancellation token.
*/
rename(oldUri: Uri, newUri: Uri, options: { flags: FileOpenFlags }, token: CancellationToken): FileStat2 | Thenable<FileStat2>;
// todo@remote
// helps with performance bigly
// copy?(from: Uri, to: Uri): FileStat2 | Thenable<FileStat2>;
/**
* Copy files or folders. Implementing this function is optional but it will speedup
* the copy operation.
*
* @param uri The existing file or folder.
* @param target The target location.
* @param token A cancellation token.
*/
copy?(uri: Uri, target: Uri, options: { flags: FileOpenFlags }, token: CancellationToken): FileStat2 | Thenable<FileStat2>;
// todo@remote
// ? useTrash, expose trash
......
......@@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { FileOpenFlags, IFileChange, IFileService, IFileSystemProviderBase, ISimpleReadWriteProvider, IStat, IWatchOptions, FileError } from 'vs/platform/files/common/files';
import { FileOpenFlags, IFileChange, IFileService, IStat, IWatchOptions, FileError, FileSystemProviderCapabilities, IFileSystemProvider } from 'vs/platform/files/common/files';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../node/extHost.protocol';
......@@ -30,9 +30,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
this._fileProvider.clear();
}
$registerFileSystemProvider(handle: number, scheme: string): void {
this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, handle, this._proxy));
$registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void {
this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, capabilities, handle, this._proxy));
}
$unregisterProvider(handle: number): void {
dispose(this._fileProvider.get(handle));
this._fileProvider.delete(handle);
......@@ -43,21 +44,22 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
}
}
class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemProviderBase {
_type: 'simple' = 'simple';
class RemoteFileSystemProvider implements IFileSystemProvider {
private readonly _onDidChange = new Emitter<IFileChange[]>();
private readonly _registrations: IDisposable[];
readonly onDidChangeFile: Event<IFileChange[]> = this._onDidChange.event;
readonly capabilities: FileSystemProviderCapabilities;
constructor(
fileService: IFileService,
scheme: string,
capabilities: FileSystemProviderCapabilities,
private readonly _handle: number,
private readonly _proxy: ExtHostFileSystemShape
) {
this.capabilities = capabilities;
this._registrations = [fileService.registerProvider(scheme, this)];
}
......@@ -91,29 +93,37 @@ class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemP
throw err;
});
}
readFile(resource: URI, opts: { flags: FileOpenFlags }): TPromise<Uint8Array, any> {
return this._proxy.$readFile(this._handle, resource, opts.flags).then(encoded => {
return Buffer.from(encoded, 'base64');
});
}
writeFile(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise<void, any> {
let encoded = Buffer.isBuffer(content)
? content.toString('base64')
: Buffer.from(content.buffer, content.byteOffset, content.byteLength).toString('base64');
return this._proxy.$writeFile(this._handle, resource, encoded, opts.flags);
}
delete(resource: URI): TPromise<void, any> {
return this._proxy.$delete(this._handle, resource);
}
rename(resource: URI, target: URI, opts: { flags: FileOpenFlags }): TPromise<IStat, any> {
return this._proxy.$rename(this._handle, resource, target, opts.flags);
}
mkdir(resource: URI): TPromise<IStat, any> {
return this._proxy.$mkdir(this._handle, resource);
}
readdir(resource: URI): TPromise<[string, IStat][], any> {
return this._proxy.$readdir(this._handle, resource);
}
rename(resource: URI, target: URI, opts: { flags: FileOpenFlags }): TPromise<IStat, any> {
return this._proxy.$rename(this._handle, resource, target, opts.flags);
}
copy(resource: URI, target: URI, opts: { flags: FileOpenFlags }): TPromise<IStat, any> {
return this._proxy.$copy(this._handle, resource, target, opts.flags);
}
}
......@@ -42,7 +42,7 @@ import { ITreeItem } from 'vs/workbench/common/views';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { SerializedError } from 'vs/base/common/errors';
import { IStat, FileChangeType, FileOpenFlags, IWatchOptions } from 'vs/platform/files/common/files';
import { IStat, FileChangeType, FileOpenFlags, IWatchOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
import { ISingleEditOperation } from 'vs/editor/common/model';
......@@ -382,7 +382,7 @@ export interface IFileChangeDto {
}
export interface MainThreadFileSystemShape extends IDisposable {
$registerFileSystemProvider(handle: number, scheme: string): void;
$registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void;
$unregisterProvider(handle: number): void;
$onFileSystemChange(handle: number, resource: IFileChangeDto[]): void;
}
......@@ -572,6 +572,7 @@ export interface ExtHostFileSystemShape {
$readFile(handle: number, resource: UriComponents, flags: FileOpenFlags): TPromise<string>;
$writeFile(handle: number, resource: UriComponents, base64Encoded: string, flags: FileOpenFlags): TPromise<void>;
$rename(handle: number, resource: UriComponents, target: UriComponents, flags: FileOpenFlags): TPromise<IStat>;
$copy(handle: number, resource: UriComponents, target: UriComponents, flags: FileOpenFlags): TPromise<IStat>;
$mkdir(handle: number, resource: UriComponents): TPromise<IStat>;
$readdir(handle: number, resource: UriComponents): TPromise<[string, IStat][]>;
$delete(handle: number, resource: UriComponents): TPromise<void>;
......
......@@ -208,7 +208,13 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
this._linkProvider.add(scheme);
this._usedSchemes.add(scheme);
this._fsProvider.set(handle, provider);
this._proxy.$registerFileSystemProvider(handle, scheme);
let capabilites = files.FileSystemProviderCapabilities.FileReadWrite;
if (typeof provider.copy === 'function') {
capabilites += files.FileSystemProviderCapabilities.FileFolderCopy;
}
this._proxy.$registerFileSystemProvider(handle, scheme, capabilites);
const subscription = provider.onDidChangeFile(event => {
let newEvent = event.map(e => {
......@@ -244,9 +250,11 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
$stat(handle: number, resource: UriComponents): TPromise<files.IStat, any> {
return asWinJsPromise(token => this._fsProvider.get(handle).stat(URI.revive(resource), token));
}
$readdir(handle: number, resource: UriComponents): TPromise<[string, files.IStat][], any> {
return asWinJsPromise(token => this._fsProvider.get(handle).readDirectory(URI.revive(resource), token));
}
$readFile(handle: number, resource: UriComponents, flags: files.FileOpenFlags): TPromise<string> {
return asWinJsPromise(token => {
return this._fsProvider.get(handle).readFile(URI.revive(resource), { flags }, token);
......@@ -254,24 +262,34 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
return Buffer.isBuffer(data) ? data.toString('base64') : Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64');
});
}
$writeFile(handle: number, resource: UriComponents, base64Content: string, flags: files.FileOpenFlags): TPromise<void, any> {
return asWinJsPromise(token => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), { flags }, token));
}
$delete(handle: number, resource: UriComponents): TPromise<void, any> {
return asWinJsPromise(token => this._fsProvider.get(handle).delete(URI.revive(resource), token));
}
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, flags: files.FileOpenFlags): TPromise<files.IStat, any> {
return asWinJsPromise(token => this._fsProvider.get(handle).rename(URI.revive(oldUri), URI.revive(newUri), { flags }, token));
}
$copy(handle: number, oldUri: UriComponents, newUri: UriComponents, flags: files.FileOpenFlags): TPromise<files.IStat, any> {
return asWinJsPromise(token => this._fsProvider.get(handle).copy(URI.revive(oldUri), URI.revive(newUri), { flags }, token));
}
$mkdir(handle: number, resource: UriComponents): TPromise<files.IStat, any> {
return asWinJsPromise(token => this._fsProvider.get(handle).createDirectory(URI.revive(resource), token));
}
$watch(handle: number, session: number, resource: UriComponents, opts: files.IWatchOptions): void {
asWinJsPromise(token => {
let subscription = this._fsProvider.get(handle).watch(URI.revive(resource), opts);
this._watches.set(session, subscription);
});
}
$unwatch(handle: number, session: number): void {
let subscription = this._watches.get(session);
if (subscription) {
......
......@@ -16,7 +16,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
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';
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';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService } from 'vs/platform/storage/common/storage';
......@@ -522,31 +522,39 @@ export class RemoteFileService extends FileService {
return super.copyFile(source, target, overwrite);
}
const prepare = overwrite
? this.del(target).then(undefined, err => { /*ignore*/ })
: TPromise.as(null);
return this._withProvider(target).then(provider => {
return prepare.then(() => {
// todo@ben, can only copy text files
// https://github.com/Microsoft/vscode/issues/41543
return this.resolveContent(source, { acceptTextOnly: true }).then(content => {
return this._withProvider(target).then(provider => {
return this._writeFile(
provider, target,
new StringSnapshot(content.value),
{ encoding: content.encoding },
FileOpenFlags.Create | FileOpenFlags.Write
).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat));
return fileStat;
if (source.scheme === target.scheme && (provider.capabilities & FileSystemProviderCapabilities.FileFolderCopy)) {
// good: provider supports copy withing scheme
return provider.copy(source, target, { flags: 0 }).then(stat => toIFileStat(provider, [target, stat]));
}
const prepare = overwrite
? this.del(target).then(undefined, err => { /*ignore*/ })
: TPromise.as(null);
return prepare.then(() => {
// todo@ben, can only copy text files
// https://github.com/Microsoft/vscode/issues/41543
return this.resolveContent(source, { acceptTextOnly: true }).then(content => {
return this._withProvider(target).then(provider => {
return this._writeFile(
provider, target,
new StringSnapshot(content.value),
{ encoding: content.encoding },
FileOpenFlags.Create | FileOpenFlags.Write
).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat));
return fileStat;
});
}, err => {
if (err instanceof Error && err.name === 'ENOPRO') {
// file scheme
return super.updateContent(target, content.value, { encoding: content.encoding });
} else {
return TPromise.wrapError(err);
}
});
}, err => {
if (err instanceof Error && err.name === 'ENOPRO') {
// file scheme
return super.updateContent(target, content.value, { encoding: content.encoding });
} else {
return TPromise.wrapError(err);
}
});
});
});
......
......@@ -7,16 +7,20 @@
import { Readable, Writable } from 'stream';
import { UTF8 } from 'vs/base/node/encoding';
import URI from 'vs/base/common/uri';
import { IFileSystemProvider, ITextSnapshot, ISimpleReadWriteProvider, IReadWriteProvider, FileOpenFlags } from 'vs/platform/files/common/files';
import { IFileSystemProvider, ITextSnapshot, FileSystemProviderCapabilities, FileOpenFlags } from 'vs/platform/files/common/files';
import { illegalArgument } from 'vs/base/common/errors';
export function createWritableOfProvider(provider: IFileSystemProvider, resource: URI, flags: FileOpenFlags): Writable {
switch (provider._type) {
case 'simple': return createSimpleWritable(provider, resource, flags);
case 'chunked': return createWritable(provider, resource, flags);
if (provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose) {
return createWritable(provider, resource, flags);
} else if (provider.capabilities & FileSystemProviderCapabilities.FileReadWrite) {
return createSimpleWritable(provider, resource, flags);
} else {
throw illegalArgument();
}
}
function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI, flags: FileOpenFlags): Writable {
function createSimpleWritable(provider: IFileSystemProvider, resource: URI, flags: FileOpenFlags): Writable {
return new class extends Writable {
_chunks: Buffer[] = [];
constructor(opts?) {
......@@ -37,7 +41,7 @@ function createSimpleWritable(provider: ISimpleReadWriteProvider, resource: URI,
};
}
function createWritable(provider: IReadWriteProvider, resource: URI, flags: FileOpenFlags): Writable {
function createWritable(provider: IFileSystemProvider, resource: URI, flags: FileOpenFlags): Writable {
return new class extends Writable {
_fd: number;
_pos: number;
......@@ -67,13 +71,16 @@ function createWritable(provider: IReadWriteProvider, resource: URI, flags: File
}
export function createReadableOfProvider(provider: IFileSystemProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
switch (provider._type) {
case 'simple': return createSimpleReadable(provider, resource, position, flags);
case 'chunked': return createReadable(provider, resource, position, flags);
if (provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose) {
return createReadable(provider, resource, position, flags);
} else if (provider.capabilities & FileSystemProviderCapabilities.FileReadWrite) {
return createSimpleReadable(provider, resource, position, flags);
} else {
throw illegalArgument();
}
}
function createReadable(provider: IReadWriteProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
function createReadable(provider: IFileSystemProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
return new class extends Readable {
_fd: number;
_pos: number = position;
......@@ -117,7 +124,7 @@ function createReadable(provider: IReadWriteProvider, resource: URI, position: n
};
}
function createSimpleReadable(provider: ISimpleReadWriteProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
function createSimpleReadable(provider: IFileSystemProvider, resource: URI, position: number, flags: FileOpenFlags): Readable {
return new class extends Readable {
_readOperation: Thenable<any>;
_read(size?: number): void {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册