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

'use strict';

8
import * as nls from 'vs/nls';
E
Erich Gamma 已提交
9 10
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
11
import * as errors from 'vs/base/common/errors';
E
Erich Gamma 已提交
12
import { assign } from 'vs/base/common/objects';
13
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
14
import { flatten } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
15
import { extract, buffer, ExtractError } from 'vs/base/node/zip';
16
import { TPromise, ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
J
Johannes Rieken 已提交
17 18
import {
	IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
19
	IGalleryExtension, IExtensionManifest, IGalleryMetadata,
J
Joao Moreno 已提交
20
	InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
S
Sandeep Somavarapu 已提交
21
	StatisticType,
J
Joao Moreno 已提交
22
	IExtensionIdentifier,
23
	IReportedExtension,
24
	InstallOperation
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';
29
import { Limiter, always } 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';
J
João Moreno 已提交
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';
E
Erich Gamma 已提交
43

J
Joao Moreno 已提交
44
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
45 46
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
47
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
S
Sandeep Somavarapu 已提交
48 49 50
const INSTALL_ERROR_INCOMPATIBLE = 'incompatible';
const INSTALL_ERROR_DOWNLOADING = 'downloading';
const INSTALL_ERROR_VALIDATING = 'validating';
51 52
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
53
const INSTALL_ERROR_EXTRACTING = 'extracting';
S
Sandeep Somavarapu 已提交
54
const INSTALL_ERROR_RENAMING = 'renaming';
55
const INSTALL_ERROR_DELETING = 'deleting';
56
const INSTALL_ERROR_MALICIOUS = 'malicious';
57
const ERROR_UNKNOWN = 'unknown';
S
Sandeep Somavarapu 已提交
58

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

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

78
export function validateLocalExtension(zipPath: string): TPromise<IExtensionManifest> {
E
Erich Gamma 已提交
79 80
	return buffer(zipPath, 'extension/package.json')
		.then(buffer => parseManifest(buffer.toString('utf8')))
81
		.then(({ manifest }) => TPromise.as(manifest));
E
Erich Gamma 已提交
82 83
}

84 85 86 87 88
function readManifest(extensionPath: string): TPromise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
	const promises = [
		pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8')
			.then(raw => parseManifest(raw)),
		pfs.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
R
Ron Buckton 已提交
89
			.then(null, err => err.code !== 'ENOENT' ? TPromise.wrapError<string>(err) : '{}')
90 91 92 93 94 95 96 97 98 99 100
			.then(raw => JSON.parse(raw))
	];

	return TPromise.join<any>(promises).then(([{ manifest, metadata }, translations]) => {
		return {
			manifest: localizeManifest(manifest, translations),
			metadata
		};
	});
}

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

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

109
	_serviceBrand: any;
E
Erich Gamma 已提交
110 111

	private extensionsPath: string;
112 113
	private uninstalledPath: string;
	private uninstalledFileLimiter: Limiter<void>;
114
	private reportedExtensions: TPromise<IReportedExtension[]> | undefined;
J
Joao Moreno 已提交
115
	private lastReportTimestamp = 0;
116
	private readonly installingExtensions: Map<string, TPromise<void>> = new Map<string, TPromise<void>>();
S
Sandeep Somavarapu 已提交
117
	private readonly uninstallingExtensions: Map<string, TPromise<void>> = new Map<string, TPromise<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 environmentService: IEnvironmentService,
135
		@IDialogService private dialogService: IDialogService,
136
		@IExtensionGalleryService private galleryService: IExtensionGalleryService,
137 138
		@ILogService private logService: ILogService,
		@ITelemetryService private telemetryService: ITelemetryService,
E
Erich Gamma 已提交
139
	) {
140
		super();
J
Joao Moreno 已提交
141
		this.extensionsPath = environmentService.extensionsPath;
142 143
		this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
		this.uninstalledFileLimiter = new Limiter(1);
144
		this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
S
Sandeep Somavarapu 已提交
145
		this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService));
S
Sandeep Somavarapu 已提交
146 147 148 149 150 151 152

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

S
Sandeep Somavarapu 已提交
155
	install(zipPath: string): TPromise<void> {
156 157
		zipPath = path.resolve(zipPath);

S
Sandeep Somavarapu 已提交
158
		return validateLocalExtension(zipPath)
S
Sandeep Somavarapu 已提交
159
			.then(manifest => {
S
Sandeep Somavarapu 已提交
160
				const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
S
Sandeep Somavarapu 已提交
161
				if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
S
Sandeep Somavarapu 已提交
162
					return TPromise.wrapError<void>(new Error(nls.localize('incompatible', "Unable to install Extension '{0}' as it is not compatible with Code '{1}'.", identifier.id, pkg.version)));
S
Sandeep Somavarapu 已提交
163
				}
S
Sandeep Somavarapu 已提交
164
				return this.removeIfExists(identifier.id)
165
					.then(
M
Matt Bierner 已提交
166 167 168 169 170 171 172 173 174 175
						() => 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, manifest),
											error => this.installFromZipPath(identifier, zipPath, null, manifest))
										.then(
S
Sandeep Somavarapu 已提交
176
											() => { this.logService.info('Successfully installed the extension:', identifier.id); },
M
Matt Bierner 已提交
177 178 179 180 181 182 183 184
											e => {
												this.logService.error('Failed to install the extension:', identifier.id, e.message);
												return TPromise.wrapError(e);
											});
								}
								return null;
							}),
						e => TPromise.wrapError(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name))));
185 186 187
			});
	}

S
Sandeep Somavarapu 已提交
188 189 190 191
	private removeIfExists(id: string): TPromise<void> {
		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 已提交
192
	}
193

S
Sandeep Somavarapu 已提交
194 195
	private checkOutdated(manifest: IExtensionManifest): TPromise<boolean> {
		const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
196
		return this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
197 198 199 200
			.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?");
201
					const buttons = [
S
Sandeep Somavarapu 已提交
202 203 204
						nls.localize('override', "Override"),
						nls.localize('cancel', "Cancel")
					];
205
					return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
S
Sandeep Somavarapu 已提交
206 207 208 209 210 211 212 213
						.then<boolean>(value => {
							if (value === 0) {
								return this.uninstall(newer, true).then(() => true);
							}
							return TPromise.wrapError(errors.canceled());
						});
				}
				return true;
214
			});
S
Sandeep Somavarapu 已提交
215 216
	}

S
Sandeep Somavarapu 已提交
217
	private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
218
		return this.toNonCancellablePromise(this.getInstalled()
S
Sandeep Somavarapu 已提交
219 220 221
			.then(installed => {
				const operation = this.getOperation({ id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }, installed);
				return this.installExtension({ zipPath, id: identifier.id, metadata })
222
					.then(local => this.installDependenciesAndPackExtensions(local, null).then(() => local, error => this.uninstall(local, true).then(() => TPromise.wrapError(error), () => TPromise.wrapError(error))))
S
Sandeep Somavarapu 已提交
223 224 225 226
					.then(
						local => { this._onDidInstallExtension.fire({ identifier, zipPath, local, operation }); return local; },
						error => { this._onDidInstallExtension.fire({ identifier, zipPath, operation, error }); return TPromise.wrapError(error); }
					);
S
Sandeep Somavarapu 已提交
227
			}));
E
Erich Gamma 已提交
228 229
	}

S
Sandeep Somavarapu 已提交
230
	installFromGallery(extension: IGalleryExtension): TPromise<void> {
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
		let installingExtension = this.installingExtensions.get(extension.identifier.id);
		if (!installingExtension) {

			let successCallback: ValueCallback<void>, errorCallback: ErrorCallback;
			installingExtension = new TPromise((c, e) => { successCallback = c; errorCallback = e; });
			this.installingExtensions.set(extension.identifier.id, installingExtension);

			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)
S
Sandeep Somavarapu 已提交
253
							.then(installableExtension => this.installExtension(installableExtension).then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local)))
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
							.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
								.then(() => local, error => this.uninstall(local, true).then(() => TPromise.wrapError(error), () => TPromise.wrapError(error))));
					})
					.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);
							errorCallback(error);
						});

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

		}
		return installingExtension;
S
Sandeep Somavarapu 已提交
281 282
	}

S
Sandeep Somavarapu 已提交
283
	reinstallFromGallery(extension: ILocalExtension): TPromise<void> {
S
Sandeep Somavarapu 已提交
284
		if (!this.galleryService.isEnabled()) {
285
			return TPromise.wrapError(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
S
Sandeep Somavarapu 已提交
286 287 288 289
		}
		return this.findGalleryExtension(extension)
			.then(galleryExtension => {
				if (galleryExtension) {
S
Sandeep Somavarapu 已提交
290
					return this.setUninstalled(extension)
291 292
						.then(() => this.removeUninstalledExtension(extension)
							.then(
M
Matt Bierner 已提交
293 294
								() => this.installFromGallery(galleryExtension),
								e => TPromise.wrapError(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 已提交
295
				}
296
				return TPromise.wrapError(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
S
Sandeep Somavarapu 已提交
297 298 299
			});
	}

S
Sandeep Somavarapu 已提交
300 301
	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 已提交
302 303
	}

304 305
	private getTelemetryEvent(operation: InstallOperation): string {
		return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
S
Sandeep Somavarapu 已提交
306 307
	}

308 309 310 311 312 313 314 315 316
	private checkMalicious(extension: IGalleryExtension): TPromise<void> {
		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 已提交
317 318
	}

S
Sandeep Somavarapu 已提交
319
	private downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): TPromise<InstallableExtension> {
S
Sandeep Somavarapu 已提交
320
		const metadata = <IGalleryMetadata>{
S
Sandeep Somavarapu 已提交
321
			id: extension.identifier.uuid,
S
Sandeep Somavarapu 已提交
322 323 324
			publisherId: extension.publisherId,
			publisherDisplayName: extension.publisherDisplayName,
		};
S
Sandeep Somavarapu 已提交
325 326 327

		return this.galleryService.loadCompatibleVersion(extension)
			.then(
M
Matt Bierner 已提交
328 329 330
				compatible => {
					if (compatible) {
						this.logService.trace('Started downloading extension:', extension.name);
331
						return this.galleryService.download(extension, operation)
M
Matt Bierner 已提交
332 333
							.then(
								zipPath => {
S
Sandeep Somavarapu 已提交
334
									this.logService.info('Downloaded extension:', extension.name, zipPath);
M
Matt Bierner 已提交
335 336 337 338 339 340 341 342 343 344 345 346
									return validateLocalExtension(zipPath)
										.then(
											manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
											error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
										);
								},
								error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
					} else {
						return TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
					}
				},
				error => TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
S
Sandeep Somavarapu 已提交
347 348
	}

349 350 351
	private installExtension(installableExtension: InstallableExtension): TPromise<ILocalExtension> {
		return this.unsetUninstalledAndGetLocal(installableExtension.id)
			.then(
M
Matt Bierner 已提交
352 353 354 355 356 357 358 359 360 361 362 363
				local => {
					if (local) {
						return local;
					}
					return this.extractAndInstall(installableExtension);
				},
				e => {
					if (isMacintosh) {
						return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
					}
					return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
				});
364
	}
J
Joao Moreno 已提交
365

366 367 368 369
	private unsetUninstalledAndGetLocal(id: string): TPromise<ILocalExtension> {
		return this.isUninstalled(id)
			.then(isUninstalled => {
				if (isUninstalled) {
370
					this.logService.trace('Removing the extension from uninstalled list:', id);
371 372
					// If the same version of extension is marked as uninstalled, remove it from there and return the local.
					return this.unsetUninstalled(id)
373
						.then(() => {
374
							this.logService.info('Removed the extension from uninstalled list:', id);
375 376
							return this.getInstalled(LocalExtensionType.User);
						})
377 378 379 380 381 382
						.then(installed => installed.filter(i => i.identifier.id === id)[0]);
				}
				return null;
			});
	}

S
Sandeep Somavarapu 已提交
383
	private extractAndInstall({ zipPath, id, metadata }: InstallableExtension): TPromise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
384
		const tempPath = path.join(this.extensionsPath, `.${id}`);
385
		const extensionPath = path.join(this.extensionsPath, id);
S
Sandeep Somavarapu 已提交
386 387
		return pfs.rimraf(extensionPath)
			.then(() => this.extractAndRename(id, zipPath, tempPath, extensionPath), e => TPromise.wrapError(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)))
388 389 390 391
			.then(() => {
				this.logService.info('Installation completed.', id);
				return this.scanExtension(id, this.extensionsPath, LocalExtensionType.User);
			})
392 393 394 395 396 397 398
			.then(local => {
				if (metadata) {
					local.metadata = metadata;
					return this.saveMetadataForLocalExtension(local);
				}
				return local;
			});
E
Erich Gamma 已提交
399 400
	}

S
Sandeep Somavarapu 已提交
401 402
	private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string): TPromise<void> {
		return this.extract(id, zipPath, extractPath)
S
Sandeep Somavarapu 已提交
403
			.then(() => this.rename(id, extractPath, renamePath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */)
S
Sandeep Somavarapu 已提交
404 405 406 407 408 409 410 411
				.then(
					() => this.logService.info('Renamed to', renamePath),
					e => {
						this.logService.info('Rename failed. Deleting from extracted location', extractPath);
						return always(pfs.rimraf(extractPath), () => null).then(() => TPromise.wrapError(e));
					}));
	}

412
	private extract(id: string, zipPath: string, extractPath: string): TPromise<void> {
413 414 415
		this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
		return pfs.rimraf(extractPath)
			.then(
S
Sandeep Somavarapu 已提交
416
				() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService)
M
Matt Bierner 已提交
417 418 419
					.then(
						() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
						e => always(pfs.rimraf(extractPath), () => null)
S
Sandeep Somavarapu 已提交
420
							.then(() => TPromise.wrapError(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))),
M
Matt Bierner 已提交
421
				e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
422 423
	}

424 425
	private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): TPromise<void> {
		return pfs.rename(extractPath, renamePath)
S
Sandeep Somavarapu 已提交
426 427 428 429 430
			.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 已提交
431
				return TPromise.wrapError(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING));
S
Sandeep Somavarapu 已提交
432
			});
S
Sandeep Somavarapu 已提交
433 434
	}

435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
	private installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension): TPromise<void> {
		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;
									return TPromise.join(extensionsToInstall.map(e => this.installFromGallery(e)))
										.then(() => null, errors => this.rollback(extensionsToInstall).then(() => TPromise.wrapError(errors), () => TPromise.wrapError(errors)));
								});
						}
						return null;
					});
			}
		}
		return TPromise.as(null);
	}

S
Sandeep Somavarapu 已提交
468 469 470 471
	private rollback(extensions: IGalleryExtension[]): TPromise<void> {
		return this.getInstalled(LocalExtensionType.User)
			.then(installed =>
				TPromise.join(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
472
					.map(local => this.uninstall(local, true))))
S
Sandeep Somavarapu 已提交
473 474 475
			.then(() => null, () => null);
	}

476
	uninstall(extension: ILocalExtension, force = false): TPromise<void> {
S
Sandeep Somavarapu 已提交
477
		return this.toNonCancellablePromise(this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
478 479 480 481 482
			.then(installed => {
				const promises = installed
					.filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name)
					.map(e => this.checkForDependenciesAndUninstall(e, installed, force));
				return TPromise.join(promises).then(() => null, error => TPromise.wrapError(this.joinErrors(error)));
S
Sandeep Somavarapu 已提交
483
			}));
S
Sandeep Somavarapu 已提交
484 485
	}

486 487
	updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
		local.metadata = metadata;
488 489 490 491 492
		return this.saveMetadataForLocalExtension(local)
			.then(localExtension => {
				this.manifestCache.invalidate();
				return localExtension;
			});
493 494 495 496 497 498 499 500 501 502 503 504 505 506
	}

	private saveMetadataForLocalExtension(local: ILocalExtension): TPromise<ILocalExtension> {
		if (!local.metadata) {
			return TPromise.as(local);
		}
		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 已提交
507
	private getMetadata(extensionName: string): TPromise<IGalleryMetadata> {
S
Sandeep Somavarapu 已提交
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
		return this.findGalleryExtensionByName(extensionName)
			.then(galleryExtension => galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : null);
	}

	private findGalleryExtension(local: ILocalExtension): TPromise<IGalleryExtension> {
		if (local.identifier.uuid) {
			return this.findGalleryExtensionById(local.identifier.uuid)
				.then(galleryExtension => galleryExtension ? galleryExtension : this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local)));
		}
		return this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local));
	}

	private findGalleryExtensionById(uuid: string): TPromise<IGalleryExtension> {
		return this.galleryService.query({ ids: [uuid], pageSize: 1 }).then(galleryResult => galleryResult.firstPage[0]);
	}

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

528 529
	private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error {
		const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
S
Sandeep Somavarapu 已提交
530 531 532 533
		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 已提交
534
			return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
S
Sandeep Somavarapu 已提交
535
		}, new Error(''));
J
Joao Moreno 已提交
536 537
	}

538
	private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], force: boolean): TPromise<void> {
J
Joao Moreno 已提交
539
		return this.preUninstallExtension(extension)
540 541 542 543 544 545 546 547
			.then(() => {
				if (force) {
					return this.uninstallExtensionAsPack(extension, installed);
				}
				const hasInstalledExtensionPack = extension.manifest.extensionPack && extension.manifest.extensionPack.length && installed.some(i => extension.manifest.extensionPack.some(dep => areSameExtensions({ id: dep }, i.galleryIdentifier)));
				const hasDependencies = extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0;
				return hasInstalledExtensionPack || hasDependencies ? this.promptForPackAndUninstall(extension, installed) : this.uninstallExtensions(extension, [], installed);
			})
548
			.then(() => this.postUninstallExtension(extension),
M
Matt Bierner 已提交
549
				error => {
550
					this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
M
Matt Bierner 已提交
551 552
					return TPromise.wrapError(error);
				});
S
Sandeep Somavarapu 已提交
553 554
	}

555 556
	private promptForPackAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): TPromise<void> {
		const message = nls.localize('uninstallExtensionPackConfirmation', "Would you like to uninstall '{0}' only or as a pack?", extension.manifest.displayName || extension.manifest.name);
557
		const buttons = [
558 559
			nls.localize('uninstallPack', "Uninstall Extension Pack"),
			nls.localize('uninstallOnly', "Uninstall Extension Only"),
S
Sandeep Somavarapu 已提交
560 561
			nls.localize('cancel', "Cancel")
		];
562
		return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 2 })
S
Sandeep Somavarapu 已提交
563 564
			.then<void>(value => {
				if (value === 0) {
565
					return this.uninstallExtensionAsPack(extension, installed);
S
Sandeep Somavarapu 已提交
566 567
				}
				if (value === 1) {
568
					return this.uninstallExtensions(extension, [], installed);
S
Sandeep Somavarapu 已提交
569
				}
570
				this.logService.info('Cancelled uninstalling extension:', extension.identifier.id);
S
Sandeep Somavarapu 已提交
571 572 573 574
				return TPromise.wrapError(errors.canceled());
			}, error => TPromise.wrapError(errors.canceled()));
	}

575 576 577 578 579
	private uninstallExtensionAsPack(extension: ILocalExtension, installed: ILocalExtension[]): TPromise<void> {
		const extensionsToUninstall = this.getDependenciesToUninstall(extension, installed);
		for (const packExtensionToUninstall of this.getAllPackExtensionsToUninstall(extension, installed)) {
			if (extensionsToUninstall.indexOf(packExtensionToUninstall) === -1) {
				extensionsToUninstall.push(packExtensionToUninstall);
580
			}
581
		}
582
		return this.uninstallExtensions(extension, extensionsToUninstall, installed);
583 584
	}

585 586 587 588 589 590
	private uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): TPromise<void> {
		const dependents = this.getDependents(extension, installed);
		if (dependents.length) {
			const remainingDependents = dependents.filter(dependent => extension !== dependent && otherExtensionsToUninstall.indexOf(dependent) === -1);
			if (remainingDependents.length) {
				return TPromise.wrapError<void>(new Error(this.getDependentsErrorMessage(extension, remainingDependents)));
591 592
			}
		}
593
		return TPromise.join([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => null);
594 595
	}

S
Sandeep Somavarapu 已提交
596 597 598 599 600 601 602 603 604 605 606 607 608
	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);
	}

609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
	private getDependenciesToUninstall(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {
		const dependencies = this.getAllDependenciesToUninstall(extension, installed).filter(e => e !== extension);

		const dependenciesToUninstall = dependencies.slice(0);
		for (let index = 0; index < dependencies.length; index++) {
			const dep = dependencies[index];
			const dependents = this.getDependents(dep, installed);
			// Remove the dependency from the uninstall list if there is a dependent which will not be uninstalled.
			if (dependents.some(e => e !== extension && dependencies.indexOf(e) === -1)) {
				dependenciesToUninstall.splice(index - (dependencies.length - dependenciesToUninstall.length), 1);
			}
		}

		return dependenciesToUninstall;
	}

625
	private getAllDependenciesToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
626 627 628 629 630 631 632
		if (checked.indexOf(extension) !== -1) {
			return [];
		}
		checked.push(extension);
		if (!extension.manifest.extensionDependencies || extension.manifest.extensionDependencies.length === 0) {
			return [];
		}
633
		const dependenciesToUninstall = installed.filter(i => extension.manifest.extensionDependencies.some(id => areSameExtensions({ id }, i.galleryIdentifier)));
634 635
		const depsOfDeps = [];
		for (const dep of dependenciesToUninstall) {
636
			depsOfDeps.push(...this.getAllDependenciesToUninstall(dep, installed, checked));
637 638 639 640
		}
		return [...dependenciesToUninstall, ...depsOfDeps];
	}

641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
	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)));
		const packOfPackedExtensions = [];
		for (const packedExtension of packedExtensions) {
			packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
		}
		return [...packedExtensions, ...packOfPackedExtensions];
	}

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

J
Joao Moreno 已提交
661 662
	private doUninstall(extension: ILocalExtension): TPromise<void> {
		return this.preUninstallExtension(extension)
S
Sandeep Somavarapu 已提交
663
			.then(() => this.uninstallExtension(extension))
664
			.then(() => this.postUninstallExtension(extension),
M
Matt Bierner 已提交
665
				error => {
666
					this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
M
Matt Bierner 已提交
667 668
					return TPromise.wrapError(error);
				});
S
Sandeep Somavarapu 已提交
669
	}
E
Erich Gamma 已提交
670

J
Joao Moreno 已提交
671
	private preUninstallExtension(extension: ILocalExtension): TPromise<void> {
S
Sandeep Somavarapu 已提交
672
		return pfs.exists(extension.location.fsPath)
673
			.then(exists => exists ? null : TPromise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
674
			.then(() => {
675
				this.logService.info('Uninstalling extension:', extension.identifier.id);
676 677
				this._onUninstallExtension.fire(extension.identifier);
			});
S
Sandeep Somavarapu 已提交
678 679
	}

S
Sandeep Somavarapu 已提交
680
	private uninstallExtension(local: ILocalExtension): TPromise<void> {
S
Sandeep Somavarapu 已提交
681 682 683 684 685 686 687 688 689 690
		const id = getGalleryExtensionIdFromLocal(local);
		let promise = this.uninstallingExtensions.get(id);
		if (!promise) {
			// Set all versions of the extension as uninstalled
			promise = this.scanUserExtensions(false)
				.then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id, uuid: local.identifier.uuid }))))
				.then(() => { this.uninstallingExtensions.delete(id); });
			this.uninstallingExtensions.set(id, promise);
		}
		return promise;
S
Sandeep Somavarapu 已提交
691 692
	}

J
Joao Moreno 已提交
693
	private async postUninstallExtension(extension: ILocalExtension, error?: Error): Promise<void> {
694
		if (error) {
695
			this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
S
Sandeep Somavarapu 已提交
696 697
		} else {
			this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
698 699 700 701
			// 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);
			}
702
		}
703 704 705
		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 已提交
706 707
	}

J
Joao Moreno 已提交
708 709 710 711
	getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {
		const promises = [];

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

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

719
		return TPromise.join<ILocalExtension[]>(promises).then(flatten, errors => TPromise.wrapError<ILocalExtension[]>(this.joinErrors(errors)));
J
Joao Moreno 已提交
720 721 722
	}

	private scanSystemExtensions(): TPromise<ILocalExtension[]> {
723
		this.logService.trace('Started scanning system extensions');
724 725
		return this.scanExtensions(SystemExtensionsRoot, LocalExtensionType.System)
			.then(result => {
726
				this.logService.info('Scanned system extensions:', result.length);
727 728
				return result;
			});
J
Joao Moreno 已提交
729 730
	}

S
Sandeep Somavarapu 已提交
731
	private scanUserExtensions(excludeOutdated: boolean): TPromise<ILocalExtension[]> {
732
		this.logService.trace('Started scanning user extensions');
S
Sandeep Somavarapu 已提交
733 734 735
		return TPromise.join([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, LocalExtensionType.User)])
			.then(([uninstalled, extensions]) => {
				extensions = extensions.filter(e => !uninstalled[e.identifier.id]);
S
Sandeep Somavarapu 已提交
736 737
				if (excludeOutdated) {
					const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid }));
S
Sandeep Somavarapu 已提交
738
					extensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
S
Sandeep Somavarapu 已提交
739
				}
S
Sandeep Somavarapu 已提交
740
				this.logService.info('Scanned user extensions:', extensions.length);
S
Sandeep Somavarapu 已提交
741 742
				return extensions;
			});
J
Joao Moreno 已提交
743 744
	}

J
Joao Moreno 已提交
745
	private scanExtensions(root: string, type: LocalExtensionType): TPromise<ILocalExtension[]> {
E
Erich Gamma 已提交
746
		const limiter = new Limiter(10);
S
Sandeep Somavarapu 已提交
747
		return pfs.readdir(root)
S
Sandeep Somavarapu 已提交
748 749
			.then(extensionsFolders => TPromise.join<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
			.then(extensions => extensions.filter(e => e && e.identifier));
S
Sandeep Somavarapu 已提交
750
	}
E
Erich Gamma 已提交
751

S
Sandeep Somavarapu 已提交
752
	private scanExtension(folderName: string, root: string, type: LocalExtensionType): TPromise<ILocalExtension> {
753 754 755
		if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user exension folder starting with `.`
			return TPromise.as(null);
		}
S
Sandeep Somavarapu 已提交
756 757 758 759
		const extensionPath = path.join(root, folderName);
		return pfs.readdir(extensionPath)
			.then(children => readManifest(extensionPath)
				.then<ILocalExtension>(({ manifest, metadata }) => {
J
Joao Moreno 已提交
760 761 762 763
					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;
764 765 766 767 768 769
					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 已提交
770
					const identifier = { id: type === LocalExtensionType.System ? folderName : getLocalExtensionIdFromManifest(manifest), uuid: metadata ? metadata.id : null };
S
Sandeep Somavarapu 已提交
771 772
					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 已提交
773 774
				}))
			.then(null, () => null);
E
Erich Gamma 已提交
775 776
	}

J
Joao Moreno 已提交
777
	removeDeprecatedExtensions(): TPromise<any> {
S
Sandeep Somavarapu 已提交
778 779 780 781 782
		return this.removeUninstalledExtensions()
			.then(() => this.removeOutdatedExtensions());
	}

	private removeUninstalledExtensions(): TPromise<void> {
783
		return this.getUninstalledExtensions()
S
Sandeep Somavarapu 已提交
784 785
			.then(uninstalled => this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
				.then(extensions => {
S
Sandeep Somavarapu 已提交
786
					const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[e.identifier.id]);
787
					return TPromise.join(toRemove.map(e => this.extensionLifecycle.uninstall(e).then(() => this.removeUninstalledExtension(e))));
S
Sandeep Somavarapu 已提交
788 789 790
				})
			).then(() => null);
	}
S
Sandeep Somavarapu 已提交
791

S
Sandeep Somavarapu 已提交
792 793 794 795
	private removeOutdatedExtensions(): TPromise<void> {
		return this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
			.then(extensions => {
				const toRemove: ILocalExtension[] = [];
S
Sandeep Somavarapu 已提交
796

S
Sandeep Somavarapu 已提交
797 798 799 800
				// 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 已提交
801
				return TPromise.join(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
S
Sandeep Somavarapu 已提交
802 803 804 805
			}).then(() => null);
	}

	private removeUninstalledExtension(extension: ILocalExtension): TPromise<void> {
806
		return this.removeExtension(extension, 'uninstalled')
S
Sandeep Somavarapu 已提交
807 808 809 810
			.then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[extension.identifier.id]))
			.then(() => null);
	}

S
Sandeep Somavarapu 已提交
811
	private removeExtension(extension: ILocalExtension, type: string): TPromise<void> {
812
		this.logService.trace(`Deleting ${type} extension from disk`, extension.identifier.id);
S
Sandeep Somavarapu 已提交
813
		return pfs.rimraf(extension.location.fsPath).then(() => this.logService.info('Deleted from disk', extension.identifier.id));
J
Joao Moreno 已提交
814 815
	}

816 817
	private isUninstalled(id: string): TPromise<boolean> {
		return this.filterUninstalled(id).then(uninstalled => uninstalled.length === 1);
818 819
	}

820 821 822
	private filterUninstalled(...ids: string[]): TPromise<string[]> {
		return this.withUninstalledExtensions(allUninstalled => {
			const uninstalled = [];
823
			for (const id of ids) {
824 825
				if (!!allUninstalled[id]) {
					uninstalled.push(id);
826 827
				}
			}
828
			return uninstalled;
829
		});
830 831
	}

S
Sandeep Somavarapu 已提交
832 833
	private setUninstalled(...extensions: ILocalExtension[]): TPromise<void> {
		const ids = extensions.map(e => e.identifier.id);
S
Sandeep Somavarapu 已提交
834
		return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {})));
835 836
	}

837 838
	private unsetUninstalled(id: string): TPromise<void> {
		return this.withUninstalledExtensions<void>(uninstalled => delete uninstalled[id]);
839 840
	}

841 842
	private getUninstalledExtensions(): TPromise<{ [id: string]: boolean; }> {
		return this.withUninstalledExtensions(uninstalled => uninstalled);
843 844
	}

845 846
	private withUninstalledExtensions<T>(fn: (uninstalled: { [id: string]: boolean; }) => T): TPromise<T> {
		return this.uninstalledFileLimiter.queue(() => {
847
			let result: T = null;
848
			return pfs.readFile(this.uninstalledPath, 'utf8')
R
Ron Buckton 已提交
849
				.then(null, err => err.code === 'ENOENT' ? TPromise.as('{}') : TPromise.wrapError(err))
J
Johannes Rieken 已提交
850
				.then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
851 852 853 854
				.then(uninstalled => { result = fn(uninstalled); return uninstalled; })
				.then(uninstalled => {
					if (Object.keys(uninstalled).length === 0) {
						return pfs.rimraf(this.uninstalledPath);
855
					} else {
856 857
						const raw = JSON.stringify(uninstalled);
						return pfs.writeFile(this.uninstalledPath, raw);
858 859 860 861 862
					}
				})
				.then(() => result);
		});
	}
863

864 865
	getExtensionsReport(): TPromise<IReportedExtension[]> {
		const now = new Date().getTime();
J
Joao Moreno 已提交
866

867 868 869 870
		if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
			this.reportedExtensions = this.updateReportCache();
			this.lastReportTimestamp = now;
		}
J
Joao Moreno 已提交
871

872
		return this.reportedExtensions;
J
Joao Moreno 已提交
873 874
	}

875
	private updateReportCache(): TPromise<IReportedExtension[]> {
J
Joao Moreno 已提交
876 877
		this.logService.trace('ExtensionManagementService.refreshReportedCache');

878
		return this.galleryService.getExtensionsReport()
J
Joao Moreno 已提交
879 880
			.then(result => {
				this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
881 882 883 884
				return result;
			}, err => {
				this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
				return [];
J
Joao Moreno 已提交
885 886
			});
	}
887

S
Sandeep Somavarapu 已提交
888 889 890 891
	private toNonCancellablePromise<T>(promise: TPromise<T>): TPromise<T> {
		return new TPromise((c, e) => promise.then(result => c(result), error => e(error)), () => this.logService.debug('Request Cancelled'));
	}

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

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