Skip to content

Commit

Permalink
add FileOpenFlags, FileErrors, and a bit of polish, #47475
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed Apr 13, 2018
1 parent f7ec491 commit e7e8812
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 67 deletions.
6 changes: 3 additions & 3 deletions src/vs/base/node/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface IDecodeStreamOptions {
overwriteEncoding?(detectedEncoding: string): string;
}

export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): TPromise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> {
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> {

if (!options.minBytesRequiredForDetection) {
options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_GUESS_BUFFER_MAX_LEN : NO_GUESS_BUFFER_MAX_LEN;
Expand All @@ -33,7 +33,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
options.overwriteEncoding = detected => detected || UTF8;
}

return new TPromise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => {
return new Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => {
readable.pipe(new class extends Writable {

private _decodeStream: NodeJS.ReadWriteStream;
Expand Down Expand Up @@ -76,7 +76,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions

_startDecodeStream(callback: Function): void {

this._decodeStreamConstruction = TPromise.as(detectEncodingFromBuffer({
this._decodeStreamConstruction = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this._buffer), bytesRead: this._bytesBuffered
}, options.guessEncoding)).then(detected => {
detected.encoding = options.overwriteEncoding(detected.encoding);
Expand Down
25 changes: 22 additions & 3 deletions src/vs/platform/files/common/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,25 @@ export enum FileType2 {
SymbolicLink = 4,
}

export class FileError extends Error {

static readonly EEXIST = new FileError('EEXIST');
static readonly ENOENT = new FileError('ENOENT');
static readonly ENOTDIR = new FileError('ENOTDIR');
static readonly EISDIR = new FileError('EISDIR');

constructor(readonly code: string) {
super(code);
}
}

export enum FileOpenFlags {
Read = 0b0001,
Write = 0b0010,
Create = 0b0100,
Exclusive = 0b1000
}

export interface IStat {
mtime: number;
size: number;
Expand All @@ -173,13 +192,13 @@ export interface IFileSystemProviderBase {

export interface ISimpleReadWriteProvider {
_type: 'simple';
readFile(resource: URI): TPromise<Uint8Array>;
writeFile(resource: URI, content: Uint8Array): TPromise<void>;
readFile(resource: URI, opts: { flags: FileOpenFlags }): TPromise<Uint8Array>;
writeFile(resource: URI, content: Uint8Array, opts: { flags: FileOpenFlags }): TPromise<void>;
}

export interface IReadWriteProvider {
_type: 'chunked';
open(resource: URI, options: { mode: string }): TPromise<number>;
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>;
Expand Down
24 changes: 21 additions & 3 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,18 @@ declare module 'vscode' {
// create(resource: Uri): Thenable<FileStat>;
}

export class FileError extends Error {

static readonly EEXIST: FileError;
static readonly ENOENT: FileError;
static readonly ENOTDIR: FileError;
static readonly EISDIR: FileError;

readonly code: string;

constructor(code: string, message?: string);
}

export enum FileChangeType2 {
Changed = 1,
Created = 2,
Expand All @@ -246,12 +258,18 @@ declare module 'vscode' {
size: number;
}

export enum FileOpenFlags {
Read = 0b0001,
Write = 0b0010,
Create = 0b0100,
Exclusive = 0b1000
}

// todo@joh discover files etc
// todo@joh add open/close calls?
export interface FileSystemProvider2 {

_version: 4;
_version: 5;

/**
* An event to signal that a resource has been created, changed, or deleted.
Expand Down Expand Up @@ -284,7 +302,7 @@ declare module 'vscode' {
* @param token A cancellation token.
* @return A thenable that resolves to an array of bytes.
*/
readFile(uri: Uri, token: CancellationToken): Uint8Array | Thenable<Uint8Array>;
readFile(uri: Uri, options: { flags: FileOpenFlags }, token: CancellationToken): Uint8Array | Thenable<Uint8Array>;

/**
* Write data to a file, replacing its entire contents.
Expand All @@ -293,7 +311,7 @@ declare module 'vscode' {
* @param content The new content of the file.
* @param token A cancellation token.
*/
writeFile(uri: Uri, content: Uint8Array, token: CancellationToken): void | Thenable<void>;
writeFile(uri: Uri, content: Uint8Array, options: { flags: FileOpenFlags }, token: CancellationToken): void | Thenable<void>;

/**
* Rename a file or folder.
Expand Down
10 changes: 5 additions & 5 deletions src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import URI, { UriComponents } from 'vs/base/common/uri';
import { TPromise, PPromise } from 'vs/base/common/winjs.base';
import { ExtHostContext, MainContext, IExtHostContext, MainThreadFileSystemShape, ExtHostFileSystemShape, IFileChangeDto } from '../node/extHost.protocol';
import { IFileService, IStat, IFileChange, ISimpleReadWriteProvider, IFileSystemProviderBase } from 'vs/platform/files/common/files';
import { IFileService, IStat, IFileChange, ISimpleReadWriteProvider, IFileSystemProviderBase, FileOpenFlags } from 'vs/platform/files/common/files';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
Expand Down Expand Up @@ -97,16 +97,16 @@ class RemoteFileSystemProvider implements ISimpleReadWriteProvider, IFileSystemP
stat(resource: URI): TPromise<IStat, any> {
return this._proxy.$stat(this._handle, resource);
}
readFile(resource: URI): TPromise<Uint8Array, any> {
return this._proxy.$readFile(this._handle, resource).then(encoded => {
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): TPromise<void, any> {
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);
return this._proxy.$writeFile(this._handle, resource, encoded, opts.flags);
}
delete(resource: URI): TPromise<void, any> {
return this._proxy.$delete(this._handle, resource);
Expand Down
3 changes: 3 additions & 0 deletions src/vs/workbench/api/node/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { OverviewRulerLane } from 'vs/editor/common/model';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview';
import * as files from 'vs/platform/files/common/files';

export interface IExtensionApiFactory {
(extension: IExtensionDescription): typeof vscode;
Expand Down Expand Up @@ -706,6 +707,8 @@ export function createApiFactory(
FileType: extHostTypes.FileType,
FileChangeType2: extHostTypes.FileChangeType2,
FileType2: extHostTypes.FileType2,
FileOpenFlags: files.FileOpenFlags,
FileError: files.FileError,
FoldingRangeList: extHostTypes.FoldingRangeList,
FoldingRange: extHostTypes.FoldingRange,
FoldingRangeType: extHostTypes.FoldingRangeType
Expand Down
6 changes: 3 additions & 3 deletions src/vs/workbench/api/node/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from 'vs/platform/files/common/files';
import { IStat, FileChangeType, FileOpenFlags } 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';
Expand Down Expand Up @@ -567,8 +567,8 @@ export interface ExtHostWorkspaceShape {
export interface ExtHostFileSystemShape {
$stat(handle: number, resource: UriComponents): TPromise<IStat>;

$readFile(handle: number, resource: UriComponents): TPromise<string>;
$writeFile(handle: number, resource: UriComponents, base64Encoded: string): TPromise<void>;
$readFile(handle: number, resource: UriComponents, flags: FileOpenFlags): TPromise<string>;
$writeFile(handle: number, resource: UriComponents, base64Encoded: string, flags: FileOpenFlags): TPromise<void>;

$move(handle: number, resource: UriComponents, target: UriComponents): TPromise<IStat>;
$mkdir(handle: number, resource: UriComponents): TPromise<IStat>;
Expand Down
16 changes: 8 additions & 8 deletions src/vs/workbench/api/node/extHostFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ class FsLinkProvider implements vscode.DocumentLinkProvider {
}
}


class FileSystemProviderShim implements vscode.FileSystemProvider2 {

_version: 4;
_version: 5;

onDidChange: vscode.Event<vscode.FileChange2[]>;

Expand Down Expand Up @@ -154,7 +153,8 @@ class FileSystemProviderShim implements vscode.FileSystemProvider2 {
return Buffer.concat(chunks);
});
}
writeFile(resource: vscode.Uri, content: Uint8Array): Thenable<void> {
writeFile(resource: vscode.Uri, content: Uint8Array, options: { flags: vscode.FileOpenFlags }): Thenable<void> {
// if (options.flags & (files.FileOpenFlags.Exclusive) )
return this._delegate.write(resource, content);
}
}
Expand All @@ -174,7 +174,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
}

registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, newProvider: vscode.FileSystemProvider2) {
if (newProvider && newProvider._version === 4) {
if (newProvider && newProvider._version === 5) {
return this._doRegisterFileSystemProvider(scheme, newProvider);
} else if (provider) {
return this._doRegisterFileSystemProvider(scheme, new FileSystemProviderShim(provider));
Expand Down Expand Up @@ -240,15 +240,15 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
$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): TPromise<string> {
$readFile(handle: number, resource: UriComponents, flags: files.FileOpenFlags): TPromise<string> {
return asWinJsPromise(token => {
return this._fsProvider.get(handle).readFile(URI.revive(resource), token);
return this._fsProvider.get(handle).readFile(URI.revive(resource), { flags }, token);
}).then(data => {
return Buffer.isBuffer(data) ? data.toString('base64') : Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64');
});
}
$writeFile(handle: number, resource: UriComponents, base64Content: string): TPromise<void, any> {
return asWinJsPromise(token => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), token));
$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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

import URI from 'vs/base/common/uri';
import { FileService } from 'vs/workbench/services/files/electron-browser/fileService';
import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType2, FileChangesEvent, ICreateFileOptions, FileOperationError, FileOperationResult, ITextSnapshot, StringSnapshot } from 'vs/platform/files/common/files';
import { IContent, IStreamContent, IFileStat, IResolveContentOptions, IUpdateContentOptions, IResolveFileOptions, IResolveFileResult, FileOperationEvent, FileOperation, IFileSystemProvider, IStat, FileType2, FileChangesEvent, ICreateFileOptions, FileOperationError, FileOperationResult, ITextSnapshot, StringSnapshot, FileOpenFlags } from 'vs/platform/files/common/files';
import { TPromise } from 'vs/base/common/winjs.base';
import { posix } from 'path';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isFalsyOrEmpty, distinct } from 'vs/base/common/arrays';
import { isFalsyOrEmpty, distinct, flatten } from 'vs/base/common/arrays';
import { Schemas } from 'vs/base/common/network';
import { toDecodeStream, IDecodeStreamOptions, decodeStream } from 'vs/base/node/encoding';
import { TernarySearchTree } from 'vs/base/common/map';
Expand Down Expand Up @@ -186,9 +186,7 @@ export class RemoteFileService extends FileService {
promises.push(this._doResolveFiles(group));
}
}
return TPromise.join(promises).then(data => {
return [].concat(...data);
});
return TPromise.join(promises).then(data => flatten(data));
}

private _doResolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): TPromise<IResolveFileResult[], any> {
Expand Down Expand Up @@ -254,7 +252,7 @@ export class RemoteFileService extends FileService {
}
};

const readable = createReadableOfProvider(provider, resource, options.position || 0);
const readable = createReadableOfProvider(provider, resource, options.position || 0, FileOpenFlags.Read);

return toDecodeStream(readable, decodeStreamOpts).then(data => {

Expand Down Expand Up @@ -286,20 +284,22 @@ export class RemoteFileService extends FileService {
return super.createFile(resource, content, options);
} else {
return this._withProvider(resource).then(provider => {
let prepare = options && !options.overwrite
? this.existsFile(resource)
: TPromise.as(false);

let flags = FileOpenFlags.Write | FileOpenFlags.Create;
if (options && options.overwrite === false) {
flags += FileOpenFlags.Exclusive;
}

return prepare.then(exists => {
if (exists && options && !options.overwrite) {
return TPromise.wrapError(new FileOperationError('EEXIST', FileOperationResult.FILE_MODIFIED_SINCE, options));
}
return this._writeFile(provider, resource, content || '', {});
}).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
return fileStat;
});
const encoding = this.encoding.getWriteEncoding(resource);
return this._writeFile(provider, resource, new StringSnapshot(content), { encoding }, flags);

}).then(fileStat => {
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
return fileStat;
}, err => {

// return TPromise.wrapError(new FileOperationError('EEXIST', FileOperationResult.FILE_MODIFIED_SINCE, options));
throw err;
});
}
}
Expand All @@ -309,21 +309,17 @@ export class RemoteFileService extends FileService {
return super.updateContent(resource, value, options);
} else {
return this._withProvider(resource).then(provider => {
return this._writeFile(provider, resource, value, options || {});
const snapshot = typeof value === 'string' ? new StringSnapshot(value) : value;
return this._writeFile(provider, resource, snapshot, options || {}, FileOpenFlags.Write);
});
}
}

private _writeFile(provider: IFileSystemProvider, resource: URI, content: string | ITextSnapshot, options: IUpdateContentOptions): TPromise<IFileStat> {

const snapshot = typeof content === 'string' ? new StringSnapshot(content) : content;
private _writeFile(provider: IFileSystemProvider, resource: URI, snapshot: ITextSnapshot, options: IUpdateContentOptions, fags: FileOpenFlags): TPromise<IFileStat> {
const readable = createReadableOfSnapshot(snapshot);

const encoding = this.encoding.getWriteEncoding(resource, options.encoding);
const decoder = decodeStream(encoding);

const target = createWritableOfProvider(provider, resource);

const target = createWritableOfProvider(provider, resource, FileOpenFlags.Write);
return new TPromise<IFileStat>((resolve, reject) => {
readable.pipe(decoder).pipe(target);
target.once('error', err => reject(err));
Expand Down Expand Up @@ -438,7 +434,12 @@ export class RemoteFileService extends FileService {
// 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, content.value, { encoding: content.encoding }).then(fileStat => {
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;
});
Expand Down
Loading

0 comments on commit e7e8812

Please sign in to comment.