mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 10:58:31 -05:00
242 lines
9.1 KiB
TypeScript
242 lines
9.1 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
|
import { Schemas } from 'vs/base/common/network';
|
|
import * as resources from 'vs/base/common/resources';
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { IDecodeStreamOptions, toDecodeStream, encodeStream } from 'vs/base/node/encoding';
|
|
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
|
import { localize } from 'vs/nls';
|
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
|
import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileWriteOptions, FileSystemProviderCapabilities, IContent, ICreateFileOptions, IFileSystemProvider, IResolveContentOptions, IStreamContent, ITextSnapshot, IWriteTextFileOptions, ILegacyFileService, IFileService, toFileOperationResult, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
|
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
|
|
import { createReadableOfProvider, createReadableOfSnapshot, createWritableOfProvider } from 'vs/workbench/services/files/node/streams';
|
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|
|
|
class StringSnapshot implements ITextSnapshot {
|
|
private _value: string | null;
|
|
constructor(value: string) {
|
|
this._value = value;
|
|
}
|
|
read(): string | null {
|
|
let ret = this._value;
|
|
this._value = null;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
export class LegacyRemoteFileService extends LegacyFileService {
|
|
|
|
private readonly _provider: Map<string, IFileSystemProvider>;
|
|
|
|
constructor(
|
|
@IFileService fileService: IFileService,
|
|
@IEnvironmentService environmentService: IEnvironmentService,
|
|
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
|
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
|
|
) {
|
|
super(
|
|
fileService,
|
|
contextService,
|
|
environmentService,
|
|
textResourceConfigurationService
|
|
);
|
|
|
|
this._provider = new Map<string, IFileSystemProvider>();
|
|
}
|
|
|
|
registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable {
|
|
if (this._provider.has(scheme)) {
|
|
throw new Error('a provider for that scheme is already registered');
|
|
}
|
|
|
|
this._provider.set(scheme, provider);
|
|
|
|
return {
|
|
dispose: () => {
|
|
this._provider.delete(scheme);
|
|
}
|
|
};
|
|
}
|
|
|
|
// --- stat
|
|
|
|
private _withProvider(resource: URI): Promise<IFileSystemProvider> {
|
|
if (!resources.isAbsolutePath(resource)) {
|
|
throw new FileOperationError(
|
|
localize('invalidPath', "The path of resource '{0}' must be absolute", resource.toString(true)),
|
|
FileOperationResult.FILE_INVALID_PATH
|
|
);
|
|
}
|
|
|
|
return Promise.all([
|
|
this.fileService.activateProvider(resource.scheme)
|
|
]).then(() => {
|
|
const provider = this._provider.get(resource.scheme);
|
|
if (!provider) {
|
|
const err = new Error();
|
|
err.name = 'ENOPRO';
|
|
err.message = `no provider for ${resource.toString()}`;
|
|
throw err;
|
|
}
|
|
return provider;
|
|
});
|
|
}
|
|
|
|
// --- resolve
|
|
|
|
resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent> {
|
|
if (resource.scheme === Schemas.file) {
|
|
return super.resolveContent(resource, options);
|
|
} else {
|
|
return this._readFile(resource, options).then(LegacyRemoteFileService._asContent);
|
|
}
|
|
}
|
|
|
|
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent> {
|
|
if (resource.scheme === Schemas.file) {
|
|
return super.resolveStreamContent(resource, options);
|
|
} else {
|
|
return this._readFile(resource, options);
|
|
}
|
|
}
|
|
|
|
private _readFile(resource: URI, options: IResolveContentOptions = Object.create(null)): Promise<IStreamContent> {
|
|
return this._withProvider(resource).then(provider => {
|
|
|
|
return this.fileService.resolve(resource).then(fileStat => {
|
|
|
|
if (fileStat.isDirectory) {
|
|
// todo@joh cannot copy a folder
|
|
// https://github.com/Microsoft/vscode/issues/41547
|
|
throw new FileOperationError(
|
|
localize('fileIsDirectoryError', "File is directory"),
|
|
FileOperationResult.FILE_IS_DIRECTORY,
|
|
options
|
|
);
|
|
}
|
|
if (typeof options.etag === 'string' && fileStat.etag === options.etag) {
|
|
throw new FileOperationError(
|
|
localize('fileNotModifiedError', "File not modified since"),
|
|
FileOperationResult.FILE_NOT_MODIFIED_SINCE,
|
|
options
|
|
);
|
|
}
|
|
|
|
const decodeStreamOpts: IDecodeStreamOptions = {
|
|
guessEncoding: options.autoGuessEncoding,
|
|
overwriteEncoding: detected => {
|
|
return this.encoding.getReadEncoding(resource, options, { encoding: detected, seemsBinary: false });
|
|
}
|
|
};
|
|
|
|
const readable = createReadableOfProvider(provider, resource, options.position || 0);
|
|
|
|
return toDecodeStream(readable, decodeStreamOpts).then(data => {
|
|
|
|
if (options.acceptTextOnly && data.detected.seemsBinary) {
|
|
return Promise.reject<any>(new FileOperationError(
|
|
localize('fileBinaryError', "File seems to be binary and cannot be opened as text"),
|
|
FileOperationResult.FILE_IS_BINARY,
|
|
options
|
|
));
|
|
}
|
|
|
|
return <IStreamContent>{
|
|
encoding: data.detected.encoding,
|
|
value: data.stream,
|
|
resource: fileStat.resource,
|
|
name: fileStat.name,
|
|
etag: fileStat.etag,
|
|
mtime: fileStat.mtime,
|
|
isReadonly: fileStat.isReadonly,
|
|
size: fileStat.size
|
|
};
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// --- saving
|
|
|
|
private static _throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider {
|
|
if (provider.capabilities & FileSystemProviderCapabilities.Readonly) {
|
|
throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED);
|
|
}
|
|
return provider;
|
|
}
|
|
|
|
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
|
|
if (resource.scheme === Schemas.file) {
|
|
return super.createFile(resource, content, options);
|
|
} else {
|
|
|
|
return this._withProvider(resource).then(LegacyRemoteFileService._throwIfFileSystemIsReadonly).then(provider => {
|
|
|
|
return this.fileService.createFolder(resources.dirname(resource)).then(() => {
|
|
const { encoding } = this.encoding.getWriteEncoding(resource);
|
|
return this._writeFile(provider, resource, new StringSnapshot(content || ''), encoding, { create: true, overwrite: Boolean(options && options.overwrite) });
|
|
});
|
|
|
|
}).then(fileStat => {
|
|
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
|
|
return fileStat;
|
|
}, err => {
|
|
const message = localize('err.create', "Failed to create file {0}", resource.toString(false));
|
|
const result = toFileOperationResult(err);
|
|
throw new FileOperationError(message, result, options);
|
|
});
|
|
}
|
|
}
|
|
|
|
updateContent(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
|
|
if (resource.scheme === Schemas.file) {
|
|
return super.updateContent(resource, value, options);
|
|
} else {
|
|
return this._withProvider(resource).then(LegacyRemoteFileService._throwIfFileSystemIsReadonly).then(provider => {
|
|
return this.fileService.createFolder(resources.dirname(resource)).then(() => {
|
|
const snapshot = typeof value === 'string' ? new StringSnapshot(value) : value;
|
|
return this._writeFile(provider, resource, snapshot, options && options.encoding, { create: true, overwrite: true });
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
private _writeFile(provider: IFileSystemProvider, resource: URI, snapshot: ITextSnapshot, preferredEncoding: string | undefined = undefined, options: FileWriteOptions): Promise<IFileStatWithMetadata> {
|
|
const readable = createReadableOfSnapshot(snapshot);
|
|
const { encoding, hasBOM } = this.encoding.getWriteEncoding(resource, preferredEncoding);
|
|
const encoder = encodeStream(encoding, { addBOM: hasBOM });
|
|
const target = createWritableOfProvider(provider, resource, options);
|
|
return new Promise((resolve, reject) => {
|
|
readable.pipe(encoder).pipe(target);
|
|
target.once('error', err => reject(err));
|
|
target.once('finish', (_: unknown) => resolve(undefined));
|
|
}).then(_ => {
|
|
return this.fileService.resolve(resource, { resolveMetadata: true }) as Promise<IFileStatWithMetadata>;
|
|
});
|
|
}
|
|
|
|
private static _asContent(content: IStreamContent): Promise<IContent> {
|
|
return new Promise<IContent>((resolve, reject) => {
|
|
let result: IContent = {
|
|
value: '',
|
|
encoding: content.encoding,
|
|
etag: content.etag,
|
|
size: content.size,
|
|
mtime: content.mtime,
|
|
name: content.name,
|
|
resource: content.resource,
|
|
isReadonly: content.isReadonly
|
|
};
|
|
content.value.on('data', chunk => result.value += chunk);
|
|
content.value.on('error', reject);
|
|
content.value.on('end', () => resolve(result));
|
|
});
|
|
}
|
|
}
|
|
|
|
registerSingleton(ILegacyFileService, LegacyRemoteFileService); |