remoteFileService.ts 8.9 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { IDisposable } from 'vs/base/common/lifecycle';
7
import { Schemas } from 'vs/base/common/network';
8
import * as resources from 'vs/base/common/resources';
9
import { URI } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
10
import { IDecodeStreamOptions, toDecodeStream, encodeStream } from 'vs/base/node/encoding';
11 12
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { localize } from 'vs/nls';
13
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
14
import { FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileWriteOptions, FileSystemProviderCapabilities, IContent, ICreateFileOptions, IFileSystemProvider, IResolveContentOptions, IStreamContent, ITextSnapshot, IUpdateContentOptions, StringSnapshot, ILegacyFileService, IFileService, toFileOperationResult, IFileStatWithMetadata } from 'vs/platform/files/common/files';
15
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
16
import { LegacyFileService } from 'vs/workbench/services/files/node/fileService';
B
Benjamin Pasero 已提交
17
import { createReadableOfProvider, createReadableOfSnapshot, createWritableOfProvider } from 'vs/workbench/services/files/node/streams';
18
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
J
Johannes Rieken 已提交
19

20
export class LegacyRemoteFileService extends LegacyFileService {
21

22
	private readonly _provider: Map<string, IFileSystemProvider>;
23

24
	constructor(
25
		@IFileService fileService: IFileService,
26
		@IEnvironmentService environmentService: IEnvironmentService,
27 28 29 30
		@IWorkspaceContextService contextService: IWorkspaceContextService,
		@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
	) {
		super(
31
			fileService,
32
			contextService,
33
			environmentService,
34
			textResourceConfigurationService
35
		);
36

37
		this._provider = new Map<string, IFileSystemProvider>();
38 39
	}

40 41 42
	registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable {
		if (this._provider.has(scheme)) {
			throw new Error('a provider for that scheme is already registered');
J
Johannes Rieken 已提交
43
		}
44

45
		this._provider.set(scheme, provider);
46

47 48
		return {
			dispose: () => {
49
				this._provider.delete(scheme);
50 51 52 53
			}
		};
	}

54 55
	// --- stat

J
Johannes Rieken 已提交
56
	private _withProvider(resource: URI): Promise<IFileSystemProvider> {
57
		if (!resources.isAbsolutePath(resource)) {
58 59 60 61 62 63
			throw new FileOperationError(
				localize('invalidPath', "The path of resource '{0}' must be absolute", resource.toString(true)),
				FileOperationResult.FILE_INVALID_PATH
			);
		}

J
Johannes Rieken 已提交
64
		return Promise.all([
65
			this.fileService.activateProvider(resource.scheme)
66
		]).then(() => {
67 68
			const provider = this._provider.get(resource.scheme);
			if (!provider) {
69 70
				const err = new Error();
				err.name = 'ENOPRO';
71
				err.message = `no provider for ${resource.toString()}`;
72
				throw err;
73 74 75 76 77
			}
			return provider;
		});
	}

78 79
	// --- resolve

J
Johannes Rieken 已提交
80
	resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent> {
81
		if (resource.scheme === Schemas.file) {
82
			return super.resolveContent(resource, options);
83
		} else {
84
			return this._readFile(resource, options).then(LegacyRemoteFileService._asContent);
85 86 87
		}
	}

J
Johannes Rieken 已提交
88
	resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent> {
89
		if (resource.scheme === Schemas.file) {
90
			return super.resolveStreamContent(resource, options);
91
		} else {
92
			return this._readFile(resource, options);
93 94 95
		}
	}

J
Johannes Rieken 已提交
96
	private _readFile(resource: URI, options: IResolveContentOptions = Object.create(null)): Promise<IStreamContent> {
97
		return this._withProvider(resource).then(provider => {
98

99
			return this.fileService.resolve(resource).then(fileStat => {
100

J
Johannes Rieken 已提交
101 102 103 104 105 106 107 108 109
				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
					);
				}
B
Benjamin Pasero 已提交
110
				if (typeof options.etag === 'string' && fileStat.etag === options.etag) {
111 112 113 114 115 116 117
					throw new FileOperationError(
						localize('fileNotModifiedError', "File not modified since"),
						FileOperationResult.FILE_NOT_MODIFIED_SINCE,
						options
					);
				}

118
				const decodeStreamOpts: IDecodeStreamOptions = {
119 120
					guessEncoding: options.autoGuessEncoding,
					overwriteEncoding: detected => {
121
						return this.encoding.getReadEncoding(resource, options, { encoding: detected, seemsBinary: false });
122
					}
123
				};
124

125
				const readable = createReadableOfProvider(provider, resource, options.position || 0);
126 127

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

129
					if (options.acceptTextOnly && data.detected.seemsBinary) {
J
Johannes Rieken 已提交
130
						return Promise.reject<any>(new FileOperationError(
131
							localize('fileBinaryError', "File seems to be binary and cannot be opened as text"),
132 133
							FileOperationResult.FILE_IS_BINARY,
							options
134
						));
135
					}
136

137 138 139
					return <IStreamContent>{
						encoding: data.detected.encoding,
						value: data.stream,
140 141 142 143
						resource: fileStat.resource,
						name: fileStat.name,
						etag: fileStat.etag,
						mtime: fileStat.mtime,
144 145
						isReadonly: fileStat.isReadonly,
						size: fileStat.size
146 147
					};
				});
148 149
			});
		});
150 151 152 153
	}

	// --- saving

154 155 156 157 158 159 160
	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;
	}

161
	createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
162
		if (resource.scheme === Schemas.file) {
J
Johannes Rieken 已提交
163
			return super.createFile(resource, content, options);
164
		} else {
165

166
			return this._withProvider(resource).then(LegacyRemoteFileService._throwIfFileSystemIsReadonly).then(provider => {
167

168
				return this.fileService.createFolder(resources.dirname(resource)).then(() => {
169
					const { encoding } = this.encoding.getWriteEncoding(resource);
170
					return this._writeFile(provider, resource, new StringSnapshot(content || ''), encoding, { create: true, overwrite: Boolean(options && options.overwrite) });
171
				});
172 173 174 175 176

			}).then(fileStat => {
				this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.CREATE, fileStat));
				return fileStat;
			}, err => {
J
Johannes Rieken 已提交
177
				const message = localize('err.create', "Failed to create file {0}", resource.toString(false));
178 179
				const result = toFileOperationResult(err);
				throw new FileOperationError(message, result, options);
180
			});
181 182 183
		}
	}

184
	updateContent(resource: URI, value: string | ITextSnapshot, options?: IUpdateContentOptions): Promise<IFileStatWithMetadata> {
185
		if (resource.scheme === Schemas.file) {
186
			return super.updateContent(resource, value, options);
187
		} else {
188
			return this._withProvider(resource).then(LegacyRemoteFileService._throwIfFileSystemIsReadonly).then(provider => {
189
				return this.fileService.createFolder(resources.dirname(resource)).then(() => {
190 191 192
					const snapshot = typeof value === 'string' ? new StringSnapshot(value) : value;
					return this._writeFile(provider, resource, snapshot, options && options.encoding, { create: true, overwrite: true });
				});
193
			});
194 195 196
		}
	}

197
	private _writeFile(provider: IFileSystemProvider, resource: URI, snapshot: ITextSnapshot, preferredEncoding: string | undefined = undefined, options: FileWriteOptions): Promise<IFileStatWithMetadata> {
198
		const readable = createReadableOfSnapshot(snapshot);
199 200
		const { encoding, hasBOM } = this.encoding.getWriteEncoding(resource, preferredEncoding);
		const encoder = encodeStream(encoding, { addBOM: hasBOM });
201
		const target = createWritableOfProvider(provider, resource, options);
202
		return new Promise((resolve, reject) => {
J
Johannes Rieken 已提交
203
			readable.pipe(encoder).pipe(target);
204
			target.once('error', err => reject(err));
B
Benjamin Pasero 已提交
205
			target.once('finish', (_: unknown) => resolve(undefined));
206
		}).then(_ => {
207
			return this.fileService.resolve(resource, { resolveMetadata: true }) as Promise<IFileStatWithMetadata>;
208
		});
209 210
	}

211 212
	private static _asContent(content: IStreamContent): Promise<IContent> {
		return new Promise<IContent>((resolve, reject) => {
J
Johannes Rieken 已提交
213 214 215 216
			let result: IContent = {
				value: '',
				encoding: content.encoding,
				etag: content.etag,
217
				size: content.size,
J
Johannes Rieken 已提交
218 219
				mtime: content.mtime,
				name: content.name,
I
isidor 已提交
220 221
				resource: content.resource,
				isReadonly: content.isReadonly
J
Johannes Rieken 已提交
222 223 224 225 226
			};
			content.value.on('data', chunk => result.value += chunk);
			content.value.on('error', reject);
			content.value.on('end', () => resolve(result));
		});
227 228
	}
}
229

230
registerSingleton(ILegacyFileService, LegacyRemoteFileService);