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

'use strict';

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

49
const SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions'));
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 55
const INSTALL_ERROR_INCOMPATIBLE = 'incompatible';
const INSTALL_ERROR_DOWNLOADING = 'downloading';
const INSTALL_ERROR_VALIDATING = 'validating';
56 57
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
58
const INSTALL_ERROR_EXTRACTING = 'extracting';
S
Sandeep Somavarapu 已提交
59
const INSTALL_ERROR_RENAMING = 'renaming';
60
const INSTALL_ERROR_DELETING = 'deleting';
61
const INSTALL_ERROR_MALICIOUS = 'malicious';
62
const ERROR_UNKNOWN = 'unknown';
S
Sandeep Somavarapu 已提交
63

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

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

83
export function validateLocalExtension(zipPath: string): TPromise<IExtensionManifest> {
E
Erich Gamma 已提交
84 85
	return buffer(zipPath, 'extension/package.json')
		.then(buffer => parseManifest(buffer.toString('utf8')))
86
		.then(({ manifest }) => TPromise.as(manifest));
E
Erich Gamma 已提交
87 88
}

89 90 91 92 93
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 已提交
94
			.then(null, err => err.code !== 'ENOENT' ? TPromise.wrapError<string>(err) : '{}')
95 96 97 98 99 100 101 102 103 104 105
			.then(raw => JSON.parse(raw))
	];

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

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

112
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
E
Erich Gamma 已提交
113

114
	_serviceBrand: any;
E
Erich Gamma 已提交
115 116

	private extensionsPath: string;
117 118
	private uninstalledPath: string;
	private uninstalledFileLimiter: Limiter<void>;
119
	private reportedExtensions: TPromise<IReportedExtension[]> | undefined;
J
Joao Moreno 已提交
120
	private lastReportTimestamp = 0;
121
	private readonly installingExtensions: Map<string, TPromise<void>> = new Map<string, TPromise<void>>();
S
Sandeep Somavarapu 已提交
122
	private readonly uninstallingExtensions: Map<string, TPromise<void>> = new Map<string, TPromise<void>>();
123
	private readonly manifestCache: ExtensionsManifestCache;
S
Sandeep Somavarapu 已提交
124
	private readonly extensionLifecycle: ExtensionsLifecycle;
E
Erich Gamma 已提交
125

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

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

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

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

	constructor(
139
		@IEnvironmentService environmentService: IEnvironmentService,
140
		@IDialogService private dialogService: IDialogService,
141
		@IExtensionGalleryService private galleryService: IExtensionGalleryService,
142
		@ILogService private logService: ILogService,
143
		@IDownloadService private downloadService: IDownloadService,
144
		@ITelemetryService private telemetryService: ITelemetryService,
E
Erich Gamma 已提交
145
	) {
146
		super();
J
Joao Moreno 已提交
147
		this.extensionsPath = environmentService.extensionsPath;
148 149
		this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
		this.uninstalledFileLimiter = new Limiter(1);
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
	}

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
	zip(extension: ILocalExtension): TPromise<URI> {
		return this.collectFiles(extension)
			.then(files => zip(path.join(tmpdir(), generateUuid()), files))
			.then(path => URI.file(path));
	}

	unzip(zipLocation: URI): TPromise<void> {
		const downloadedLocation = path.join(tmpdir(), generateUuid());
		return this.downloadService.download(zipLocation, downloadedLocation).then(() => this.install(URI.file(downloadedLocation)));
	}

	private collectFiles(extension: ILocalExtension): TPromise<IFile[]> {
		return new TPromise((c, e) => {
			glob('**', { cwd: extension.location.fsPath, nodir: true, dot: true }, (err: Error, files: string[]) => {
				if (err) {
					e(err);
				} else {
					c(files.map(f => f.replace(/\\/g, '/'))
						.map(f => (<IFile>{ path: `extension/${f}`, localPath: path.join(extension.location.fsPath, f) })));
				}
			});
		});
	}

	install(vsix: URI): TPromise<void> {
		const zipPath = path.resolve(vsix.fsPath);
187

S
Sandeep Somavarapu 已提交
188
		return validateLocalExtension(zipPath)
S
Sandeep Somavarapu 已提交
189
			.then(manifest => {
S
Sandeep Somavarapu 已提交
190
				const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
S
Sandeep Somavarapu 已提交
191
				if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
S
Sandeep Somavarapu 已提交
192
					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 已提交
193
				}
S
Sandeep Somavarapu 已提交
194
				return this.removeIfExists(identifier.id)
195
					.then(
M
Matt Bierner 已提交
196 197 198 199 200 201 202 203 204 205
						() => 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 已提交
206
											() => { this.logService.info('Successfully installed the extension:', identifier.id); },
M
Matt Bierner 已提交
207 208 209 210 211 212 213 214
											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))));
215 216 217
			});
	}

S
Sandeep Somavarapu 已提交
218 219 220 221
	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 已提交
222
	}
223

S
Sandeep Somavarapu 已提交
224 225
	private checkOutdated(manifest: IExtensionManifest): TPromise<boolean> {
		const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
226
		return this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
227 228 229 230
			.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?");
231
					const buttons = [
S
Sandeep Somavarapu 已提交
232 233 234
						nls.localize('override', "Override"),
						nls.localize('cancel', "Cancel")
					];
235
					return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
S
Sandeep Somavarapu 已提交
236 237 238 239 240 241 242 243
						.then<boolean>(value => {
							if (value === 0) {
								return this.uninstall(newer, true).then(() => true);
							}
							return TPromise.wrapError(errors.canceled());
						});
				}
				return true;
244
			});
S
Sandeep Somavarapu 已提交
245 246
	}

S
Sandeep Somavarapu 已提交
247
	private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
248
		return this.toNonCancellablePromise(this.getInstalled()
S
Sandeep Somavarapu 已提交
249 250 251
			.then(installed => {
				const operation = this.getOperation({ id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }, installed);
				return this.installExtension({ zipPath, id: identifier.id, metadata })
252
					.then(local => this.installDependenciesAndPackExtensions(local, null).then(() => local, error => this.uninstall(local, true).then(() => TPromise.wrapError(error), () => TPromise.wrapError(error))))
S
Sandeep Somavarapu 已提交
253 254 255 256
					.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 已提交
257
			}));
E
Erich Gamma 已提交
258 259
	}

S
Sandeep Somavarapu 已提交
260
	installFromGallery(extension: IGalleryExtension): TPromise<void> {
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
		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 已提交
283
							.then(installableExtension => this.installExtension(installableExtension).then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local)))
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
							.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 已提交
311 312
	}

S
Sandeep Somavarapu 已提交
313
	reinstallFromGallery(extension: ILocalExtension): TPromise<void> {
S
Sandeep Somavarapu 已提交
314
		if (!this.galleryService.isEnabled()) {
315
			return TPromise.wrapError(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
S
Sandeep Somavarapu 已提交
316 317 318 319
		}
		return this.findGalleryExtension(extension)
			.then(galleryExtension => {
				if (galleryExtension) {
S
Sandeep Somavarapu 已提交
320
					return this.setUninstalled(extension)
321 322
						.then(() => this.removeUninstalledExtension(extension)
							.then(
M
Matt Bierner 已提交
323 324
								() => 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 已提交
325
				}
326
				return TPromise.wrapError(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
S
Sandeep Somavarapu 已提交
327 328 329
			});
	}

S
Sandeep Somavarapu 已提交
330 331
	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 已提交
332 333
	}

334 335
	private getTelemetryEvent(operation: InstallOperation): string {
		return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
S
Sandeep Somavarapu 已提交
336 337
	}

338 339 340 341 342 343 344 345 346
	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 已提交
347 348
	}

S
Sandeep Somavarapu 已提交
349
	private downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): TPromise<InstallableExtension> {
S
Sandeep Somavarapu 已提交
350
		const metadata = <IGalleryMetadata>{
S
Sandeep Somavarapu 已提交
351
			id: extension.identifier.uuid,
S
Sandeep Somavarapu 已提交
352 353 354
			publisherId: extension.publisherId,
			publisherDisplayName: extension.publisherDisplayName,
		};
S
Sandeep Somavarapu 已提交
355 356 357

		return this.galleryService.loadCompatibleVersion(extension)
			.then(
M
Matt Bierner 已提交
358 359 360
				compatible => {
					if (compatible) {
						this.logService.trace('Started downloading extension:', extension.name);
361
						return this.galleryService.download(extension, operation)
M
Matt Bierner 已提交
362 363
							.then(
								zipPath => {
S
Sandeep Somavarapu 已提交
364
									this.logService.info('Downloaded extension:', extension.name, zipPath);
M
Matt Bierner 已提交
365 366 367 368 369 370 371 372 373 374 375 376
									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 已提交
377 378
	}

379 380 381
	private installExtension(installableExtension: InstallableExtension): TPromise<ILocalExtension> {
		return this.unsetUninstalledAndGetLocal(installableExtension.id)
			.then(
M
Matt Bierner 已提交
382 383 384 385 386 387 388 389 390 391 392 393
				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));
				});
394
	}
J
Joao Moreno 已提交
395

396 397 398 399
	private unsetUninstalledAndGetLocal(id: string): TPromise<ILocalExtension> {
		return this.isUninstalled(id)
			.then(isUninstalled => {
				if (isUninstalled) {
400
					this.logService.trace('Removing the extension from uninstalled list:', id);
401 402
					// If the same version of extension is marked as uninstalled, remove it from there and return the local.
					return this.unsetUninstalled(id)
403
						.then(() => {
404
							this.logService.info('Removed the extension from uninstalled list:', id);
405 406
							return this.getInstalled(LocalExtensionType.User);
						})
407 408 409 410 411 412
						.then(installed => installed.filter(i => i.identifier.id === id)[0]);
				}
				return null;
			});
	}

S
Sandeep Somavarapu 已提交
413
	private extractAndInstall({ zipPath, id, metadata }: InstallableExtension): TPromise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
414
		const tempPath = path.join(this.extensionsPath, `.${id}`);
415
		const extensionPath = path.join(this.extensionsPath, id);
S
Sandeep Somavarapu 已提交
416 417
		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)))
418 419 420 421
			.then(() => {
				this.logService.info('Installation completed.', id);
				return this.scanExtension(id, this.extensionsPath, LocalExtensionType.User);
			})
422 423 424 425 426 427 428
			.then(local => {
				if (metadata) {
					local.metadata = metadata;
					return this.saveMetadataForLocalExtension(local);
				}
				return local;
			});
E
Erich Gamma 已提交
429 430
	}

S
Sandeep Somavarapu 已提交
431 432
	private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string): TPromise<void> {
		return this.extract(id, zipPath, extractPath)
S
Sandeep Somavarapu 已提交
433
			.then(() => this.rename(id, extractPath, renamePath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */)
S
Sandeep Somavarapu 已提交
434 435 436 437 438 439 440 441
				.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));
					}));
	}

442
	private extract(id: string, zipPath: string, extractPath: string): TPromise<void> {
443 444 445
		this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
		return pfs.rimraf(extractPath)
			.then(
S
Sandeep Somavarapu 已提交
446
				() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService)
M
Matt Bierner 已提交
447 448 449
					.then(
						() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
						e => always(pfs.rimraf(extractPath), () => null)
S
Sandeep Somavarapu 已提交
450
							.then(() => TPromise.wrapError(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))),
M
Matt Bierner 已提交
451
				e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
452 453
	}

454 455
	private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): TPromise<void> {
		return pfs.rename(extractPath, renamePath)
S
Sandeep Somavarapu 已提交
456 457 458 459 460
			.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 已提交
461
				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 已提交
462
			});
S
Sandeep Somavarapu 已提交
463 464
	}

465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
	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 已提交
498 499 500 501
	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
502
					.map(local => this.uninstall(local, true))))
S
Sandeep Somavarapu 已提交
503 504 505
			.then(() => null, () => null);
	}

506
	uninstall(extension: ILocalExtension, force = false): TPromise<void> {
S
Sandeep Somavarapu 已提交
507
		return this.toNonCancellablePromise(this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
508 509 510 511 512
			.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 已提交
513
			}));
S
Sandeep Somavarapu 已提交
514 515
	}

516 517
	updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
		local.metadata = metadata;
518 519 520 521 522
		return this.saveMetadataForLocalExtension(local)
			.then(localExtension => {
				this.manifestCache.invalidate();
				return localExtension;
			});
523 524 525 526 527 528 529 530 531 532 533 534 535 536
	}

	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 已提交
537
	private getMetadata(extensionName: string): TPromise<IGalleryMetadata> {
S
Sandeep Somavarapu 已提交
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
		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 已提交
556 557
	}

558 559
	private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error {
		const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
S
Sandeep Somavarapu 已提交
560 561 562 563
		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 已提交
564
			return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
S
Sandeep Somavarapu 已提交
565
		}, new Error(''));
J
Joao Moreno 已提交
566 567
	}

568
	private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], force: boolean): TPromise<void> {
J
Joao Moreno 已提交
569
		return this.preUninstallExtension(extension)
570
			.then(() => {
571 572 573
				const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
				if (packedExtensions.length) {
					return this.uninstallExtensions(extension, packedExtensions, installed);
S
Sandeep Somavarapu 已提交
574 575 576
				}
				const dependencies = this.getDependenciesToUninstall(extension, installed);
				if (dependencies.length) {
577 578 579 580 581
					if (force) {
						return this.uninstallExtensions(extension, dependencies, installed);
					} else {
						return this.promptForDependenciesAndUninstall(extension, dependencies, installed);
					}
S
Sandeep Somavarapu 已提交
582
				} else {
583
					return this.uninstallExtensions(extension, [], installed);
584 585
				}
			})
586
			.then(() => this.postUninstallExtension(extension),
M
Matt Bierner 已提交
587
				error => {
588
					this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
M
Matt Bierner 已提交
589 590
					return TPromise.wrapError(error);
				});
S
Sandeep Somavarapu 已提交
591 592
	}

593
	private promptForDependenciesAndUninstall(extension: ILocalExtension, dependencies: ILocalExtension[], installed: ILocalExtension[]): TPromise<void> {
S
Sandeep Somavarapu 已提交
594
		const message = nls.localize('uninstallDependeciesConfirmation', "Also uninstall the dependencies of the extension '{0}'?", extension.manifest.displayName || extension.manifest.name);
595
		const buttons = [
S
Sandeep Somavarapu 已提交
596 597
			nls.localize('yes', "Yes"),
			nls.localize('no', "No"),
S
Sandeep Somavarapu 已提交
598 599
			nls.localize('cancel', "Cancel")
		];
600
		return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 2 })
S
Sandeep Somavarapu 已提交
601 602
			.then<void>(value => {
				if (value === 0) {
S
Sandeep Somavarapu 已提交
603
					return this.uninstallExtensions(extension, dependencies, installed);
S
Sandeep Somavarapu 已提交
604 605
				}
				if (value === 1) {
S
Sandeep Somavarapu 已提交
606
					return this.uninstallExtensions(extension, [], installed);
S
Sandeep Somavarapu 已提交
607
				}
608
				this.logService.info('Cancelled uninstalling extension:', extension.identifier.id);
S
Sandeep Somavarapu 已提交
609 610 611 612
				return TPromise.wrapError(errors.canceled());
			}, error => TPromise.wrapError(errors.canceled()));
	}

613 614 615 616 617 618
	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)));
619 620
			}
		}
621
		return TPromise.join([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => null);
622 623
	}

S
Sandeep Somavarapu 已提交
624 625 626 627 628 629 630 631 632 633 634 635 636
	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);
	}

637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
	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;
	}

653
	private getAllDependenciesToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
654 655 656 657 658 659 660
		if (checked.indexOf(extension) !== -1) {
			return [];
		}
		checked.push(extension);
		if (!extension.manifest.extensionDependencies || extension.manifest.extensionDependencies.length === 0) {
			return [];
		}
661
		const dependenciesToUninstall = installed.filter(i => extension.manifest.extensionDependencies.some(id => areSameExtensions({ id }, i.galleryIdentifier)));
662 663
		const depsOfDeps = [];
		for (const dep of dependenciesToUninstall) {
664
			depsOfDeps.push(...this.getAllDependenciesToUninstall(dep, installed, checked));
665 666 667 668
		}
		return [...dependenciesToUninstall, ...depsOfDeps];
	}

669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
	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[] {
686
		return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.galleryIdentifier)));
687 688
	}

J
Joao Moreno 已提交
689 690
	private doUninstall(extension: ILocalExtension): TPromise<void> {
		return this.preUninstallExtension(extension)
S
Sandeep Somavarapu 已提交
691
			.then(() => this.uninstallExtension(extension))
692
			.then(() => this.postUninstallExtension(extension),
M
Matt Bierner 已提交
693
				error => {
694
					this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
M
Matt Bierner 已提交
695 696
					return TPromise.wrapError(error);
				});
S
Sandeep Somavarapu 已提交
697
	}
E
Erich Gamma 已提交
698

J
Joao Moreno 已提交
699
	private preUninstallExtension(extension: ILocalExtension): TPromise<void> {
S
Sandeep Somavarapu 已提交
700
		return pfs.exists(extension.location.fsPath)
701
			.then(exists => exists ? null : TPromise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
702
			.then(() => {
703
				this.logService.info('Uninstalling extension:', extension.identifier.id);
704 705
				this._onUninstallExtension.fire(extension.identifier);
			});
S
Sandeep Somavarapu 已提交
706 707
	}

S
Sandeep Somavarapu 已提交
708
	private uninstallExtension(local: ILocalExtension): TPromise<void> {
S
Sandeep Somavarapu 已提交
709 710 711 712 713 714 715 716 717 718
		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 已提交
719 720
	}

J
Joao Moreno 已提交
721
	private async postUninstallExtension(extension: ILocalExtension, error?: Error): Promise<void> {
722
		if (error) {
723
			this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
S
Sandeep Somavarapu 已提交
724 725
		} else {
			this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
726 727 728 729
			// 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);
			}
730
		}
731 732 733
		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 已提交
734 735
	}

J
Joao Moreno 已提交
736 737 738 739
	getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {
		const promises = [];

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

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

747
		return TPromise.join<ILocalExtension[]>(promises).then(flatten, errors => TPromise.wrapError<ILocalExtension[]>(this.joinErrors(errors)));
J
Joao Moreno 已提交
748 749 750
	}

	private scanSystemExtensions(): TPromise<ILocalExtension[]> {
751
		this.logService.trace('Started scanning system extensions');
752 753
		return this.scanExtensions(SystemExtensionsRoot, LocalExtensionType.System)
			.then(result => {
754
				this.logService.info('Scanned system extensions:', result.length);
755 756
				return result;
			});
J
Joao Moreno 已提交
757 758
	}

S
Sandeep Somavarapu 已提交
759
	private scanUserExtensions(excludeOutdated: boolean): TPromise<ILocalExtension[]> {
760
		this.logService.trace('Started scanning user extensions');
S
Sandeep Somavarapu 已提交
761 762 763
		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 已提交
764 765
				if (excludeOutdated) {
					const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid }));
S
Sandeep Somavarapu 已提交
766
					extensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
S
Sandeep Somavarapu 已提交
767
				}
S
Sandeep Somavarapu 已提交
768
				this.logService.info('Scanned user extensions:', extensions.length);
S
Sandeep Somavarapu 已提交
769 770
				return extensions;
			});
J
Joao Moreno 已提交
771 772
	}

J
Joao Moreno 已提交
773
	private scanExtensions(root: string, type: LocalExtensionType): TPromise<ILocalExtension[]> {
E
Erich Gamma 已提交
774
		const limiter = new Limiter(10);
S
Sandeep Somavarapu 已提交
775
		return pfs.readdir(root)
S
Sandeep Somavarapu 已提交
776 777
			.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 已提交
778
	}
E
Erich Gamma 已提交
779

S
Sandeep Somavarapu 已提交
780
	private scanExtension(folderName: string, root: string, type: LocalExtensionType): TPromise<ILocalExtension> {
781 782 783
		if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user exension folder starting with `.`
			return TPromise.as(null);
		}
S
Sandeep Somavarapu 已提交
784 785 786 787
		const extensionPath = path.join(root, folderName);
		return pfs.readdir(extensionPath)
			.then(children => readManifest(extensionPath)
				.then<ILocalExtension>(({ manifest, metadata }) => {
J
Joao Moreno 已提交
788 789 790 791
					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;
792 793 794 795 796 797
					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 已提交
798
					const identifier = { id: type === LocalExtensionType.System ? folderName : getLocalExtensionIdFromManifest(manifest), uuid: metadata ? metadata.id : null };
S
Sandeep Somavarapu 已提交
799 800
					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 已提交
801 802
				}))
			.then(null, () => null);
E
Erich Gamma 已提交
803 804
	}

J
Joao Moreno 已提交
805
	removeDeprecatedExtensions(): TPromise<any> {
S
Sandeep Somavarapu 已提交
806 807 808 809 810
		return this.removeUninstalledExtensions()
			.then(() => this.removeOutdatedExtensions());
	}

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

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

S
Sandeep Somavarapu 已提交
825 826 827 828
				// 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 已提交
829
				return TPromise.join(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
S
Sandeep Somavarapu 已提交
830 831 832 833
			}).then(() => null);
	}

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

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

844 845
	private isUninstalled(id: string): TPromise<boolean> {
		return this.filterUninstalled(id).then(uninstalled => uninstalled.length === 1);
846 847
	}

848 849 850
	private filterUninstalled(...ids: string[]): TPromise<string[]> {
		return this.withUninstalledExtensions(allUninstalled => {
			const uninstalled = [];
851
			for (const id of ids) {
852 853
				if (!!allUninstalled[id]) {
					uninstalled.push(id);
854 855
				}
			}
856
			return uninstalled;
857
		});
858 859
	}

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

865 866
	private unsetUninstalled(id: string): TPromise<void> {
		return this.withUninstalledExtensions<void>(uninstalled => delete uninstalled[id]);
867 868
	}

869 870
	private getUninstalledExtensions(): TPromise<{ [id: string]: boolean; }> {
		return this.withUninstalledExtensions(uninstalled => uninstalled);
871 872
	}

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

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

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

900
		return this.reportedExtensions;
J
Joao Moreno 已提交
901 902
	}

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

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

S
Sandeep Somavarapu 已提交
916 917 918 919
	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'));
	}

920 921 922 923 924 925 926
	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" },
927
				"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942
				"${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}"
				]
			}
		*/
943 944 945 946 947 948 949 950 951 952
		/* __GDPR__
			"extensionGallery:update" : {
				"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
				"${include}": [
					"${GalleryExtensionTelemetryData}"
				]
			}
		*/
953 954
		this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
	}
E
Erich Gamma 已提交
955
}
S
Sandeep Somavarapu 已提交
956 957 958 959 960 961 962

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