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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
96 97 98
interface InstallableExtension {
	zipPath: string;
	id: string;
S
Sandeep Somavarapu 已提交
99
	metadata?: IGalleryMetadata;
S
Sandeep Somavarapu 已提交
100 101
}

102
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
E
Erich Gamma 已提交
103

104
	_serviceBrand: any;
E
Erich Gamma 已提交
105

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

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

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

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

S
Sandeep Somavarapu 已提交
126 127
	private _onDidUninstallExtension = new Emitter<DidUninstallExtensionEvent>();
	onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
E
Erich Gamma 已提交
128 129

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

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

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

S
Sandeep Somavarapu 已提交
159
	unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
S
Sandeep Somavarapu 已提交
160
		this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString());
161
		return this.install(zipLocation, type);
162 163
	}

S
Sandeep Somavarapu 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
	private collectFiles(extension: ILocalExtension): Promise<IFile[]> {

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

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

188 189
	}

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

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

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

S
Sandeep Somavarapu 已提交
244
	private removeIfExists(id: string): Promise<void> {
S
Sandeep Somavarapu 已提交
245 246 247
		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 已提交
248
	}
249

S
Sandeep Somavarapu 已提交
250
	private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
251
		return this.toNonCancellablePromise(this.getInstalled()
S
Sandeep Somavarapu 已提交
252 253
			.then(installed => {
				const operation = this.getOperation({ id: getIdFromLocalExtensionId(identifier.id), uuid: identifier.uuid }, installed);
254
				return this.installExtension({ zipPath, id: identifier.id, metadata }, type, token)
S
Sandeep Somavarapu 已提交
255
					.then(local => this.installDependenciesAndPackExtensions(local, null).then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
S
Sandeep Somavarapu 已提交
256 257
					.then(
						local => { this._onDidInstallExtension.fire({ identifier, zipPath, local, operation }); return local; },
S
Sandeep Somavarapu 已提交
258
						error => { this._onDidInstallExtension.fire({ identifier, zipPath, operation, error }); return Promise.reject(error); }
S
Sandeep Somavarapu 已提交
259
					);
S
Sandeep Somavarapu 已提交
260
			}));
E
Erich Gamma 已提交
261 262
	}

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
	async installFromGallery(extension: IGalleryExtension): Promise<void> {
		const startTime = new Date().getTime();

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

		const onDidInstallExtensionSuccess = (extension: IGalleryExtension, operation: InstallOperation, local: ILocalExtension) => {
			this.logService.info(`Extensions installed successfully:`, extension.identifier.id);
			this._onDidInstallExtension.fire({ identifier: { id: getLocalExtensionIdFromGallery(extension, extension.version), uuid: extension.identifier.uuid }, gallery: extension, local, operation });
			this.reportTelemetry(this.getTelemetryEvent(operation), getGalleryExtensionTelemetryData(extension), new Date().getTime() - startTime, void 0);
		};

		const onDidInstallExtensionFailure = (extension: IGalleryExtension, operation: InstallOperation, error) => {
			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: extension.identifier, gallery: extension, operation, error: errorCode });
			this.reportTelemetry(this.getTelemetryEvent(operation), getGalleryExtensionTelemetryData(extension), new Date().getTime() - startTime, error);
			if (error instanceof Error) {
				error.name = errorCode;
			}
		};

		try {
			extension = await this.checkAndGetCompatibleVersion(extension);
		} catch (error) {
			onDidInstallExtensionFailure(extension, InstallOperation.Install, error);
			return Promise.reject(error);
		}

		const key = getLocalExtensionId(extension.identifier.id, extension.version);
		let cancellablePromise = this.installingExtensions.get(key);
294
		if (!cancellablePromise) {
295

296
			let operation: InstallOperation = InstallOperation.Install;
297
			let cancellationToken: CancellationToken, successCallback: ValueCallback<void>, errorCallback: ErrorCallback;
S
Sandeep Somavarapu 已提交
298
			cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); });
299
			this.installingExtensions.set(key, cancellablePromise);
300 301

			try {
302 303 304 305 306 307 308 309 310 311 312 313 314 315
				const installed = await this.getInstalled(LocalExtensionType.User);
				const existingExtension = installed.filter(i => areSameExtensions(i.galleryIdentifier, extension.identifier))[0];
				if (existingExtension) {
					operation = InstallOperation.Update;
					if (semver.gt(existingExtension.manifest.version, extension.version)) {
						await this.uninstall(existingExtension, true);
					}
				}

				this.downloadInstallableExtension(extension, operation)
					.then(installableExtension => this.installExtension(installableExtension, LocalExtensionType.User, cancellationToken)
						.then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local)))
					.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
						.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
316 317
					.then(
						local => {
318 319
							this.installingExtensions.delete(key);
							onDidInstallExtensionSuccess(extension, operation, local);
320 321 322
							successCallback(null);
						},
						error => {
323 324
							this.installingExtensions.delete(key);
							onDidInstallExtensionFailure(extension, operation, error);
325 326 327 328
							errorCallback(error);
						});

			} catch (error) {
329 330
				this.installingExtensions.delete(key);
				onDidInstallExtensionFailure(extension, operation, error);
331 332 333 334
				errorCallback(error);
			}

		}
335

S
Sandeep Somavarapu 已提交
336
		return cancellablePromise;
S
Sandeep Somavarapu 已提交
337 338
	}

339 340 341 342 343 344 345 346 347 348 349 350 351 352
	private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise<IGalleryExtension> {
		if (await this.isMalicious(extension)) {
			return Promise.reject(new ExtensionManagementError(nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."), INSTALL_ERROR_MALICIOUS));
		}

		extension = await this.galleryService.loadCompatibleVersion(extension);

		if (!extension) {
			return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
		}

		return extension;
	}

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

S
Sandeep Somavarapu 已提交
371 372
	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 已提交
373 374
	}

375 376
	private getTelemetryEvent(operation: InstallOperation): string {
		return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
S
Sandeep Somavarapu 已提交
377 378
	}

379
	private isMalicious(extension: IGalleryExtension): Promise<boolean> {
380
		return this.getExtensionsReport()
381
			.then(report => getMaliciousExtensionsSet(report).has(extension.identifier.id));
S
Sandeep Somavarapu 已提交
382 383
	}

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

391 392
		this.logService.trace('Started downloading extension:', extension.name);
		return this.galleryService.download(extension, operation)
S
Sandeep Somavarapu 已提交
393
			.then(
394 395 396 397 398 399 400
				zipPath => {
					this.logService.info('Downloaded extension:', extension.name, zipPath);
					return getManifest(zipPath)
						.then(
							manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
							error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
						);
M
Matt Bierner 已提交
401
				},
402
				error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
S
Sandeep Somavarapu 已提交
403 404
	}

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

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

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

S
Sandeep Somavarapu 已提交
456
	private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string, token: CancellationToken): Promise<void> {
457
		return this.extract(id, zipPath, extractPath, token)
S
Sandeep Somavarapu 已提交
458
			.then(() => this.rename(id, extractPath, renamePath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */)
S
Sandeep Somavarapu 已提交
459 460 461 462
				.then(
					() => this.logService.info('Renamed to', renamePath),
					e => {
						this.logService.info('Rename failed. Deleting from extracted location', extractPath);
S
Sandeep Somavarapu 已提交
463
						return always(pfs.rimraf(extractPath), () => null).then(() => Promise.reject(e));
S
Sandeep Somavarapu 已提交
464 465 466
					}));
	}

S
Sandeep Somavarapu 已提交
467
	private extract(id: string, zipPath: string, extractPath: string, token: CancellationToken): Promise<void> {
468 469 470
		this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
		return pfs.rimraf(extractPath)
			.then(
471
				() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService, token)
M
Matt Bierner 已提交
472 473 474
					.then(
						() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
						e => always(pfs.rimraf(extractPath), () => null)
S
Sandeep Somavarapu 已提交
475 476
							.then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))),
				e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
477 478
	}

S
Sandeep Somavarapu 已提交
479
	private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
480
		return pfs.rename(extractPath, renamePath)
S
Sandeep Somavarapu 已提交
481 482 483 484 485
			.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 已提交
486
				return Promise.reject(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING));
S
Sandeep Somavarapu 已提交
487
			});
S
Sandeep Somavarapu 已提交
488 489
	}

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

S
Sandeep Somavarapu 已提交
523
	private rollback(extensions: IGalleryExtension[]): Promise<void> {
S
Sandeep Somavarapu 已提交
524 525
		return this.getInstalled(LocalExtensionType.User)
			.then(installed =>
S
Sandeep Somavarapu 已提交
526
				Promise.all(installed.filter(local => extensions.some(galleryExtension => local.identifier.id === getLocalExtensionIdFromGallery(galleryExtension, galleryExtension.version))) // Only check id (pub.name-version) because we want to rollback the exact version
527
					.map(local => this.uninstall(local, true))))
S
Sandeep Somavarapu 已提交
528 529 530
			.then(() => null, () => null);
	}

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

S
Sandeep Somavarapu 已提交
546
	updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
S
Sandeep Somavarapu 已提交
547
		this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);
548
		local.metadata = metadata;
549 550 551 552 553
		return this.saveMetadataForLocalExtension(local)
			.then(localExtension => {
				this.manifestCache.invalidate();
				return localExtension;
			});
554 555
	}

S
Sandeep Somavarapu 已提交
556
	private saveMetadataForLocalExtension(local: ILocalExtension): Promise<ILocalExtension> {
557
		if (!local.metadata) {
S
Sandeep Somavarapu 已提交
558
			return Promise.resolve(local);
559 560 561 562 563 564 565 566 567
		}
		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 已提交
568
	private getMetadata(extensionName: string): Promise<IGalleryMetadata> {
S
Sandeep Somavarapu 已提交
569 570 571 572
		return this.findGalleryExtensionByName(extensionName)
			.then(galleryExtension => galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : null);
	}

S
Sandeep Somavarapu 已提交
573
	private findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
S
Sandeep Somavarapu 已提交
574 575 576 577 578 579 580
		if (local.identifier.uuid) {
			return this.findGalleryExtensionById(local.identifier.uuid)
				.then(galleryExtension => galleryExtension ? galleryExtension : this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local)));
		}
		return this.findGalleryExtensionByName(getGalleryExtensionIdFromLocal(local));
	}

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

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

589 590
	private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error {
		const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
S
Sandeep Somavarapu 已提交
591 592 593 594
		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 已提交
595
			return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
S
Sandeep Somavarapu 已提交
596
		}, new Error(''));
J
Joao Moreno 已提交
597 598
	}

S
Sandeep Somavarapu 已提交
599
	private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
J
Joao Moreno 已提交
600
		return this.preUninstallExtension(extension)
601
			.then(() => {
602 603 604
				const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
				if (packedExtensions.length) {
					return this.uninstallExtensions(extension, packedExtensions, installed);
S
Sandeep Somavarapu 已提交
605
				}
S
Sandeep Somavarapu 已提交
606
				return this.uninstallExtensions(extension, [], installed);
607
			})
608
			.then(() => this.postUninstallExtension(extension),
M
Matt Bierner 已提交
609
				error => {
610
					this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
S
Sandeep Somavarapu 已提交
611
					return Promise.reject(error);
M
Matt Bierner 已提交
612
				});
S
Sandeep Somavarapu 已提交
613 614
	}

S
Sandeep Somavarapu 已提交
615
	private uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
616 617 618 619
		const dependents = this.getDependents(extension, installed);
		if (dependents.length) {
			const remainingDependents = dependents.filter(dependent => extension !== dependent && otherExtensionsToUninstall.indexOf(dependent) === -1);
			if (remainingDependents.length) {
S
Sandeep Somavarapu 已提交
620
				return Promise.reject(new Error(this.getDependentsErrorMessage(extension, remainingDependents)));
621 622
			}
		}
S
Sandeep Somavarapu 已提交
623
		return Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => null);
624 625
	}

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

639 640 641 642 643 644 645 646 647
	private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
		if (checked.indexOf(extension) !== -1) {
			return [];
		}
		checked.push(extension);
		if (!extension.manifest.extensionPack || extension.manifest.extensionPack.length === 0) {
			return [];
		}
		const packedExtensions = installed.filter(i => extension.manifest.extensionPack.some(id => areSameExtensions({ id }, i.galleryIdentifier)));
M
Matt Bierner 已提交
648
		const packOfPackedExtensions: ILocalExtension[] = [];
649 650 651 652 653 654 655
		for (const packedExtension of packedExtensions) {
			packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
		}
		return [...packedExtensions, ...packOfPackedExtensions];
	}

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

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

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

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

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

706
	getInstalled(type: LocalExtensionType | null = null): Promise<ILocalExtension[]> {
J
Joao Moreno 已提交
707 708 709
		const promises = [];

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

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

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

S
Sandeep Somavarapu 已提交
720
	private scanSystemExtensions(): Promise<ILocalExtension[]> {
721
		this.logService.trace('Started scanning system extensions');
722
		const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, LocalExtensionType.System)
723
			.then(result => {
724
				this.logService.info('Scanned system extensions:', result.length);
725 726
				return result;
			});
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
		if (this.environmentService.isBuilt) {
			return systemExtensionsPromise;
		}

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

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

S
Sandeep Somavarapu 已提交
762
	private scanExtensions(root: string, type: LocalExtensionType): Promise<ILocalExtension[]> {
J
Joao Moreno 已提交
763
		const limiter = new Limiter<any>(10);
S
Sandeep Somavarapu 已提交
764
		return pfs.readdir(root)
S
Sandeep Somavarapu 已提交
765
			.then(extensionsFolders => Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
S
Sandeep Somavarapu 已提交
766
			.then(extensions => extensions.filter(e => e && e.identifier));
S
Sandeep Somavarapu 已提交
767
	}
E
Erich Gamma 已提交
768

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

S
Sandeep Somavarapu 已提交
794
	removeDeprecatedExtensions(): Promise<any> {
S
Sandeep Somavarapu 已提交
795 796 797 798
		return this.removeUninstalledExtensions()
			.then(() => this.removeOutdatedExtensions());
	}

S
Sandeep Somavarapu 已提交
799
	private removeUninstalledExtensions(): Promise<void> {
800
		return this.getUninstalledExtensions()
S
Sandeep Somavarapu 已提交
801 802
			.then(uninstalled => this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
				.then(extensions => {
S
Sandeep Somavarapu 已提交
803
					const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[e.identifier.id]);
804
					return Promise.all(toRemove.map(e => this.extensionLifecycle.postUninstall(e).then(() => this.removeUninstalledExtension(e))));
S
Sandeep Somavarapu 已提交
805 806 807
				})
			).then(() => null);
	}
S
Sandeep Somavarapu 已提交
808

S
Sandeep Somavarapu 已提交
809
	private removeOutdatedExtensions(): Promise<void> {
S
Sandeep Somavarapu 已提交
810 811 812
		return this.scanExtensions(this.extensionsPath, LocalExtensionType.User) // All user extensions
			.then(extensions => {
				const toRemove: ILocalExtension[] = [];
S
Sandeep Somavarapu 已提交
813

S
Sandeep Somavarapu 已提交
814 815 816 817
				// 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 已提交
818
				return Promise.all(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
S
Sandeep Somavarapu 已提交
819 820 821
			}).then(() => null);
	}

S
Sandeep Somavarapu 已提交
822
	private removeUninstalledExtension(extension: ILocalExtension): Promise<void> {
823
		return this.removeExtension(extension, 'uninstalled')
S
Sandeep Somavarapu 已提交
824 825 826 827
			.then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[extension.identifier.id]))
			.then(() => null);
	}

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

S
Sandeep Somavarapu 已提交
833
	private isUninstalled(id: string): Promise<boolean> {
834
		return this.filterUninstalled(id).then(uninstalled => uninstalled.length === 1);
835 836
	}

S
Sandeep Somavarapu 已提交
837
	private filterUninstalled(...ids: string[]): Promise<string[]> {
838
		return this.withUninstalledExtensions(allUninstalled => {
M
Matt Bierner 已提交
839
			const uninstalled: string[] = [];
840
			for (const id of ids) {
841 842
				if (!!allUninstalled[id]) {
					uninstalled.push(id);
843 844
				}
			}
845
			return uninstalled;
846
		});
847 848
	}

M
Matt Bierner 已提交
849
	private setUninstalled(...extensions: ILocalExtension[]): Promise<{ [id: string]: boolean }> {
S
Sandeep Somavarapu 已提交
850
		const ids = extensions.map(e => e.identifier.id);
M
Matt Bierner 已提交
851
		return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {} as { [id: string]: boolean })));
852 853
	}

S
Sandeep Somavarapu 已提交
854
	private unsetUninstalled(id: string): Promise<void> {
855
		return this.withUninstalledExtensions<void>(uninstalled => delete uninstalled[id]);
856 857
	}

S
Sandeep Somavarapu 已提交
858
	private getUninstalledExtensions(): Promise<{ [id: string]: boolean; }> {
859
		return this.withUninstalledExtensions(uninstalled => uninstalled);
860 861
	}

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

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

884 885 886 887
		if (!this.reportedExtensions || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness
			this.reportedExtensions = this.updateReportCache();
			this.lastReportTimestamp = now;
		}
J
Joao Moreno 已提交
888

889
		return this.reportedExtensions;
J
Joao Moreno 已提交
890 891
	}

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

895
		return this.galleryService.getExtensionsReport()
J
Joao Moreno 已提交
896 897
			.then(result => {
				this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
898 899 900 901
				return result;
			}, err => {
				this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
				return [];
J
Joao Moreno 已提交
902 903
			});
	}
904

905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928
	private _devSystemExtensionsPath: string | null = null;
	private get devSystemExtensionsPath(): string {
		if (!this._devSystemExtensionsPath) {
			this._devSystemExtensionsPath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', '.build', 'builtInExtensions'));
		}
		return this._devSystemExtensionsPath;
	}

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

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

S
Sandeep Somavarapu 已提交
929 930
	private toNonCancellablePromise<T>(promise: Promise<T>): Promise<T> {
		return new Promise((c, e) => promise.then(result => c(result), error => e(error)));
S
Sandeep Somavarapu 已提交
931 932
	}

933 934 935 936 937 938 939
	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" },
940
				"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
				"${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}"
				]
			}
		*/
956 957 958 959 960 961 962 963 964 965
		/* __GDPR__
			"extensionGallery:update" : {
				"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
				"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
				"${include}": [
					"${GalleryExtensionTelemetryData}"
				]
			}
		*/
966 967
		this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
	}
E
Erich Gamma 已提交
968
}
S
Sandeep Somavarapu 已提交
969 970 971 972 973 974 975

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