fileService.ts 11.5 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import nls = require('vs/nls');
J
Johannes Rieken 已提交
8 9
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
E
Erich Gamma 已提交
10
import paths = require('vs/base/common/paths');
B
wip  
Benjamin Pasero 已提交
11
import encoding = require('vs/base/node/encoding');
E
Erich Gamma 已提交
12 13
import errors = require('vs/base/common/errors');
import uri from 'vs/base/common/uri';
14
import { FileOperation, FileOperationEvent, IFileService, IFilesConfiguration, IResolveFileOptions, IFileStat, IResolveFileResult, IContent, IStreamContent, IImportResult, IResolveContentOptions, IUpdateContentOptions, FileChangesEvent, ICreateFileOptions, ITextSnapshot } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
15
import { FileService as NodeFileService, IFileServiceOptions, IEncodingOverride } from 'vs/workbench/services/files/node/fileService';
16
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
J
Johannes Rieken 已提交
17 18 19 20
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
21
import Event, { Emitter } from 'vs/base/common/event';
J
Johannes Rieken 已提交
22
import { shell } from 'electron';
23
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
24
import { isMacintosh, isWindows } from 'vs/base/common/platform';
25
import product from 'vs/platform/node/product';
B
Benjamin Pasero 已提交
26
import { Schemas } from 'vs/base/common/network';
27
import { Severity, INotificationService, PromptOption } from 'vs/platform/notification/common/notification';
E
Erich Gamma 已提交
28

29
export class FileService implements IFileService {
E
Erich Gamma 已提交
30

31
	public _serviceBrand: any;
32

33
	// If we run with .NET framework < 4.5, we need to detect this error to inform the user
34 35
	private static readonly NET_VERSION_ERROR = 'System.MissingMethodException';
	private static readonly NET_VERSION_ERROR_IGNORE_KEY = 'ignoreNetVersionError';
36

B
Benjamin Pasero 已提交
37 38 39
	private static readonly ENOSPC_ERROR = 'ENOSPC';
	private static readonly ENOSPC_ERROR_IGNORE_KEY = 'ignoreEnospcError';

40
	private raw: NodeFileService;
E
Erich Gamma 已提交
41

42
	private toUnbind: IDisposable[];
E
Erich Gamma 已提交
43

44
	protected _onFileChanges: Emitter<FileChangesEvent>;
45
	protected _onAfterOperation: Emitter<FileOperationEvent>;
46

E
Erich Gamma 已提交
47
	constructor(
48 49
		@IConfigurationService private configurationService: IConfigurationService,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
B
Benjamin Pasero 已提交
50
		@IEnvironmentService private environmentService: IEnvironmentService,
51
		@ILifecycleService private lifecycleService: ILifecycleService,
52
		@INotificationService private notificationService: INotificationService,
53 54
		@IStorageService private storageService: IStorageService,
		@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService
E
Erich Gamma 已提交
55
	) {
56 57
		this.toUnbind = [];

58 59 60 61 62 63
		this._onFileChanges = new Emitter<FileChangesEvent>();
		this.toUnbind.push(this._onFileChanges);

		this._onAfterOperation = new Emitter<FileOperationEvent>();
		this.toUnbind.push(this._onAfterOperation);

64
		const configuration = this.configurationService.getValue<IFilesConfiguration>();
E
Erich Gamma 已提交
65

66 67 68 69 70 71
		let watcherIgnoredPatterns: string[] = [];
		if (configuration.files && configuration.files.watcherExclude) {
			watcherIgnoredPatterns = Object.keys(configuration.files.watcherExclude).filter(k => !!configuration.files.watcherExclude[k]);
		}

		// build config
B
Benjamin Pasero 已提交
72
		const fileServiceConfig: IFileServiceOptions = {
73
			errorLogger: (msg: string) => this.onFileServiceError(msg),
B
Benjamin Pasero 已提交
74
			encodingOverride: this.getEncodingOverrides(),
B
wip  
Benjamin Pasero 已提交
75
			watcherIgnoredPatterns,
76
			verboseLogging: environmentService.verbose,
77 78 79 80 81 82
			useExperimentalFileWatcher: configuration.files.useExperimentalFileWatcher,
			elevationSupport: {
				cliPath: this.environmentService.cliPath,
				promptTitle: this.environmentService.appNameLong,
				promptIcnsPath: (isMacintosh && this.environmentService.isBuilt) ? paths.join(paths.dirname(this.environmentService.appRoot), `${product.nameShort}.icns`) : void 0
			}
83 84 85
		};

		// create service
86
		this.raw = new NodeFileService(contextService, environmentService, textResourceConfigurationService, configurationService, lifecycleService, fileServiceConfig);
E
Erich Gamma 已提交
87 88

		// Listeners
89
		this.registerListeners();
E
Erich Gamma 已提交
90 91
	}

92 93 94 95 96 97 98 99
	public get onFileChanges(): Event<FileChangesEvent> {
		return this._onFileChanges.event;
	}

	public get onAfterOperation(): Event<FileOperationEvent> {
		return this._onAfterOperation.event;
	}

B
Benjamin Pasero 已提交
100 101 102 103 104
	private onFileServiceError(error: string | Error): void {
		const msg = error ? error.toString() : void 0;
		if (!msg) {
			return;
		}
105 106

		// Forward to unexpected error handler
107 108
		errors.onUnexpectedError(msg);

B
Benjamin Pasero 已提交
109
		// Detect if we run < .NET Framework 4.5 (TODO@ben remove with new watcher impl)
B
Benjamin Pasero 已提交
110
		if (msg.indexOf(FileService.NET_VERSION_ERROR) >= 0 && !this.storageService.getBoolean(FileService.NET_VERSION_ERROR_IGNORE_KEY, StorageScope.WORKSPACE)) {
111 112
			const choices: PromptOption[] = [nls.localize('installNet', "Download .NET Framework 4.5"), { label: nls.localize('neverShowAgain', "Don't Show Again") }];
			this.notificationService.prompt(Severity.Warning, nls.localize('netVersionError', "The Microsoft .NET Framework 4.5 is required. Please follow the link to install it."), choices).then(choice => {
113 114
				switch (choice) {
					case 0 /* Read More */:
B
Benjamin Pasero 已提交
115
						window.open('https://go.microsoft.com/fwlink/?LinkId=786533');
116 117
						break;
					case 1 /* Never show again */:
118
						this.storageService.store(FileService.NET_VERSION_ERROR_IGNORE_KEY, true, StorageScope.WORKSPACE);
119 120
						break;
				}
121 122
			});
		}
B
Benjamin Pasero 已提交
123

124
		// Detect if we run into ENOSPC issues
B
Benjamin Pasero 已提交
125
		if (msg.indexOf(FileService.ENOSPC_ERROR) >= 0 && !this.storageService.getBoolean(FileService.ENOSPC_ERROR_IGNORE_KEY, StorageScope.WORKSPACE)) {
126
			const choices: PromptOption[] = [nls.localize('learnMore', "Instructions"), { label: nls.localize('neverShowAgain', "Don't Show Again") }];
127
			this.notificationService.prompt(Severity.Warning, nls.localize('enospcError', "{0} is unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue.", product.nameLong), choices).then(choice => {
128 129
				switch (choice) {
					case 0 /* Read More */:
B
Benjamin Pasero 已提交
130
						window.open('https://go.microsoft.com/fwlink/?linkid=867693');
131 132
						break;
					case 1 /* Never show again */:
B
Benjamin Pasero 已提交
133
						this.storageService.store(FileService.ENOSPC_ERROR_IGNORE_KEY, true, StorageScope.WORKSPACE);
134 135
						break;
				}
B
Benjamin Pasero 已提交
136 137
			});
		}
138 139
	}

E
Erich Gamma 已提交
140 141
	private registerListeners(): void {

142 143 144 145
		// File events
		this.toUnbind.push(this.raw.onFileChanges(e => this._onFileChanges.fire(e)));
		this.toUnbind.push(this.raw.onAfterOperation(e => this._onAfterOperation.fire(e)));

146
		// Config changes
147
		this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e)));
148

B
Benjamin Pasero 已提交
149
		// Root changes
S
Sandeep Somavarapu 已提交
150
		this.toUnbind.push(this.contextService.onDidChangeWorkspaceFolders(() => this.onDidChangeWorkspaceFolders()));
B
Benjamin Pasero 已提交
151

152 153
		// Lifecycle
		this.lifecycleService.onShutdown(this.dispose, this);
154 155
	}

S
Sandeep Somavarapu 已提交
156
	private onDidChangeWorkspaceFolders(): void {
B
Benjamin Pasero 已提交
157 158 159 160 161 162
		this.updateOptions({ encodingOverride: this.getEncodingOverrides() });
	}

	private getEncodingOverrides(): IEncodingOverride[] {
		const encodingOverride: IEncodingOverride[] = [];
		encodingOverride.push({ resource: uri.file(this.environmentService.appSettingsHome), encoding: encoding.UTF8 });
S
Sandeep Somavarapu 已提交
163
		this.contextService.getWorkspace().folders.forEach(folder => {
164
			encodingOverride.push({ resource: uri.file(paths.join(folder.uri.fsPath, '.vscode')), encoding: encoding.UTF8 });
165
		});
B
Benjamin Pasero 已提交
166 167 168 169

		return encodingOverride;
	}

170
	private onConfigurationChange(event: IConfigurationChangeEvent): void {
171 172
		if (event.affectsConfiguration('files.useExperimentalFileWatcher')) {
			this.updateOptions({ useExperimentalFileWatcher: this.configurationService.getValue<boolean>('files.useExperimentalFileWatcher') });
173
		}
E
Erich Gamma 已提交
174 175
	}

B
Benjamin Pasero 已提交
176
	public updateOptions(options: object): void {
177
		this.raw.updateOptions(options);
E
Erich Gamma 已提交
178 179
	}

180 181
	public resolveFile(resource: uri, options?: IResolveFileOptions): TPromise<IFileStat> {
		return this.raw.resolveFile(resource, options);
E
Erich Gamma 已提交
182 183
	}

I
isidor 已提交
184
	public resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): TPromise<IResolveFileResult[]> {
I
isidor 已提交
185 186 187
		return this.raw.resolveFiles(toResolve);
	}

188 189 190 191
	public existsFile(resource: uri): TPromise<boolean> {
		return this.raw.existsFile(resource);
	}

192
	public resolveContent(resource: uri, options?: IResolveContentOptions): TPromise<IContent> {
B
Benjamin Pasero 已提交
193
		return this.raw.resolveContent(resource, options);
E
Erich Gamma 已提交
194 195
	}

A
Alex Dima 已提交
196
	public resolveStreamContent(resource: uri, options?: IResolveContentOptions): TPromise<IStreamContent> {
B
Benjamin Pasero 已提交
197
		return this.raw.resolveStreamContent(resource, options);
A
Alex Dima 已提交
198 199
	}

200
	public updateContent(resource: uri, value: string | ITextSnapshot, options?: IUpdateContentOptions): TPromise<IFileStat> {
B
Benjamin Pasero 已提交
201
		return this.raw.updateContent(resource, value, options);
E
Erich Gamma 已提交
202 203
	}

204 205
	public moveFile(source: uri, target: uri, overwrite?: boolean): TPromise<IFileStat> {
		return this.raw.moveFile(source, target, overwrite);
E
Erich Gamma 已提交
206 207
	}

208 209
	public copyFile(source: uri, target: uri, overwrite?: boolean): TPromise<IFileStat> {
		return this.raw.copyFile(source, target, overwrite);
E
Erich Gamma 已提交
210 211
	}

212 213
	public createFile(resource: uri, content?: string, options?: ICreateFileOptions): TPromise<IFileStat> {
		return this.raw.createFile(resource, content, options);
E
Erich Gamma 已提交
214 215
	}

216 217
	public createFolder(resource: uri): TPromise<IFileStat> {
		return this.raw.createFolder(resource);
E
Erich Gamma 已提交
218 219
	}

220 221 222 223
	public touchFile(resource: uri): TPromise<IFileStat> {
		return this.raw.touchFile(resource);
	}

224 225
	public rename(resource: uri, newName: string): TPromise<IFileStat> {
		return this.raw.rename(resource, newName);
E
Erich Gamma 已提交
226 227 228 229 230 231 232
	}

	public del(resource: uri, useTrash?: boolean): TPromise<void> {
		if (useTrash) {
			return this.doMoveItemToTrash(resource);
		}

233
		return this.raw.del(resource);
E
Erich Gamma 已提交
234 235
	}

236
	private doMoveItemToTrash(resource: uri): TPromise<void> {
B
Benjamin Pasero 已提交
237 238
		const absolutePath = resource.fsPath;
		const result = shell.moveItemToTrash(absolutePath);
E
Erich Gamma 已提交
239
		if (!result) {
240
			return TPromise.wrapError<void>(new Error(isWindows ? nls.localize('binFailed', "Failed to move '{0}' to the recycle bin", paths.basename(absolutePath)) : nls.localize('trashFailed', "Failed to move '{0}' to the trash", paths.basename(absolutePath))));
E
Erich Gamma 已提交
241 242
		}

243 244
		this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));

A
Alex Dima 已提交
245
		return TPromise.as(null);
E
Erich Gamma 已提交
246 247
	}

248 249 250 251 252 253
	public importFile(source: uri, targetFolder: uri): TPromise<IImportResult> {
		return this.raw.importFile(source, targetFolder).then((result) => {
			return <IImportResult>{
				isNew: result && result.isNew,
				stat: result && result.stat
			};
E
Erich Gamma 已提交
254 255 256 257 258 259 260 261
		});
	}

	public watchFileChanges(resource: uri): void {
		if (!resource) {
			return;
		}

B
Benjamin Pasero 已提交
262
		if (resource.scheme !== Schemas.file) {
E
Erich Gamma 已提交
263 264 265 266 267 268 269 270
			return; // only support files
		}

		// return early if the resource is inside the workspace for which we have another watcher in place
		if (this.contextService.isInsideWorkspace(resource)) {
			return;
		}

271
		this.raw.watchFileChanges(resource);
E
Erich Gamma 已提交
272 273
	}

J
Johannes Rieken 已提交
274 275
	public unwatchFileChanges(resource: uri): void {
		this.raw.unwatchFileChanges(resource);
E
Erich Gamma 已提交
276 277
	}

278 279
	public getEncoding(resource: uri, preferredEncoding?: string): string {
		return this.raw.getEncoding(resource, preferredEncoding);
280 281
	}

E
Erich Gamma 已提交
282
	public dispose(): void {
283
		this.toUnbind = dispose(this.toUnbind);
E
Erich Gamma 已提交
284 285

		// Dispose service
286
		this.raw.dispose();
E
Erich Gamma 已提交
287
	}
288
}