extensionManagementService.ts 47.7 KB
Newer Older
E
Erich Gamma 已提交
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 * as nls from 'vs/nls';
E
Erich Gamma 已提交
7 8
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
9
import * as errors from 'vs/base/common/errors';
E
Erich Gamma 已提交
10
import { assign } from 'vs/base/common/objects';
11
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
12
import { flatten } from 'vs/base/common/arrays';
13
import { extract, ExtractError, zip, IFile } from 'vs/platform/node/zip';
S
Sandeep Somavarapu 已提交
14
import { ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
J
Johannes Rieken 已提交
15 16
import {
	IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
17
	IGalleryExtension, IExtensionManifest, IGalleryMetadata,
J
Joao Moreno 已提交
18
	InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
S
Sandeep Somavarapu 已提交
19
	StatisticType,
J
Joao Moreno 已提交
20
	IExtensionIdentifier,
21
	IReportedExtension,
S
Sandeep Somavarapu 已提交
22 23 24
	InstallOperation,
	INSTALL_ERROR_MALICIOUS,
	INSTALL_ERROR_INCOMPATIBLE
J
Joao Moreno 已提交
25
} from 'vs/platform/extensionManagement/common/extensionManagement';
S
Sandeep Somavarapu 已提交
26
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getIdFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
27
import { localizeManifest } from '../common/extensionNls';
J
Joao Moreno 已提交
28
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
B
Benjamin Pasero 已提交
29
import { Limiter, always, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async';
M
Matt Bierner 已提交
30
import { Event, Emitter } from 'vs/base/common/event';
J
Joao Moreno 已提交
31
import * as semver from 'semver';
32
import { URI } from 'vs/base/common/uri';
S
Sandeep Somavarapu 已提交
33
import pkg from 'vs/platform/node/package';
34
import { isMacintosh, isWindows } from 'vs/base/common/platform';
35
import { ILogService } from 'vs/platform/log/common/log';
36
import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache';
37
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
38
import Severity from 'vs/base/common/severity';
S
Sandeep Somavarapu 已提交
39
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
40
import { toErrorMessage } from 'vs/base/common/errorMessage';
41
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
S
Sandeep Somavarapu 已提交
42
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
43 44 45
import { tmpdir } from 'os';
import { generateUuid } from 'vs/base/common/uuid';
import { IDownloadService } from 'vs/platform/download/common/download';
46
import { optional } from 'vs/platform/instantiation/common/instantiation';
47
import { Schemas } from 'vs/base/common/network';
48
import { CancellationToken } from 'vs/base/common/cancellation';
49
import { getPathFromAmdModule } from 'vs/base/common/amd';
50
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
E
Erich Gamma 已提交
51

52 53
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
54
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
S
Sandeep Somavarapu 已提交
55 56
const INSTALL_ERROR_DOWNLOADING = 'downloading';
const INSTALL_ERROR_VALIDATING = 'validating';
57 58
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
59
const INSTALL_ERROR_EXTRACTING = 'extracting';
S
Sandeep Somavarapu 已提交
60
const INSTALL_ERROR_RENAMING = 'renaming';
61
const INSTALL_ERROR_DELETING = 'deleting';
62
const ERROR_UNKNOWN = 'unknown';
S
Sandeep Somavarapu 已提交
63

64
export class ExtensionManagementError extends Error {
S
Sandeep Somavarapu 已提交
65 66 67 68
	constructor(message: string, readonly code: string) {
		super(message);
	}
}
J
Joao Moreno 已提交
69

S
Sandeep Somavarapu 已提交
70 71
function parseManifest(raw: string): Promise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
	return new Promise((c, e) => {
E
Erich Gamma 已提交
72
		try {
J
Joao Moreno 已提交
73 74 75 76
			const manifest = JSON.parse(raw);
			const metadata = manifest.__metadata || null;
			delete manifest.__metadata;
			c({ manifest, metadata });
E
Erich Gamma 已提交
77 78 79 80 81 82
		} catch (err) {
			e(new Error(nls.localize('invalidManifest', "Extension invalid: package.json is not a JSON file.")));
		}
	});
}

S
Sandeep Somavarapu 已提交
83
function readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
84 85 86 87
	const promises = [
		pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8')
			.then(raw => parseManifest(raw)),
		pfs.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
S
Sandeep Somavarapu 已提交
88
			.then(null, err => err.code !== 'ENOENT' ? Promise.reject<string>(err) : '{}')
89 90 91
			.then(raw => JSON.parse(raw))
	];

S
Sandeep Somavarapu 已提交
92
	return Promise.all<any>(promises).then(([{ manifest, metadata }, translations]) => {
93 94 95 96 97 98 99
		return {
			manifest: localizeManifest(manifest, translations),
			metadata
		};
	});
}

S
Sandeep Somavarapu 已提交
100 101 102
interface InstallableExtension {
	zipPath: string;
	id: string;
S
Sandeep Somavarapu 已提交
103
	metadata?: IGalleryMetadata;
S
Sandeep Somavarapu 已提交
104 105
}

106
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
E
Erich Gamma 已提交
107

108
	_serviceBrand: any;
E
Erich Gamma 已提交
109

110
	private systemExtensionsPath: string;
E
Erich Gamma 已提交
111
	private extensionsPath: string;
112
	private uninstalledPath: string;
J
Joao Moreno 已提交
113
	private uninstalledFileLimiter: Queue<any>;
S
Sandeep Somavarapu 已提交
114
	private reportedExtensions: Promise<IReportedExtension[]> | undefined;
J
Joao Moreno 已提交
115
	private lastReportTimestamp = 0;
116 117
	private readonly installingExtensions: Map<string, CancelablePromise<void>> = new Map<string, CancelablePromise<void>>();
	private readonly uninstallingExtensions: Map<string, CancelablePromise<void>> = new Map<string, CancelablePromise<void>>();
118
	private readonly manifestCache: ExtensionsManifestCache;
S
Sandeep Somavarapu 已提交
119
	private readonly extensionLifecycle: ExtensionsLifecycle;
E
Erich Gamma 已提交
120

S
Sandeep Somavarapu 已提交
121 122
	private readonly _onInstallExtension = new Emitter<InstallExtensionEvent>();
	readonly onInstallExtension: Event<InstallExtensionEvent> = this._onInstallExtension.event;
E
Erich Gamma 已提交
123

S
Sandeep Somavarapu 已提交
124 125
	private readonly _onDidInstallExtension = new Emitter<DidInstallExtensionEvent>();
	readonly onDidInstallExtension: Event<DidInstallExtensionEvent> = this._onDidInstallExtension.event;
E
Erich Gamma 已提交
126

S
Sandeep Somavarapu 已提交
127 128 129
	private readonly _onUninstallExtension = new Emitter<IExtensionIdentifier>();
	readonly onUninstallExtension: Event<IExtensionIdentifier> = this._onUninstallExtension.event;

S
Sandeep Somavarapu 已提交
130 131
	private _onDidUninstallExtension = new Emitter<DidUninstallExtensionEvent>();
	onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
E
Erich Gamma 已提交
132 133

	constructor(
134
		@IEnvironmentService private environmentService: IEnvironmentService,
135
		@IDialogService private dialogService: IDialogService,
136
		@IExtensionGalleryService private galleryService: IExtensionGalleryService,
137
		@ILogService private logService: ILogService,
138
		@optional(IDownloadService) private downloadService: IDownloadService,
139
		@ITelemetryService private telemetryService: ITelemetryService,
E
Erich Gamma 已提交
140
	) {
141
		super();
142
		this.systemExtensionsPath = environmentService.builtinExtensionsPath;
J
Joao Moreno 已提交
143
		this.extensionsPath = environmentService.extensionsPath;
144
		this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
J
Joao Moreno 已提交
145
		this.uninstalledFileLimiter = new Queue();
146
		this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
S
Sandeep Somavarapu 已提交
147
		this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService));
S
Sandeep Somavarapu 已提交
148 149 150 151 152 153 154

		this._register(toDisposable(() => {
			this.installingExtensions.forEach(promise => promise.cancel());
			this.uninstallingExtensions.forEach(promise => promise.cancel());
			this.installingExtensions.clear();
			this.uninstallingExtensions.clear();
		}));
A
Alex Dima 已提交
155 156
	}

S
Sandeep Somavarapu 已提交
157
	zip(extension: ILocalExtension): Promise<URI> {
S
Sandeep Somavarapu 已提交
158
		this.logService.trace('ExtensionManagementService#zip', extension.identifier.id);
S
Sandeep Somavarapu 已提交
159
		return this.collectFiles(extension)
160 161 162 163
			.then(files => zip(path.join(tmpdir(), generateUuid()), files))
			.then(path => URI.file(path));
	}

S
Sandeep Somavarapu 已提交
164
	unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
S
Sandeep Somavarapu 已提交
165
		this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString());
166
		return this.install(zipLocation, type);
167 168
	}

S
Sandeep Somavarapu 已提交
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	private collectFiles(extension: ILocalExtension): Promise<IFile[]> {

		const collectFilesFromDirectory = async (dir): Promise<string[]> => {
			let entries = await pfs.readdir(dir);
			entries = entries.map(e => path.join(dir, e));
			const stats = await Promise.all(entries.map(e => pfs.stat(e)));
			let promise: Promise<string[]> = Promise.resolve([]);
			stats.forEach((stat, index) => {
				const entry = entries[index];
				if (stat.isFile()) {
					promise = promise.then(result => ([...result, entry]));
				}
				if (stat.isDirectory()) {
					promise = promise
						.then(result => collectFilesFromDirectory(entry)
							.then(files => ([...result, ...files])));
185 186
				}
			});
S
Sandeep Somavarapu 已提交
187 188 189 190 191 192
			return promise;
		};

		return collectFilesFromDirectory(extension.location.fsPath)
			.then(files => files.map(f => (<IFile>{ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })));

193 194
	}

S
Sandeep Somavarapu 已提交
195
	install(vsix: URI, type: LocalExtensionType = LocalExtensionType.User): Promise<IExtensionIdentifier> {
S
Sandeep Somavarapu 已提交
196
		this.logService.trace('ExtensionManagementService#install', vsix.toString());
S
Sandeep Somavarapu 已提交
197
		return createCancelablePromise(token => {
198 199 200 201
			return this.downloadVsix(vsix)
				.then(downloadLocation => {
					const zipPath = path.resolve(downloadLocation.fsPath);

202
					return getManifest(zipPath)
203 204 205
						.then(manifest => {
							const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
							if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
R
Rob Lourens 已提交
206
								return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, pkg.version)));
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
							}
							return this.removeIfExists(identifier.id)
								.then(
									() => this.checkOutdated(manifest)
										.then(validated => {
											if (validated) {
												this.logService.info('Installing the extension:', identifier.id);
												this._onInstallExtension.fire({ identifier, zipPath });
												return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name))
													.then(
														metadata => this.installFromZipPath(identifier, zipPath, metadata, type, token),
														error => this.installFromZipPath(identifier, zipPath, null, type, token))
													.then(
														() => { this.logService.info('Successfully installed the extension:', identifier.id); return identifier; },
														e => {
															this.logService.error('Failed to install the extension:', identifier.id, e.message);
S
Sandeep Somavarapu 已提交
223
															return Promise.reject(e);
224 225 226 227
														});
											}
											return null;
										}),
R
Rob Lourens 已提交
228
									e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name))));
229 230
						});
				});
S
Sandeep Somavarapu 已提交
231
		});
232 233
	}

S
Sandeep Somavarapu 已提交
234
	private downloadVsix(vsix: URI): Promise<URI> {
235
		if (vsix.scheme === Schemas.file) {
S
Sandeep Somavarapu 已提交
236
			return Promise.resolve(vsix);
237 238 239 240 241 242 243 244
		}
		if (!this.downloadService) {
			throw new Error('Download service is not available');
		}
		const downloadedLocation = path.join(tmpdir(), generateUuid());
		return this.downloadService.download(vsix, downloadedLocation).then(() => URI.file(downloadedLocation));
	}

S
Sandeep Somavarapu 已提交
245
	private removeIfExists(id: string): Promise<void> {
S
Sandeep Somavarapu 已提交
246 247 248
		return this.getInstalled(LocalExtensionType.User)
			.then(installed => installed.filter(i => i.identifier.id === id)[0])
			.then(existing => existing ? this.removeExtension(existing, 'existing') : null);
S
Sandeep Somavarapu 已提交
249
	}
250

S
Sandeep Somavarapu 已提交
251
	private checkOutdated(manifest: IExtensionManifest): Promise<boolean> {
S
Sandeep Somavarapu 已提交
252
		const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
253
		return this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
254 255 256 257
			.then(installedExtensions => {
				const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
				if (newer) {
					const message = nls.localize('installingOutdatedExtension', "A newer version of this extension is already installed. Would you like to override this with the older version?");
258
					const buttons = [
S
Sandeep Somavarapu 已提交
259 260 261
						nls.localize('override', "Override"),
						nls.localize('cancel', "Cancel")
					];
262
					return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
S
Sandeep Somavarapu 已提交
263 264 265 266
						.then<boolean>(value => {
							if (value === 0) {
								return this.uninstall(newer, true).then(() => true);
							}
S
Sandeep Somavarapu 已提交
267
							return Promise.reject(errors.canceled());
S
Sandeep Somavarapu 已提交
268 269 270
						});
				}
				return true;
271
			});
S
Sandeep Somavarapu 已提交
272 273
	}

S
Sandeep Somavarapu 已提交
274
	private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
275
		return this.toNonCancellablePromise(this.getInstalled()
S
Sandeep Somavarapu 已提交
276 277
			.then(installed => {
				const operation = this.getOperation({ id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }, installed);
278
				return this.installExtension({ zipPath, id: identifier.id, metadata }, type, token)
S
Sandeep Somavarapu 已提交
279
					.then(local => this.installDependenciesAndPackExtensions(local, null).then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
S
Sandeep Somavarapu 已提交
280 281
					.then(
						local => { this._onDidInstallExtension.fire({ identifier, zipPath, local, operation }); return local; },
S
Sandeep Somavarapu 已提交
282
						error => { this._onDidInstallExtension.fire({ identifier, zipPath, operation, error }); return Promise.reject(error); }
S
Sandeep Somavarapu 已提交
283
					);
S
Sandeep Somavarapu 已提交
284
			}));
E
Erich Gamma 已提交
285 286
	}

S
Sandeep Somavarapu 已提交
287
	installFromGallery(extension: IGalleryExtension): Promise<void> {
S
Sandeep Somavarapu 已提交
288
		this.logService.trace('ExtensionManagementService#installFromGallery', extension.identifier.id);
289 290
		let cancellablePromise = this.installingExtensions.get(extension.identifier.id);
		if (!cancellablePromise) {
291

292
			let cancellationToken: CancellationToken, successCallback: ValueCallback<void>, errorCallback: ErrorCallback;
S
Sandeep Somavarapu 已提交
293
			cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); });
294
			this.installingExtensions.set(extension.identifier.id, cancellablePromise);
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

			try {
				const startTime = new Date().getTime();
				const identifier = { id: getLocalExtensionIdFromGallery(extension, extension.version), uuid: extension.identifier.uuid };
				const telemetryData = getGalleryExtensionTelemetryData(extension);
				let operation: InstallOperation = InstallOperation.Install;

				this.logService.info('Installing extension:', extension.name);
				this._onInstallExtension.fire({ identifier, gallery: extension });

				this.checkMalicious(extension)
					.then(() => this.getInstalled(LocalExtensionType.User))
					.then(installed => {
						const existingExtension = installed.filter(i => areSameExtensions(i.galleryIdentifier, extension.identifier))[0];
						operation = existingExtension ? InstallOperation.Update : InstallOperation.Install;
						return this.downloadInstallableExtension(extension, operation)
311
							.then(installableExtension => this.installExtension(installableExtension, LocalExtensionType.User, cancellationToken).then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local)))
312
							.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
S
Sandeep Somavarapu 已提交
313
								.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))));
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
					})
					.then(
						local => {
							this.installingExtensions.delete(extension.identifier.id);
							this.logService.info(`Extensions installed successfully:`, extension.identifier.id);
							this._onDidInstallExtension.fire({ identifier, gallery: extension, local, operation });
							this.reportTelemetry(this.getTelemetryEvent(operation), telemetryData, new Date().getTime() - startTime, void 0);
							successCallback(null);
						},
						error => {
							this.installingExtensions.delete(extension.identifier.id);
							const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
							this.logService.error(`Failed to install extension:`, extension.identifier.id, error ? error.message : errorCode);
							this._onDidInstallExtension.fire({ identifier, gallery: extension, operation, error: errorCode });
							this.reportTelemetry(this.getTelemetryEvent(operation), telemetryData, new Date().getTime() - startTime, error);
S
Sandeep Somavarapu 已提交
329 330 331
							if (error instanceof Error) {
								error.name = errorCode;
							}
332 333 334 335 336 337 338 339 340
							errorCallback(error);
						});

			} catch (error) {
				this.installingExtensions.delete(extension.identifier.id);
				errorCallback(error);
			}

		}
341

S
Sandeep Somavarapu 已提交
342
		return cancellablePromise;
S
Sandeep Somavarapu 已提交
343 344
	}

S
Sandeep Somavarapu 已提交
345
	reinstallFromGallery(extension: ILocalExtension): Promise<void> {
S
Sandeep Somavarapu 已提交
346
		this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
S
Sandeep Somavarapu 已提交
347
		if (!this.galleryService.isEnabled()) {
S
Sandeep Somavarapu 已提交
348
			return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
S
Sandeep Somavarapu 已提交
349 350 351 352
		}
		return this.findGalleryExtension(extension)
			.then(galleryExtension => {
				if (galleryExtension) {
S
Sandeep Somavarapu 已提交
353
					return this.setUninstalled(extension)
354 355
						.then(() => this.removeUninstalledExtension(extension)
							.then(
M
Matt Bierner 已提交
356
								() => this.installFromGallery(galleryExtension),
S
Sandeep Somavarapu 已提交
357
								e => Promise.reject(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))))));
S
Sandeep Somavarapu 已提交
358
				}
S
Sandeep Somavarapu 已提交
359
				return Promise.reject(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
S
Sandeep Somavarapu 已提交
360 361 362
			});
	}

S
Sandeep Somavarapu 已提交
363 364
	private getOperation(extensionToInstall: IExtensionIdentifier, installed: ILocalExtension[]): InstallOperation {
		return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall)) ? InstallOperation.Update : InstallOperation.Install;
S
Sandeep Somavarapu 已提交
365 366
	}

367 368
	private getTelemetryEvent(operation: InstallOperation): string {
		return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
S
Sandeep Somavarapu 已提交
369 370
	}

S
Sandeep Somavarapu 已提交
371
	private checkMalicious(extension: IGalleryExtension): Promise<void> {
372 373 374 375 376 377 378 379
		return this.getExtensionsReport()
			.then(report => {
				if (getMaliciousExtensionsSet(report).has(extension.identifier.id)) {
					throw new ExtensionManagementError(INSTALL_ERROR_MALICIOUS, nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."));
				} else {
					return null;
				}
			});
S
Sandeep Somavarapu 已提交
380 381
	}

S
Sandeep Somavarapu 已提交
382
	private downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<InstallableExtension> {
S
Sandeep Somavarapu 已提交
383
		const metadata = <IGalleryMetadata>{
S
Sandeep Somavarapu 已提交
384
			id: extension.identifier.uuid,
S
Sandeep Somavarapu 已提交
385 386 387
			publisherId: extension.publisherId,
			publisherDisplayName: extension.publisherDisplayName,
		};
S
Sandeep Somavarapu 已提交
388 389 390

		return this.galleryService.loadCompatibleVersion(extension)
			.then(
M
Matt Bierner 已提交
391 392 393
				compatible => {
					if (compatible) {
						this.logService.trace('Started downloading extension:', extension.name);
394
						return this.galleryService.download(compatible, operation)
M
Matt Bierner 已提交
395 396
							.then(
								zipPath => {
S
Sandeep Somavarapu 已提交
397
									this.logService.info('Downloaded extension:', extension.name, zipPath);
398
									return getManifest(zipPath)
M
Matt Bierner 已提交
399 400
										.then(
											manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
S
Sandeep Somavarapu 已提交
401
											error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
M
Matt Bierner 已提交
402 403
										);
								},
S
Sandeep Somavarapu 已提交
404
								error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
M
Matt Bierner 已提交
405
					} else {
S
Sandeep Somavarapu 已提交
406
						return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
M
Matt Bierner 已提交
407 408
					}
				},
S
Sandeep Somavarapu 已提交
409
				error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
S
Sandeep Somavarapu 已提交
410 411
	}

S
Sandeep Somavarapu 已提交
412
	private installExtension(installableExtension: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
413 414
		return this.unsetUninstalledAndGetLocal(installableExtension.id)
			.then(
M
Matt Bierner 已提交
415 416 417 418
				local => {
					if (local) {
						return local;
					}
419
					return this.extractAndInstall(installableExtension, type, token);
M
Matt Bierner 已提交
420 421 422
				},
				e => {
					if (isMacintosh) {
S
Sandeep Somavarapu 已提交
423
						return Promise.reject(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
M
Matt Bierner 已提交
424
					}
S
Sandeep Somavarapu 已提交
425
					return Promise.reject(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
M
Matt Bierner 已提交
426
				});
427
	}
J
Joao Moreno 已提交
428

S
Sandeep Somavarapu 已提交
429
	private unsetUninstalledAndGetLocal(id: string): Promise<ILocalExtension> {
430 431 432
		return this.isUninstalled(id)
			.then(isUninstalled => {
				if (isUninstalled) {
433
					this.logService.trace('Removing the extension from uninstalled list:', id);
434 435
					// If the same version of extension is marked as uninstalled, remove it from there and return the local.
					return this.unsetUninstalled(id)
436
						.then(() => {
437
							this.logService.info('Removed the extension from uninstalled list:', id);
438 439
							return this.getInstalled(LocalExtensionType.User);
						})
440 441 442 443 444 445
						.then(installed => installed.filter(i => i.identifier.id === id)[0]);
				}
				return null;
			});
	}

S
Sandeep Somavarapu 已提交
446
	private extractAndInstall({ zipPath, id, metadata }: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
447 448 449
		const location = type === LocalExtensionType.User ? this.extensionsPath : this.systemExtensionsPath;
		const tempPath = path.join(location, `.${id}`);
		const extensionPath = path.join(location, id);
S
Sandeep Somavarapu 已提交
450
		return pfs.rimraf(extensionPath)
S
Sandeep Somavarapu 已提交
451
			.then(() => this.extractAndRename(id, zipPath, tempPath, extensionPath, token), e => Promise.reject(new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, id), INSTALL_ERROR_DELETING)))
452 453
			.then(() => {
				this.logService.info('Installation completed.', id);
454
				return this.scanExtension(id, location, type);
455
			})
456 457 458 459 460 461 462
			.then(local => {
				if (metadata) {
					local.metadata = metadata;
					return this.saveMetadataForLocalExtension(local);
				}
				return local;
			});
E
Erich Gamma 已提交
463 464
	}

S
Sandeep Somavarapu 已提交
465
	private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string, token: CancellationToken): Promise<void> {
466
		return this.extract(id, zipPath, extractPath, token)
S
Sandeep Somavarapu 已提交
467
			.then(() => this.rename(id, extractPath, renamePath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */)
S
Sandeep Somavarapu 已提交
468 469 470 471
				.then(
					() => this.logService.info('Renamed to', renamePath),
					e => {
						this.logService.info('Rename failed. Deleting from extracted location', extractPath);
S
Sandeep Somavarapu 已提交
472
						return always(pfs.rimraf(extractPath), () => null).then(() => Promise.reject(e));
S
Sandeep Somavarapu 已提交
473 474 475
					}));
	}

S
Sandeep Somavarapu 已提交
476
	private extract(id: string, zipPath: string, extractPath: string, token: CancellationToken): Promise<void> {
477 478 479
		this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
		return pfs.rimraf(extractPath)
			.then(
480
				() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService, token)
M
Matt Bierner 已提交
481 482 483
					.then(
						() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
						e => always(pfs.rimraf(extractPath), () => null)
S
Sandeep Somavarapu 已提交
484 485
							.then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))),
				e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
486 487
	}

S
Sandeep Somavarapu 已提交
488
	private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
489
		return pfs.rename(extractPath, renamePath)
S
Sandeep Somavarapu 已提交
490 491 492 493 494
			.then(null, error => {
				if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
					this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`);
					return this.rename(id, extractPath, renamePath, retryUntil);
				}
S
Sandeep Somavarapu 已提交
495
				return Promise.reject(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING));
S
Sandeep Somavarapu 已提交
496
			});
S
Sandeep Somavarapu 已提交
497 498
	}

S
Sandeep Somavarapu 已提交
499
	private installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension): Promise<void> {
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
		if (this.galleryService.isEnabled()) {
			const dependenciesAndPackExtensions: string[] = installed.manifest.extensionDependencies || [];
			if (installed.manifest.extensionPack) {
				for (const extension of installed.manifest.extensionPack) {
					// add only those extensions which are new in currently installed extension
					if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {
						if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {
							dependenciesAndPackExtensions.push(extension);
						}
					}
				}
			}
			if (dependenciesAndPackExtensions.length) {
				return this.getInstalled()
					.then(installed => {
						// filter out installing and installed extensions
						const names = dependenciesAndPackExtensions.filter(id => !this.installingExtensions.has(adoptToGalleryExtensionId(id)) && installed.every(({ galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id })));
						if (names.length) {
							return this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length })
								.then(galleryResult => {
									const extensionsToInstall = galleryResult.firstPage;
S
Sandeep Somavarapu 已提交
521 522
									return Promise.all(extensionsToInstall.map(e => this.installFromGallery(e)))
										.then(() => null, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors)));
523 524 525 526 527 528
								});
						}
						return null;
					});
			}
		}
S
Sandeep Somavarapu 已提交
529
		return Promise.resolve(null);
530 531
	}

S
Sandeep Somavarapu 已提交
532
	private rollback(extensions: IGalleryExtension[]): Promise<void> {
S
Sandeep Somavarapu 已提交
533 534
		return this.getInstalled(LocalExtensionType.User)
			.then(installed =>
S
Sandeep Somavarapu 已提交
535
				Promise.all(installed.filter(local => extensions.some(galleryExtension => local.identifier.id === getLocalExtensionIdFromGallery(galleryExtension, galleryExtension.version))) // Only check id (pub.name-version) because we want to rollback the exact version
536
					.map(local => this.uninstall(local, true))))
S
Sandeep Somavarapu 已提交
537 538 539
			.then(() => null, () => null);
	}

S
Sandeep Somavarapu 已提交
540
	uninstall(extension: ILocalExtension, force = false): Promise<void> {
S
Sandeep Somavarapu 已提交
541
		this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
S
Sandeep Somavarapu 已提交
542
		return this.toNonCancellablePromise(this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
543
			.then(installed => {
544 545 546 547
				const extensionsToUninstall = installed
					.filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name);
				if (extensionsToUninstall.length) {
					const promises = extensionsToUninstall.map(e => this.checkForDependenciesAndUninstall(e, installed));
S
Sandeep Somavarapu 已提交
548
					return Promise.all(promises).then(() => null, error => Promise.reject(this.joinErrors(error)));
549
				} else {
S
Sandeep Somavarapu 已提交
550
					return Promise.reject(new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name)));
551
				}
S
Sandeep Somavarapu 已提交
552
			}));
S
Sandeep Somavarapu 已提交
553 554
	}

S
Sandeep Somavarapu 已提交
555
	updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
556
		this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);
557
		local.metadata = metadata;
558 559 560 561 562
		return this.saveMetadataForLocalExtension(local)
			.then(localExtension => {
				this.manifestCache.invalidate();
				return localExtension;
			});
563 564
	}

S
Sandeep Somavarapu 已提交
565
	private saveMetadataForLocalExtension(local: ILocalExtension): Promise<ILocalExtension> {
566
		if (!local.metadata) {
S
Sandeep Somavarapu 已提交
567
			return Promise.resolve(local);
568 569 570 571 572 573 574 575 576
		}
		const manifestPath = path.join(this.extensionsPath, local.identifier.id, 'package.json');
		return pfs.readFile(manifestPath, 'utf8')
			.then(raw => parseManifest(raw))
			.then(({ manifest }) => assign(manifest, { __metadata: local.metadata }))
			.then(manifest => pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')))
			.then(() => local);
	}

S
Sandeep Somavarapu 已提交
577
	private getMetadata(extensionName: string): Promise<IGalleryMetadata> {
S
Sandeep Somavarapu 已提交
578 579 580 581
		return this.findGalleryExtensionByName(extensionName)
			.then(galleryExtension => galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : null);
	}

S
Sandeep Somavarapu 已提交
582
	private findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
S
Sandeep Somavarapu 已提交
583 584 585 586 587 588 589
		if (local.identifier.uuid) {
			return this.findGalleryExtensionById(local.identifier.uuid)
				.then(galleryExtension => galleryExtension ? galleryExtension : this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local)));
		}
		return this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local));
	}

S
Sandeep Somavarapu 已提交
590
	private findGalleryExtensionById(uuid: string): Promise<IGalleryExtension> {
S
Sandeep Somavarapu 已提交
591 592 593
		return this.galleryService.query({ ids: [uuid], pageSize: 1 }).then(galleryResult => galleryResult.firstPage[0]);
	}

S
Sandeep Somavarapu 已提交
594
	private findGalleryExtensionByName(name: string): Promise<IGalleryExtension> {
S
Sandeep Somavarapu 已提交
595
		return this.galleryService.query({ names: [name], pageSize: 1 }).then(galleryResult => galleryResult.firstPage[0]);
S
Sandeep Somavarapu 已提交
596 597
	}

598 599
	private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error {
		const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
S
Sandeep Somavarapu 已提交
600 601 602 603
		if (errors.length === 1) {
			return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
		}
		return errors.reduce<Error>((previousValue: Error, currentValue: Error | string) => {
S
Sandeep Somavarapu 已提交
604
			return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
S
Sandeep Somavarapu 已提交
605
		}, new Error(''));
J
Joao Moreno 已提交
606 607
	}

S
Sandeep Somavarapu 已提交
608
	private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
J
Joao Moreno 已提交
609
		return this.preUninstallExtension(extension)
610
			.then(() => {
611 612 613
				const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
				if (packedExtensions.length) {
					return this.uninstallExtensions(extension, packedExtensions, installed);
S
Sandeep Somavarapu 已提交
614
				}
S
Sandeep Somavarapu 已提交
615
				return this.uninstallExtensions(extension, [], installed);
616
			})
617
			.then(() => this.postUninstallExtension(extension),
M
Matt Bierner 已提交
618
				error => {
619
					this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
S
Sandeep Somavarapu 已提交
620
					return Promise.reject(error);
M
Matt Bierner 已提交
621
				});
S
Sandeep Somavarapu 已提交
622 623
	}

S
Sandeep Somavarapu 已提交
624
	private uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
625 626 627 628
		const dependents = this.getDependents(extension, installed);
		if (dependents.length) {
			const remainingDependents = dependents.filter(dependent => extension !== dependent && otherExtensionsToUninstall.indexOf(dependent) === -1);
			if (remainingDependents.length) {
S
Sandeep Somavarapu 已提交
629
				return Promise.reject(new Error(this.getDependentsErrorMessage(extension, remainingDependents)));
630 631
			}
		}
S
Sandeep Somavarapu 已提交
632
		return Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => null);
633 634
	}

S
Sandeep Somavarapu 已提交
635 636 637 638 639 640 641 642 643 644 645 646 647
	private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string {
		if (dependents.length === 1) {
			return nls.localize('singleDependentError', "Cannot uninstall extension '{0}'. Extension '{1}' depends on this.",
				extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
		}
		if (dependents.length === 2) {
			return nls.localize('twoDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}' and '{2}' depend on this.",
				extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
		}
		return nls.localize('multipleDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}', '{2}' and others depend on this.",
			extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
	}

648 649 650 651 652 653 654 655 656
	private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
		if (checked.indexOf(extension) !== -1) {
			return [];
		}
		checked.push(extension);
		if (!extension.manifest.extensionPack || extension.manifest.extensionPack.length === 0) {
			return [];
		}
		const packedExtensions = installed.filter(i => extension.manifest.extensionPack.some(id => areSameExtensions({ id }, i.galleryIdentifier)));
M
Matt Bierner 已提交
657
		const packOfPackedExtensions: ILocalExtension[] = [];
658 659 660 661 662 663 664
		for (const packedExtension of packedExtensions) {
			packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
		}
		return [...packedExtensions, ...packOfPackedExtensions];
	}

	private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {
665
		return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.galleryIdentifier)));
666 667
	}

S
Sandeep Somavarapu 已提交
668
	private doUninstall(extension: ILocalExtension): Promise<void> {
J
Joao Moreno 已提交
669
		return this.preUninstallExtension(extension)
S
Sandeep Somavarapu 已提交
670
			.then(() => this.uninstallExtension(extension))
671
			.then(() => this.postUninstallExtension(extension),
M
Matt Bierner 已提交
672
				error => {
673
					this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
S
Sandeep Somavarapu 已提交
674
					return Promise.reject(error);
M
Matt Bierner 已提交
675
				});
S
Sandeep Somavarapu 已提交
676
	}
E
Erich Gamma 已提交
677

S
Sandeep Somavarapu 已提交
678
	private preUninstallExtension(extension: ILocalExtension): Promise<void> {
679
		return Promise.resolve(pfs.exists(extension.location.fsPath))
S
Sandeep Somavarapu 已提交
680
			.then(exists => exists ? null : Promise.reject(new Error(nls.localize('notExists', "Could not find extension"))))
681
			.then(() => {
682
				this.logService.info('Uninstalling extension:', extension.identifier.id);
683 684
				this._onUninstallExtension.fire(extension.identifier);
			});
S
Sandeep Somavarapu 已提交
685 686
	}

S
Sandeep Somavarapu 已提交
687
	private uninstallExtension(local: ILocalExtension): Promise<void> {
S
Sandeep Somavarapu 已提交
688 689 690 691
		const id = getGalleryExtensionIdFromLocal(local);
		let promise = this.uninstallingExtensions.get(id);
		if (!promise) {
			// Set all versions of the extension as uninstalled
692
			promise = createCancelablePromise(token => this.scanUserExtensions(false)
S
Sandeep Somavarapu 已提交
693
				.then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id, uuid: local.identifier.uuid }))))
694
				.then(() => { this.uninstallingExtensions.delete(id); }));
S
Sandeep Somavarapu 已提交
695 696
			this.uninstallingExtensions.set(id, promise);
		}
S
Sandeep Somavarapu 已提交
697
		return promise;
S
Sandeep Somavarapu 已提交
698 699
	}

J
Joao Moreno 已提交
700
	private async postUninstallExtension(extension: ILocalExtension, error?: Error): Promise<void> {
701
		if (error) {
702
			this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
S
Sandeep Somavarapu 已提交
703 704
		} else {
			this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
705 706 707 708
			// only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
			if (extension.identifier.uuid) {
				await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
			}
709
		}
710 711 712
		this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), void 0, error);
		const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
		this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode });
E
Erich Gamma 已提交
713 714
	}

715
	getInstalled(type: LocalExtensionType | null = null): Promise<ILocalExtension[]> {
J
Joao Moreno 已提交
716 717 718
		const promises = [];

		if (type === null || type === LocalExtensionType.System) {
719
			promises.push(this.scanSystemExtensions().then(null, e => new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_SYS_EXTENSIONS)));
J
Joao Moreno 已提交
720 721 722
		}

		if (type === null || type === LocalExtensionType.User) {
723
			promises.push(this.scanUserExtensions(true).then(null, e => new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_USER_EXTENSIONS)));
J
Joao Moreno 已提交
724 725
		}

S
Sandeep Somavarapu 已提交
726
		return Promise.all<ILocalExtension[]>(promises).then(flatten, errors => Promise.reject(this.joinErrors(errors)));
J
Joao Moreno 已提交
727 728
	}

S
Sandeep Somavarapu 已提交
729
	private scanSystemExtensions(): Promise<ILocalExtension[]> {
730
		this.logService.trace('Started scanning system extensions');
731
		const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, LocalExtensionType.System)
732
			.then(result => {
733
				this.logService.info('Scanned system extensions:', result.length);
734 735
				return result;
			});
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
		if (this.environmentService.isBuilt) {
			return systemExtensionsPromise;
		}

		// Scan other system extensions during development
		const devSystemExtensionsPromise = this.getDevSystemExtensionsList()
			.then(devSystemExtensionsList => {
				if (devSystemExtensionsList.length) {
					return this.scanExtensions(this.devSystemExtensionsPath, LocalExtensionType.System)
						.then(result => {
							this.logService.info('Scanned dev system extensions:', result.length);
							return result.filter(r => devSystemExtensionsList.some(id => areSameExtensions(r.galleryIdentifier, { id })));
						});
				} else {
					return [];
				}
			});
		return Promise.all([systemExtensionsPromise, devSystemExtensionsPromise])
			.then(([systemExtensions, devSystemExtensions]) => [...systemExtensions, ...devSystemExtensions]);
J
Joao Moreno 已提交
755 756
	}

S
Sandeep Somavarapu 已提交
757
	private scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
758
		this.logService.trace('Started scanning user extensions');
S
Sandeep Somavarapu 已提交
759
		return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, LocalExtensionType.User)])
S
Sandeep Somavarapu 已提交
760 761
			.then(([uninstalled, extensions]) => {
				extensions = extensions.filter(e => !uninstalled[e.identifier.id]);
S
Sandeep Somavarapu 已提交
762 763
				if (excludeOutdated) {
					const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid }));
S
Sandeep Somavarapu 已提交
764
					extensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
S
Sandeep Somavarapu 已提交
765
				}
S
Sandeep Somavarapu 已提交
766
				this.logService.info('Scanned user extensions:', extensions.length);
S
Sandeep Somavarapu 已提交
767 768
				return extensions;
			});
J
Joao Moreno 已提交
769 770
	}

S
Sandeep Somavarapu 已提交
771
	private scanExtensions(root: string, type: LocalExtensionType): Promise<ILocalExtension[]> {
J
Joao Moreno 已提交
772
		const limiter = new Limiter<any>(10);
S
Sandeep Somavarapu 已提交
773
		return pfs.readdir(root)
S
Sandeep Somavarapu 已提交
774
			.then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
S
Sandeep Somavarapu 已提交
775
			.then(extensions => extensions.filter(e => e && e.identifier));
S
Sandeep Somavarapu 已提交
776
	}
E
Erich Gamma 已提交
777

S
Sandeep Somavarapu 已提交
778
	private scanExtension(folderName: string, root: string, type: LocalExtensionType): Promise<ILocalExtension> {
N
Nestor Arias 已提交
779
		if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
S
Sandeep Somavarapu 已提交
780
			return Promise.resolve(null);
781
		}
S
Sandeep Somavarapu 已提交
782 783 784 785
		const extensionPath = path.join(root, folderName);
		return pfs.readdir(extensionPath)
			.then(children => readManifest(extensionPath)
				.then<ILocalExtension>(({ manifest, metadata }) => {
J
Joao Moreno 已提交
786 787 788 789
					const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
					const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null;
					const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
					const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)).toString() : null;
790 791 792 793 794 795
					if (manifest.extensionDependencies) {
						manifest.extensionDependencies = manifest.extensionDependencies.map(id => adoptToGalleryExtensionId(id));
					}
					if (manifest.extensionPack) {
						manifest.extensionPack = manifest.extensionPack.map(id => adoptToGalleryExtensionId(id));
					}
S
Sandeep Somavarapu 已提交
796
					const identifier = { id: type === LocalExtensionType.System ? folderName : getLocalExtensionIdFromManifest(manifest), uuid: metadata ? metadata.id : null };
S
Sandeep Somavarapu 已提交
797 798
					const galleryIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: identifier.uuid };
					return { type, identifier, galleryIdentifier, manifest, metadata, location: URI.file(extensionPath), readmeUrl, changelogUrl };
S
Sandeep Somavarapu 已提交
799 800
				}))
			.then(null, () => null);
E
Erich Gamma 已提交
801 802
	}

S
Sandeep Somavarapu 已提交
803
	removeDeprecatedExtensions(): Promise<any> {
S
Sandeep Somavarapu 已提交
804 805 806 807
		return this.removeUninstalledExtensions()
			.then(() => this.removeOutdatedExtensions());
	}

S
Sandeep Somavarapu 已提交
808
	private removeUninstalledExtensions(): Promise<void> {
809
		return this.getUninstalledExtensions()
S
Sandeep Somavarapu 已提交
810 811
			.then(uninstalled => this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
				.then(extensions => {
S
Sandeep Somavarapu 已提交
812
					const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[e.identifier.id]);
S
Sandeep Somavarapu 已提交
813
					return Promise.all(toRemove.map(e => this.extensionLifecycle.uninstall(e).then(() => this.removeUninstalledExtension(e))));
S
Sandeep Somavarapu 已提交
814 815 816
				})
			).then(() => null);
	}
S
Sandeep Somavarapu 已提交
817

S
Sandeep Somavarapu 已提交
818
	private removeOutdatedExtensions(): Promise<void> {
S
Sandeep Somavarapu 已提交
819 820 821
		return this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
			.then(extensions => {
				const toRemove: ILocalExtension[] = [];
S
Sandeep Somavarapu 已提交
822

S
Sandeep Somavarapu 已提交
823 824 825 826
				// Outdated extensions
				const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid }));
				toRemove.push(...flatten(byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version)).slice(1))));

S
Sandeep Somavarapu 已提交
827
				return Promise.all(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
S
Sandeep Somavarapu 已提交
828 829 830
			}).then(() => null);
	}

S
Sandeep Somavarapu 已提交
831
	private removeUninstalledExtension(extension: ILocalExtension): Promise<void> {
832
		return this.removeExtension(extension, 'uninstalled')
S
Sandeep Somavarapu 已提交
833 834 835 836
			.then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[extension.identifier.id]))
			.then(() => null);
	}

S
Sandeep Somavarapu 已提交
837
	private removeExtension(extension: ILocalExtension, type: string): Promise<void> {
838
		this.logService.trace(`Deleting ${type} extension from disk`, extension.identifier.id);
S
Sandeep Somavarapu 已提交
839
		return pfs.rimraf(extension.location.fsPath).then(() => this.logService.info('Deleted from disk', extension.identifier.id));
J
Joao Moreno 已提交
840 841
	}

S
Sandeep Somavarapu 已提交
842
	private isUninstalled(id: string): Promise<boolean> {
843
		return this.filterUninstalled(id).then(uninstalled => uninstalled.length === 1);
844 845
	}

S
Sandeep Somavarapu 已提交
846
	private filterUninstalled(...ids: string[]): Promise<string[]> {
847
		return this.withUninstalledExtensions(allUninstalled => {
M
Matt Bierner 已提交
848
			const uninstalled: string[] = [];
849
			for (const id of ids) {
850 851
				if (!!allUninstalled[id]) {
					uninstalled.push(id);
852 853
				}
			}
854
			return uninstalled;
855
		});
856 857
	}

S
Sandeep Somavarapu 已提交
858
	private setUninstalled(...extensions: ILocalExtension[]): Promise<void> {
S
Sandeep Somavarapu 已提交
859
		const ids = extensions.map(e => e.identifier.id);
S
Sandeep Somavarapu 已提交
860
		return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {})));
861 862
	}

S
Sandeep Somavarapu 已提交
863
	private unsetUninstalled(id: string): Promise<void> {
864
		return this.withUninstalledExtensions<void>(uninstalled => delete uninstalled[id]);
865 866
	}

S
Sandeep Somavarapu 已提交
867
	private getUninstalledExtensions(): Promise<{ [id: string]: boolean; }> {
868
		return this.withUninstalledExtensions(uninstalled => uninstalled);
869 870
	}

871 872
	private async withUninstalledExtensions<T>(fn: (uninstalled: { [id: string]: boolean; }) => T): Promise<T> {
		return await this.uninstalledFileLimiter.queue(() => {
873
			let result: T | null = null;
874
			return pfs.readFile(this.uninstalledPath, 'utf8')
S
Sandeep Somavarapu 已提交
875
				.then(null, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
J
Johannes Rieken 已提交
876
				.then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
877 878 879 880
				.then(uninstalled => { result = fn(uninstalled); return uninstalled; })
				.then(uninstalled => {
					if (Object.keys(uninstalled).length === 0) {
						return pfs.rimraf(this.uninstalledPath);
881
					} else {
882 883
						const raw = JSON.stringify(uninstalled);
						return pfs.writeFile(this.uninstalledPath, raw);
884 885 886
					}
				})
				.then(() => result);
887
		});
888
	}
889

S
Sandeep Somavarapu 已提交
890
	getExtensionsReport(): Promise<IReportedExtension[]> {
891
		const now = new Date().getTime();
J
Joao Moreno 已提交
892

893 894 895 896
		if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
			this.reportedExtensions = this.updateReportCache();
			this.lastReportTimestamp = now;
		}
J
Joao Moreno 已提交
897

898
		return this.reportedExtensions;
J
Joao Moreno 已提交
899 900
	}

S
Sandeep Somavarapu 已提交
901
	private updateReportCache(): Promise<IReportedExtension[]> {
J
Joao Moreno 已提交
902 903
		this.logService.trace('ExtensionManagementService.refreshReportedCache');

904
		return this.galleryService.getExtensionsReport()
J
Joao Moreno 已提交
905 906
			.then(result => {
				this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
907 908 909 910
				return result;
			}, err => {
				this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
				return [];
J
Joao Moreno 已提交
911 912
			});
	}
913

914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937
	private _devSystemExtensionsPath: string | null = null;
	private get devSystemExtensionsPath(): string {
		if (!this._devSystemExtensionsPath) {
			this._devSystemExtensionsPath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', '.build', 'builtInExtensions'));
		}
		return this._devSystemExtensionsPath;
	}

	private _devSystemExtensionsFilePath: string | null = null;
	private get devSystemExtensionsFilePath(): string {
		if (!this._devSystemExtensionsFilePath) {
			this._devSystemExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json'));
		}
		return this._devSystemExtensionsFilePath;
	}

	private getDevSystemExtensionsList(): Promise<string[]> {
		return pfs.readFile(this.devSystemExtensionsFilePath, 'utf8')
			.then<string[]>(raw => {
				const parsed: { name: string }[] = JSON.parse(raw);
				return parsed.map(({ name }) => name);
			});
	}

S
Sandeep Somavarapu 已提交
938 939
	private toNonCancellablePromise<T>(promise: Promise<T>): Promise<T> {
		return new Promise((c, e) => promise.then(result => c(result), error => e(error)));
S
Sandeep Somavarapu 已提交
940 941
	}

942 943 944 945 946 947 948
	private reportTelemetry(eventName: string, extensionData: any, duration: number, error?: Error): void {
		const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
		/* __GDPR__
			"extensionGallery:install" : {
				"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
949
				"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
950 951 952 953 954 955 956 957 958 959 960 961 962 963 964
				"${include}": [
					"${GalleryExtensionTelemetryData}"
				]
			}
		*/
		/* __GDPR__
			"extensionGallery:uninstall" : {
				"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
				"${include}": [
					"${GalleryExtensionTelemetryData}"
				]
			}
		*/
965 966 967 968 969 970 971 972 973 974
		/* __GDPR__
			"extensionGallery:update" : {
				"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
				"${include}": [
					"${GalleryExtensionTelemetryData}"
				]
			}
		*/
975 976
		this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
	}
E
Erich Gamma 已提交
977
}
S
Sandeep Somavarapu 已提交
978 979 980 981 982 983 984

export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, version: string): string {
	return getLocalExtensionId(extension.identifier.id, version);
}

export function getLocalExtensionIdFromManifest(manifest: IExtensionManifest): string {
	return getLocalExtensionId(getGalleryExtensionId(manifest.publisher, manifest.name), manifest.version);
985
}