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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
	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])));
179 180
				}
			});
S
Sandeep Somavarapu 已提交
181 182 183 184 185 186
			return promise;
		};

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

187 188
	}

S
Sandeep Somavarapu 已提交
189
	install(vsix: URI, type: LocalExtensionType = LocalExtensionType.User): Promise<IExtensionIdentifier> {
S
Sandeep Somavarapu 已提交
190
		this.logService.trace('ExtensionManagementService#install', vsix.toString());
S
Sandeep Somavarapu 已提交
191
		return createCancelablePromise(token => {
M
Matt Bierner 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
			return this.downloadVsix(vsix).then(downloadLocation => {
				const zipPath = path.resolve(downloadLocation.fsPath);

				return getManifest(zipPath)
					.then(manifest => {
						const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
						if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
							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)));
						}
						return this.removeIfExists(identifier.id).then(
							() => {
								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];
R
Rob Lourens 已提交
207
										return newer ? this.uninstall(newer, true) : undefined;
M
Matt Bierner 已提交
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
									})
									.then(() => {
										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);
													return Promise.reject(e);
												});
									});
							},
							e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name))));
					});
			});
S
Sandeep Somavarapu 已提交
227
		});
228 229
	}

S
Sandeep Somavarapu 已提交
230
	private downloadVsix(vsix: URI): Promise<URI> {
231
		if (vsix.scheme === Schemas.file) {
S
Sandeep Somavarapu 已提交
232
			return Promise.resolve(vsix);
233 234 235 236 237 238 239 240
		}
		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 已提交
241
	private removeIfExists(id: string): Promise<void> {
S
Sandeep Somavarapu 已提交
242 243
		return this.getInstalled(LocalExtensionType.User)
			.then(installed => installed.filter(i => i.identifier.id === id)[0])
R
Rob Lourens 已提交
244
			.then(existing => existing ? this.removeExtension(existing, 'existing') : undefined);
S
Sandeep Somavarapu 已提交
245
	}
246

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

260 261 262 263 264 265 266 267 268
	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 });
R
Rob Lourens 已提交
269
			this.reportTelemetry(this.getTelemetryEvent(operation), getGalleryExtensionTelemetryData(extension), new Date().getTime() - startTime, undefined);
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
		};

		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);
291
		if (!cancellablePromise) {
292

293

294
			let operation: InstallOperation = InstallOperation.Install;
295
			let cancellationToken: CancellationToken, successCallback: (a?: any) => void, errorCallback: (e?: any) => any | null;
S
Sandeep Somavarapu 已提交
296
			cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); });
297
			this.installingExtensions.set(key, cancellablePromise);
298
			try {
299 300 301 302 303 304 305 306 307 308 309 310 311 312
				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))))
313 314
					.then(
						local => {
315 316
							this.installingExtensions.delete(key);
							onDidInstallExtensionSuccess(extension, operation, local);
317 318 319
							successCallback(null);
						},
						error => {
320 321
							this.installingExtensions.delete(key);
							onDidInstallExtensionFailure(extension, operation, error);
322 323 324 325
							errorCallback(error);
						});

			} catch (error) {
326 327
				this.installingExtensions.delete(key);
				onDidInstallExtensionFailure(extension, operation, error);
328
				return Promise.reject(error);
329 330 331
			}

		}
332

S
Sandeep Somavarapu 已提交
333
		return cancellablePromise;
S
Sandeep Somavarapu 已提交
334 335
	}

336 337 338 339 340
	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));
		}

341
		const compatibleExtension = await this.galleryService.loadCompatibleVersion(extension);
342

343
		if (!compatibleExtension) {
344 345 346
			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));
		}

347
		return compatibleExtension;
348 349
	}

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

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

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

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

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

388 389
		this.logService.trace('Started downloading extension:', extension.name);
		return this.galleryService.download(extension, operation)
S
Sandeep Somavarapu 已提交
390
			.then(
391 392 393 394 395 396 397
				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 已提交
398
				},
399
				error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
S
Sandeep Somavarapu 已提交
400 401
	}

S
Sandeep Somavarapu 已提交
402
	private installExtension(installableExtension: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
403 404
		return this.unsetUninstalledAndGetLocal(installableExtension.id)
			.then(
M
Matt Bierner 已提交
405 406 407 408
				local => {
					if (local) {
						return local;
					}
409
					return this.extractAndInstall(installableExtension, type, token);
M
Matt Bierner 已提交
410 411 412
				},
				e => {
					if (isMacintosh) {
S
Sandeep Somavarapu 已提交
413
						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 已提交
414
					}
S
Sandeep Somavarapu 已提交
415
					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 已提交
416
				});
417
	}
J
Joao Moreno 已提交
418

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

S
Sandeep Somavarapu 已提交
436
	private extractAndInstall({ zipPath, id, metadata }: InstallableExtension, type: LocalExtensionType, token: CancellationToken): Promise<ILocalExtension> {
437 438 439
		const location = type === LocalExtensionType.User ? this.extensionsPath : this.systemExtensionsPath;
		const tempPath = path.join(location, `.${id}`);
		const extensionPath = path.join(location, id);
S
Sandeep Somavarapu 已提交
440
		return pfs.rimraf(extensionPath)
S
Sandeep Somavarapu 已提交
441
			.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)))
442
			.then(() => this.scanExtension(id, location, type))
S
Sandeep Somavarapu 已提交
443
			.then(local => {
444 445 446
				if (!local) {
					return Promise.reject(nls.localize('cannot read', "Cannot read the extension from {0}", location));
				}
S
Sandeep Somavarapu 已提交
447 448 449 450 451 452 453
				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)
475
							.then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING)))),
S
Sandeep Somavarapu 已提交
476
				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)
R
Rob Lourens 已提交
481
			.then(undefined, error => {
S
Sandeep Somavarapu 已提交
482 483 484 485
				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
	}

490
	private installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | null): 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;
					});
			}
		}
R
Rob Lourens 已提交
520
		return Promise.resolve(undefined);
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))))
R
Rob Lourens 已提交
528
			.then(() => undefined, () => undefined);
S
Sandeep Somavarapu 已提交
529 530
	}

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

568
	private getMetadata(extensionName: string): Promise<IGalleryMetadata | null> {
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
	private joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
590
		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
			}
		}
R
Rob Lourens 已提交
623
		return Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]).then(() => undefined);
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
	private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
		if (checked.indexOf(extension) !== -1) {
			return [];
		}
		checked.push(extension);
644 645 646 647 648 649 650 651
		const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];
		if (extensionsPack.length) {
			const packedExtensions = installed.filter(i => extensionsPack.some(id => areSameExtensions({ id }, i.galleryIdentifier)));
			const packOfPackedExtensions: ILocalExtension[] = [];
			for (const packedExtension of packedExtensions) {
				packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
			}
			return [...packedExtensions, ...packOfPackedExtensions];
652
		}
653
		return [];
654 655 656
	}

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

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

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

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

J
Joao Moreno 已提交
692
	private async postUninstallExtension(extension: ILocalExtension, error?: Error): Promise<void> {
693
		if (error) {
694
			this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
S
Sandeep Somavarapu 已提交
695 696
		} else {
			this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
697 698 699 700
			// 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);
			}
701
		}
R
Rob Lourens 已提交
702 703
		this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error);
		const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
704
		this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode });
E
Erich Gamma 已提交
705 706
	}

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

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

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

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

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

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

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

770
	private scanExtension(folderName: string, root: string, type: LocalExtensionType): Promise<ILocalExtension | null> {
N
Nestor Arias 已提交
771
		if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
S
Sandeep Somavarapu 已提交
772
			return Promise.resolve(null);
773
		}
S
Sandeep Somavarapu 已提交
774 775 776
		const extensionPath = path.join(root, folderName);
		return pfs.readdir(extensionPath)
			.then(children => readManifest(extensionPath)
777
				.then(({ manifest, metadata }) => {
J
Joao Moreno 已提交
778
					const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
779
					const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)) : null;
J
Joao Moreno 已提交
780
					const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
781
					const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)) : null;
782 783 784 785 786 787
					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 已提交
788
					const identifier = { id: type === LocalExtensionType.System ? folderName : getLocalExtensionIdFromManifest(manifest), uuid: metadata ? metadata.id : null };
S
Sandeep Somavarapu 已提交
789
					const galleryIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: identifier.uuid };
790
					return <ILocalExtension>{ type, identifier, galleryIdentifier, manifest, metadata, location: URI.file(extensionPath), readmeUrl, changelogUrl };
S
Sandeep Somavarapu 已提交
791
				}))
R
Rob Lourens 已提交
792
			.then(undefined, () => null);
E
Erich Gamma 已提交
793 794
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

896
		return this.galleryService.getExtensionsReport()
J
Joao Moreno 已提交
897 898
			.then(result => {
				this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
899 900 901 902
				return result;
			}, err => {
				this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
				return [];
J
Joao Moreno 已提交
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 929
	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 已提交
930 931
	private toNonCancellablePromise<T>(promise: Promise<T>): Promise<T> {
		return new Promise((c, e) => promise.then(result => c(result), error => e(error)));
S
Sandeep Somavarapu 已提交
932 933
	}

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

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