extensionManagementService.ts 46.1 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';
S
Sandeep Somavarapu 已提交
13
import { extract, buffer, 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';
E
Erich Gamma 已提交
49

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

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

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

S
Sandeep Somavarapu 已提交
81
export function validateLocalExtension(zipPath: string): Promise<IExtensionManifest> {
E
Erich Gamma 已提交
82 83
	return buffer(zipPath, 'extension/package.json')
		.then(buffer => parseManifest(buffer.toString('utf8')))
S
Sandeep Somavarapu 已提交
84
		.then(({ manifest }) => manifest);
E
Erich Gamma 已提交
85 86
}

S
Sandeep Somavarapu 已提交
87
function readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
88 89 90 91
	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 已提交
92
			.then(null, err => err.code !== 'ENOENT' ? Promise.reject<string>(err) : '{}')
93 94 95
			.then(raw => JSON.parse(raw))
	];

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

S
Sandeep Somavarapu 已提交
104 105 106
interface InstallableExtension {
	zipPath: string;
	id: string;
S
Sandeep Somavarapu 已提交
107
	metadata?: IGalleryMetadata;
S
Sandeep Somavarapu 已提交
108 109
}

110
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
E
Erich Gamma 已提交
111

112
	_serviceBrand: any;
E
Erich Gamma 已提交
113

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

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

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

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

S
Sandeep Somavarapu 已提交
134 135
	private _onDidUninstallExtension = new Emitter<DidUninstallExtensionEvent>();
	onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
E
Erich Gamma 已提交
136 137

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

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

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

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

S
Sandeep Somavarapu 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
	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])));
189 190
				}
			});
S
Sandeep Somavarapu 已提交
191 192 193 194 195 196
			return promise;
		};

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

197 198
	}

S
Sandeep Somavarapu 已提交
199
	install(vsix: URI, type: LocalExtensionType = LocalExtensionType.User): Promise<IExtensionIdentifier> {
S
Sandeep Somavarapu 已提交
200
		this.logService.trace('ExtensionManagementService#install', vsix.toString());
S
Sandeep Somavarapu 已提交
201
		return createCancelablePromise(token => {
202 203 204 205 206 207 208 209
			return this.downloadVsix(vsix)
				.then(downloadLocation => {
					const zipPath = path.resolve(downloadLocation.fsPath);

					return validateLocalExtension(zipPath)
						.then(manifest => {
							const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
							if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
S
Sandeep Somavarapu 已提交
210
								return Promise.reject(new Error(nls.localize('incompatible', "Unable to install Extension '{0}' as it is not compatible with Code '{1}'.", identifier.id, pkg.version)));
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
							}
							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 已提交
227
															return Promise.reject(e);
228 229 230 231
														});
											}
											return null;
										}),
S
Sandeep Somavarapu 已提交
232
									e => Promise.reject(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name))));
233 234
						});
				});
S
Sandeep Somavarapu 已提交
235
		});
236 237
	}

S
Sandeep Somavarapu 已提交
238
	private downloadVsix(vsix: URI): Promise<URI> {
239
		if (vsix.scheme === Schemas.file) {
S
Sandeep Somavarapu 已提交
240
			return Promise.resolve(vsix);
241 242 243 244 245 246 247 248
		}
		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 已提交
249
	private removeIfExists(id: string): Promise<void> {
S
Sandeep Somavarapu 已提交
250 251 252
		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 已提交
253
	}
254

S
Sandeep Somavarapu 已提交
255
	private checkOutdated(manifest: IExtensionManifest): Promise<boolean> {
S
Sandeep Somavarapu 已提交
256
		const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
257
		return this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
258 259 260 261
			.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?");
262
					const buttons = [
S
Sandeep Somavarapu 已提交
263 264 265
						nls.localize('override', "Override"),
						nls.localize('cancel', "Cancel")
					];
266
					return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
S
Sandeep Somavarapu 已提交
267 268 269 270
						.then<boolean>(value => {
							if (value === 0) {
								return this.uninstall(newer, true).then(() => true);
							}
S
Sandeep Somavarapu 已提交
271
							return Promise.reject(errors.canceled());
S
Sandeep Somavarapu 已提交
272 273 274
						});
				}
				return true;
275
			});
S
Sandeep Somavarapu 已提交
276 277
	}

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

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

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

			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)
315
							.then(installableExtension => this.installExtension(installableExtension, LocalExtensionType.User, cancellationToken).then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local)))
316
							.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
S
Sandeep Somavarapu 已提交
317
								.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))));
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
					})
					.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 已提交
333 334 335
							if (error instanceof Error) {
								error.name = errorCode;
							}
336 337 338 339 340 341 342 343 344
							errorCallback(error);
						});

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

		}
345

S
Sandeep Somavarapu 已提交
346
		return cancellablePromise;
S
Sandeep Somavarapu 已提交
347 348
	}

S
Sandeep Somavarapu 已提交
349
	reinstallFromGallery(extension: ILocalExtension): Promise<void> {
S
Sandeep Somavarapu 已提交
350
		this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
S
Sandeep Somavarapu 已提交
351
		if (!this.galleryService.isEnabled()) {
S
Sandeep Somavarapu 已提交
352
			return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
S
Sandeep Somavarapu 已提交
353 354 355 356
		}
		return this.findGalleryExtension(extension)
			.then(galleryExtension => {
				if (galleryExtension) {
S
Sandeep Somavarapu 已提交
357
					return this.setUninstalled(extension)
358 359
						.then(() => this.removeUninstalledExtension(extension)
							.then(
M
Matt Bierner 已提交
360
								() => this.installFromGallery(galleryExtension),
S
Sandeep Somavarapu 已提交
361
								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 已提交
362
				}
S
Sandeep Somavarapu 已提交
363
				return Promise.reject(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
S
Sandeep Somavarapu 已提交
364 365 366
			});
	}

S
Sandeep Somavarapu 已提交
367 368
	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 已提交
369 370
	}

371 372
	private getTelemetryEvent(operation: InstallOperation): string {
		return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
S
Sandeep Somavarapu 已提交
373 374
	}

S
Sandeep Somavarapu 已提交
375
	private checkMalicious(extension: IGalleryExtension): Promise<void> {
376 377 378 379 380 381 382 383
		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 已提交
384 385
	}

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

		return this.galleryService.loadCompatibleVersion(extension)
			.then(
M
Matt Bierner 已提交
395 396 397
				compatible => {
					if (compatible) {
						this.logService.trace('Started downloading extension:', extension.name);
398
						return this.galleryService.download(compatible, operation)
M
Matt Bierner 已提交
399 400
							.then(
								zipPath => {
S
Sandeep Somavarapu 已提交
401
									this.logService.info('Downloaded extension:', extension.name, zipPath);
M
Matt Bierner 已提交
402 403 404
									return validateLocalExtension(zipPath)
										.then(
											manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
S
Sandeep Somavarapu 已提交
405
											error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
M
Matt Bierner 已提交
406 407
										);
								},
S
Sandeep Somavarapu 已提交
408
								error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
M
Matt Bierner 已提交
409
					} else {
S
Sandeep Somavarapu 已提交
410
						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 已提交
411 412
					}
				},
S
Sandeep Somavarapu 已提交
413
				error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
S
Sandeep Somavarapu 已提交
414 415
	}

S
Sandeep Somavarapu 已提交
416
	private installExtension(installableExtension: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
417 418
		return this.unsetUninstalledAndGetLocal(installableExtension.id)
			.then(
M
Matt Bierner 已提交
419 420 421 422
				local => {
					if (local) {
						return local;
					}
423
					return this.extractAndInstall(installableExtension, type, token);
M
Matt Bierner 已提交
424 425 426
				},
				e => {
					if (isMacintosh) {
S
Sandeep Somavarapu 已提交
427
						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 已提交
428
					}
S
Sandeep Somavarapu 已提交
429
					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 已提交
430
				});
431
	}
J
Joao Moreno 已提交
432

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

S
Sandeep Somavarapu 已提交
450
	private extractAndInstall({ zipPath, id, metadata }: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
451 452 453
		const location = type === LocalExtensionType.User ? this.extensionsPath : this.systemExtensionsPath;
		const tempPath = path.join(location, `.${id}`);
		const extensionPath = path.join(location, id);
S
Sandeep Somavarapu 已提交
454
		return pfs.rimraf(extensionPath)
S
Sandeep Somavarapu 已提交
455
			.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)))
456 457
			.then(() => {
				this.logService.info('Installation completed.', id);
458
				return this.scanExtension(id, location, type);
459
			})
460 461 462 463 464 465 466
			.then(local => {
				if (metadata) {
					local.metadata = metadata;
					return this.saveMetadataForLocalExtension(local);
				}
				return local;
			});
E
Erich Gamma 已提交
467 468
	}

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

S
Sandeep Somavarapu 已提交
480
	private extract(id: string, zipPath: string, extractPath: string, token: CancellationToken): Promise<void> {
481 482 483
		this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
		return pfs.rimraf(extractPath)
			.then(
484
				() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService, token)
M
Matt Bierner 已提交
485 486 487
					.then(
						() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
						e => always(pfs.rimraf(extractPath), () => null)
S
Sandeep Somavarapu 已提交
488 489
							.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)));
490 491
	}

S
Sandeep Somavarapu 已提交
492
	private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
493
		return pfs.rename(extractPath, renamePath)
S
Sandeep Somavarapu 已提交
494 495 496 497 498
			.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 已提交
499
				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 已提交
500
			});
S
Sandeep Somavarapu 已提交
501 502
	}

S
Sandeep Somavarapu 已提交
503
	private installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension): Promise<void> {
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
		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 已提交
525 526
									return Promise.all(extensionsToInstall.map(e => this.installFromGallery(e)))
										.then(() => null, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors)));
527 528 529 530 531 532
								});
						}
						return null;
					});
			}
		}
S
Sandeep Somavarapu 已提交
533
		return Promise.resolve(null);
534 535
	}

S
Sandeep Somavarapu 已提交
536
	private rollback(extensions: IGalleryExtension[]): Promise<void> {
S
Sandeep Somavarapu 已提交
537 538
		return this.getInstalled(LocalExtensionType.User)
			.then(installed =>
S
Sandeep Somavarapu 已提交
539
				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
540
					.map(local => this.uninstall(local, true))))
S
Sandeep Somavarapu 已提交
541 542 543
			.then(() => null, () => null);
	}

S
Sandeep Somavarapu 已提交
544
	uninstall(extension: ILocalExtension, force = false): Promise<void> {
S
Sandeep Somavarapu 已提交
545
		this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
S
Sandeep Somavarapu 已提交
546
		return this.toNonCancellablePromise(this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
547
			.then(installed => {
548 549 550 551
				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 已提交
552
					return Promise.all(promises).then(() => null, error => Promise.reject(this.joinErrors(error)));
553
				} else {
S
Sandeep Somavarapu 已提交
554
					return Promise.reject(new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name)));
555
				}
S
Sandeep Somavarapu 已提交
556
			}));
S
Sandeep Somavarapu 已提交
557 558
	}

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

S
Sandeep Somavarapu 已提交
569
	private saveMetadataForLocalExtension(local: ILocalExtension): Promise<ILocalExtension> {
570
		if (!local.metadata) {
S
Sandeep Somavarapu 已提交
571
			return Promise.resolve(local);
572 573 574 575 576 577 578 579 580
		}
		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 已提交
581
	private getMetadata(extensionName: string): Promise<IGalleryMetadata> {
S
Sandeep Somavarapu 已提交
582 583 584 585
		return this.findGalleryExtensionByName(extensionName)
			.then(galleryExtension => galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : null);
	}

S
Sandeep Somavarapu 已提交
586
	private findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
S
Sandeep Somavarapu 已提交
587 588 589 590 591 592 593
		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 已提交
594
	private findGalleryExtensionById(uuid: string): Promise<IGalleryExtension> {
S
Sandeep Somavarapu 已提交
595 596 597
		return this.galleryService.query({ ids: [uuid], pageSize: 1 }).then(galleryResult => galleryResult.firstPage[0]);
	}

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

602 603
	private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error {
		const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
S
Sandeep Somavarapu 已提交
604 605 606 607
		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 已提交
608
			return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
S
Sandeep Somavarapu 已提交
609
		}, new Error(''));
J
Joao Moreno 已提交
610 611
	}

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

S
Sandeep Somavarapu 已提交
628
	private uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
629 630 631 632
		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 已提交
633
				return Promise.reject(new Error(this.getDependentsErrorMessage(extension, remainingDependents)));
634 635
			}
		}
S
Sandeep Somavarapu 已提交
636
		return Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => null);
637 638
	}

S
Sandeep Somavarapu 已提交
639 640 641 642 643 644 645 646 647 648 649 650 651
	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);
	}

652 653 654 655 656 657 658 659 660
	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 已提交
661
		const packOfPackedExtensions: ILocalExtension[] = [];
662 663 664 665 666 667 668
		for (const packedExtension of packedExtensions) {
			packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
		}
		return [...packedExtensions, ...packOfPackedExtensions];
	}

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

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

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

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

J
Joao Moreno 已提交
704
	private async postUninstallExtension(extension: ILocalExtension, error?: Error): Promise<void> {
705
		if (error) {
706
			this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
S
Sandeep Somavarapu 已提交
707 708
		} else {
			this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
709 710 711 712
			// 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);
			}
713
		}
714 715 716
		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 已提交
717 718
	}

719
	getInstalled(type: LocalExtensionType | null = null): Promise<ILocalExtension[]> {
J
Joao Moreno 已提交
720 721 722
		const promises = [];

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

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

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

S
Sandeep Somavarapu 已提交
733
	private scanSystemExtensions(): Promise<ILocalExtension[]> {
734
		this.logService.trace('Started scanning system extensions');
735
		return this.scanExtensions(this.systemExtensionsPath, LocalExtensionType.System)
736
			.then(result => {
737
				this.logService.info('Scanned system extensions:', result.length);
738 739
				return result;
			});
J
Joao Moreno 已提交
740 741
	}

S
Sandeep Somavarapu 已提交
742
	private scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
743
		this.logService.trace('Started scanning user extensions');
S
Sandeep Somavarapu 已提交
744
		return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, LocalExtensionType.User)])
S
Sandeep Somavarapu 已提交
745 746
			.then(([uninstalled, extensions]) => {
				extensions = extensions.filter(e => !uninstalled[e.identifier.id]);
S
Sandeep Somavarapu 已提交
747 748
				if (excludeOutdated) {
					const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid }));
S
Sandeep Somavarapu 已提交
749
					extensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
S
Sandeep Somavarapu 已提交
750
				}
S
Sandeep Somavarapu 已提交
751
				this.logService.info('Scanned user extensions:', extensions.length);
S
Sandeep Somavarapu 已提交
752 753
				return extensions;
			});
J
Joao Moreno 已提交
754 755
	}

S
Sandeep Somavarapu 已提交
756
	private scanExtensions(root: string, type: LocalExtensionType): Promise<ILocalExtension[]> {
E
Erich Gamma 已提交
757
		const limiter = new Limiter(10);
S
Sandeep Somavarapu 已提交
758
		return pfs.readdir(root)
S
Sandeep Somavarapu 已提交
759
			.then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
S
Sandeep Somavarapu 已提交
760
			.then(extensions => extensions.filter(e => e && e.identifier));
S
Sandeep Somavarapu 已提交
761
	}
E
Erich Gamma 已提交
762

S
Sandeep Somavarapu 已提交
763
	private scanExtension(folderName: string, root: string, type: LocalExtensionType): Promise<ILocalExtension> {
N
Nestor Arias 已提交
764
		if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
S
Sandeep Somavarapu 已提交
765
			return Promise.resolve(null);
766
		}
S
Sandeep Somavarapu 已提交
767 768 769 770
		const extensionPath = path.join(root, folderName);
		return pfs.readdir(extensionPath)
			.then(children => readManifest(extensionPath)
				.then<ILocalExtension>(({ manifest, metadata }) => {
J
Joao Moreno 已提交
771 772 773 774
					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;
775 776 777 778 779 780
					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 已提交
781
					const identifier = { id: type === LocalExtensionType.System ? folderName : getLocalExtensionIdFromManifest(manifest), uuid: metadata ? metadata.id : null };
S
Sandeep Somavarapu 已提交
782 783
					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 已提交
784 785
				}))
			.then(null, () => null);
E
Erich Gamma 已提交
786 787
	}

S
Sandeep Somavarapu 已提交
788
	removeDeprecatedExtensions(): Promise<any> {
S
Sandeep Somavarapu 已提交
789 790 791 792
		return this.removeUninstalledExtensions()
			.then(() => this.removeOutdatedExtensions());
	}

S
Sandeep Somavarapu 已提交
793
	private removeUninstalledExtensions(): Promise<void> {
794
		return this.getUninstalledExtensions()
S
Sandeep Somavarapu 已提交
795 796
			.then(uninstalled => this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
				.then(extensions => {
S
Sandeep Somavarapu 已提交
797
					const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[e.identifier.id]);
S
Sandeep Somavarapu 已提交
798
					return Promise.all(toRemove.map(e => this.extensionLifecycle.uninstall(e).then(() => this.removeUninstalledExtension(e))));
S
Sandeep Somavarapu 已提交
799 800 801
				})
			).then(() => null);
	}
S
Sandeep Somavarapu 已提交
802

S
Sandeep Somavarapu 已提交
803
	private removeOutdatedExtensions(): Promise<void> {
S
Sandeep Somavarapu 已提交
804 805 806
		return this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
			.then(extensions => {
				const toRemove: ILocalExtension[] = [];
S
Sandeep Somavarapu 已提交
807

S
Sandeep Somavarapu 已提交
808 809 810 811
				// 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 已提交
812
				return Promise.all(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
S
Sandeep Somavarapu 已提交
813 814 815
			}).then(() => null);
	}

S
Sandeep Somavarapu 已提交
816
	private removeUninstalledExtension(extension: ILocalExtension): Promise<void> {
817
		return this.removeExtension(extension, 'uninstalled')
S
Sandeep Somavarapu 已提交
818 819 820 821
			.then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[extension.identifier.id]))
			.then(() => null);
	}

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

S
Sandeep Somavarapu 已提交
827
	private isUninstalled(id: string): Promise<boolean> {
828
		return this.filterUninstalled(id).then(uninstalled => uninstalled.length === 1);
829 830
	}

S
Sandeep Somavarapu 已提交
831
	private filterUninstalled(...ids: string[]): Promise<string[]> {
832
		return this.withUninstalledExtensions(allUninstalled => {
M
Matt Bierner 已提交
833
			const uninstalled: string[] = [];
834
			for (const id of ids) {
835 836
				if (!!allUninstalled[id]) {
					uninstalled.push(id);
837 838
				}
			}
839
			return uninstalled;
840
		});
841 842
	}

S
Sandeep Somavarapu 已提交
843
	private setUninstalled(...extensions: ILocalExtension[]): Promise<void> {
S
Sandeep Somavarapu 已提交
844
		const ids = extensions.map(e => e.identifier.id);
S
Sandeep Somavarapu 已提交
845
		return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {})));
846 847
	}

S
Sandeep Somavarapu 已提交
848
	private unsetUninstalled(id: string): Promise<void> {
849
		return this.withUninstalledExtensions<void>(uninstalled => delete uninstalled[id]);
850 851
	}

S
Sandeep Somavarapu 已提交
852
	private getUninstalledExtensions(): Promise<{ [id: string]: boolean; }> {
853
		return this.withUninstalledExtensions(uninstalled => uninstalled);
854 855
	}

856 857
	private async withUninstalledExtensions<T>(fn: (uninstalled: { [id: string]: boolean; }) => T): Promise<T> {
		return await this.uninstalledFileLimiter.queue(() => {
858
			let result: T | null = null;
859
			return pfs.readFile(this.uninstalledPath, 'utf8')
S
Sandeep Somavarapu 已提交
860
				.then(null, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
J
Johannes Rieken 已提交
861
				.then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
862 863 864 865
				.then(uninstalled => { result = fn(uninstalled); return uninstalled; })
				.then(uninstalled => {
					if (Object.keys(uninstalled).length === 0) {
						return pfs.rimraf(this.uninstalledPath);
866
					} else {
867 868
						const raw = JSON.stringify(uninstalled);
						return pfs.writeFile(this.uninstalledPath, raw);
869 870 871
					}
				})
				.then(() => result);
872
		});
873
	}
874

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

878 879 880 881
		if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
			this.reportedExtensions = this.updateReportCache();
			this.lastReportTimestamp = now;
		}
J
Joao Moreno 已提交
882

883
		return this.reportedExtensions;
J
Joao Moreno 已提交
884 885
	}

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

889
		return this.galleryService.getExtensionsReport()
J
Joao Moreno 已提交
890 891
			.then(result => {
				this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
892 893 894 895
				return result;
			}, err => {
				this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
				return [];
J
Joao Moreno 已提交
896 897
			});
	}
898

S
Sandeep Somavarapu 已提交
899 900
	private toNonCancellablePromise<T>(promise: Promise<T>): Promise<T> {
		return new Promise((c, e) => promise.then(result => c(result), error => e(error)));
S
Sandeep Somavarapu 已提交
901 902
	}

903 904 905 906 907 908 909
	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" },
910
				"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
				"${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}"
				]
			}
		*/
926 927 928 929 930 931 932 933 934 935
		/* __GDPR__
			"extensionGallery:update" : {
				"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
				"${include}": [
					"${GalleryExtensionTelemetryData}"
				]
			}
		*/
936 937
		this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
	}
E
Erich Gamma 已提交
938
}
S
Sandeep Somavarapu 已提交
939 940 941 942 943 944 945

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);
946
}