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

6
import * as nls from 'vs/nls';
E
Erich Gamma 已提交
7 8
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
9
import * as errors from 'vs/base/common/errors';
E
Erich Gamma 已提交
10
import { assign } from 'vs/base/common/objects';
11
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
12
import { flatten } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
13
import { extract, buffer, ExtractError, zip, IFile } from 'vs/platform/node/zip';
S
Sandeep Somavarapu 已提交
14
import { ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
J
Johannes Rieken 已提交
15 16
import {
	IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
17
	IGalleryExtension, IExtensionManifest, IGalleryMetadata,
J
Joao Moreno 已提交
18
	InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
S
Sandeep Somavarapu 已提交
19
	StatisticType,
J
Joao Moreno 已提交
20
	IExtensionIdentifier,
21
	IReportedExtension,
22
	InstallOperation
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';
27
import { Limiter, always, createCancelablePromise, CancelablePromise } 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';
35
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
36
import Severity from 'vs/base/common/severity';
S
Sandeep Somavarapu 已提交
37
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
38
import { toErrorMessage } from 'vs/base/common/errorMessage';
39
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
S
Sandeep Somavarapu 已提交
40
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
41 42 43
import { tmpdir } from 'os';
import { generateUuid } from 'vs/base/common/uuid';
import { IDownloadService } from 'vs/platform/download/common/download';
44
import { optional } from 'vs/platform/instantiation/common/instantiation';
45
import { Schemas } from 'vs/base/common/network';
46
import { CancellationToken } from 'vs/base/common/cancellation';
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 53
const INSTALL_ERROR_INCOMPATIBLE = 'incompatible';
const INSTALL_ERROR_DOWNLOADING = 'downloading';
const INSTALL_ERROR_VALIDATING = 'validating';
54 55
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
56
const INSTALL_ERROR_EXTRACTING = 'extracting';
S
Sandeep Somavarapu 已提交
57
const INSTALL_ERROR_RENAMING = 'renaming';
58
const INSTALL_ERROR_DELETING = 'deleting';
59
const INSTALL_ERROR_MALICIOUS = 'malicious';
60
const ERROR_UNKNOWN = 'unknown';
S
Sandeep Somavarapu 已提交
61

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

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

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

S
Sandeep Somavarapu 已提交
87
function readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
88 89 90 91
	const promises = [
		pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8')
			.then(raw => parseManifest(raw)),
		pfs.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
S
Sandeep Somavarapu 已提交
92
			.then(null, err => err.code !== 'ENOENT' ? Promise.reject<string>(err) : '{}')
93 94 95
			.then(raw => JSON.parse(raw))
	];

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

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

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

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

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

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
	private collectFiles(extension: ILocalExtension): Promise<IFile[]> {

		const collectFilesFromDirectory = async (dir): Promise<string[]> => {
			let entries = await pfs.readdir(dir);
			entries = entries.map(e => path.join(dir, e));
			const stats = await Promise.all(entries.map(e => pfs.stat(e)));
			let promise: Promise<string[]> = Promise.resolve([]);
			stats.forEach((stat, index) => {
				const entry = entries[index];
				if (stat.isFile()) {
					promise = promise.then(result => ([...result, entry]));
				}
				if (stat.isDirectory()) {
					promise = promise
						.then(result => collectFilesFromDirectory(entry)
							.then(files => ([...result, ...files])));
189 190
				}
			});
S
Sandeep Somavarapu 已提交
191 192 193 194 195 196
			return promise;
		};

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

197 198
	}

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

					return validateLocalExtension(zipPath)
						.then(manifest => {
							const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
							if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
S
Sandeep Somavarapu 已提交
210
								return Promise.reject(new Error(nls.localize('incompatible', "Unable to install Extension '{0}' as it is not compatible with Code '{1}'.", identifier.id, pkg.version)));
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
							}
							return this.removeIfExists(identifier.id)
								.then(
									() => this.checkOutdated(manifest)
										.then(validated => {
											if (validated) {
												this.logService.info('Installing the extension:', identifier.id);
												this._onInstallExtension.fire({ identifier, zipPath });
												return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name))
													.then(
														metadata => this.installFromZipPath(identifier, zipPath, metadata, type, token),
														error => this.installFromZipPath(identifier, zipPath, null, type, token))
													.then(
														() => { this.logService.info('Successfully installed the extension:', identifier.id); return identifier; },
														e => {
															this.logService.error('Failed to install the extension:', identifier.id, e.message);
S
Sandeep Somavarapu 已提交
227
															return Promise.reject(e);
228 229 230 231
														});
											}
											return null;
										}),
S
Sandeep Somavarapu 已提交
232
									e => Promise.reject(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name))));
233 234
						});
				});
S
Sandeep Somavarapu 已提交
235
		});
236 237
	}

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

S
Sandeep Somavarapu 已提交
249
	private removeIfExists(id: string): Promise<void> {
S
Sandeep Somavarapu 已提交
250 251 252
		return this.getInstalled(LocalExtensionType.User)
			.then(installed => installed.filter(i => i.identifier.id === id)[0])
			.then(existing => existing ? this.removeExtension(existing, 'existing') : null);
S
Sandeep Somavarapu 已提交
253
	}
254

S
Sandeep Somavarapu 已提交
255
	private checkOutdated(manifest: IExtensionManifest): Promise<boolean> {
S
Sandeep Somavarapu 已提交
256
		const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
257
		return this.getInstalled(LocalExtensionType.User)
S
Sandeep Somavarapu 已提交
258 259 260 261
			.then(installedExtensions => {
				const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
				if (newer) {
					const message = nls.localize('installingOutdatedExtension', "A newer version of this extension is already installed. Would you like to override this with the older version?");
262
					const buttons = [
S
Sandeep Somavarapu 已提交
263 264 265
						nls.localize('override', "Override"),
						nls.localize('cancel', "Cancel")
					];
266
					return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
S
Sandeep Somavarapu 已提交
267 268 269 270
						.then<boolean>(value => {
							if (value === 0) {
								return this.uninstall(newer, true).then(() => true);
							}
S
Sandeep Somavarapu 已提交
271
							return Promise.reject(errors.canceled());
S
Sandeep Somavarapu 已提交
272 273 274
						});
				}
				return true;
275
			});
S
Sandeep Somavarapu 已提交
276 277
	}

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

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

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

			try {
				const startTime = new Date().getTime();
				const identifier = { id: getLocalExtensionIdFromGallery(extension, extension.version), uuid: extension.identifier.uuid };
				const telemetryData = getGalleryExtensionTelemetryData(extension);
				let operation: InstallOperation = InstallOperation.Install;

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

				this.checkMalicious(extension)
					.then(() => this.getInstalled(LocalExtensionType.User))
					.then(installed => {
						const existingExtension = installed.filter(i => areSameExtensions(i.galleryIdentifier, extension.identifier))[0];
						operation = existingExtension ? InstallOperation.Update : InstallOperation.Install;
						return this.downloadInstallableExtension(extension, operation)
315
							.then(installableExtension => this.installExtension(installableExtension, LocalExtensionType.User, cancellationToken).then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local)))
316
							.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
S
Sandeep Somavarapu 已提交
317
								.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))));
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
					})
					.then(
						local => {
							this.installingExtensions.delete(extension.identifier.id);
							this.logService.info(`Extensions installed successfully:`, extension.identifier.id);
							this._onDidInstallExtension.fire({ identifier, gallery: extension, local, operation });
							this.reportTelemetry(this.getTelemetryEvent(operation), telemetryData, new Date().getTime() - startTime, void 0);
							successCallback(null);
						},
						error => {
							this.installingExtensions.delete(extension.identifier.id);
							const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
							this.logService.error(`Failed to install extension:`, extension.identifier.id, error ? error.message : errorCode);
							this._onDidInstallExtension.fire({ identifier, gallery: extension, operation, error: errorCode });
							this.reportTelemetry(this.getTelemetryEvent(operation), telemetryData, new Date().getTime() - startTime, error);
							errorCallback(error);
						});

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

		}
342

S
Sandeep Somavarapu 已提交
343
		return cancellablePromise;
S
Sandeep Somavarapu 已提交
344 345
	}

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

S
Sandeep Somavarapu 已提交
364 365
	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 已提交
366 367
	}

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

S
Sandeep Somavarapu 已提交
372
	private checkMalicious(extension: IGalleryExtension): Promise<void> {
373 374 375 376 377 378 379 380
		return this.getExtensionsReport()
			.then(report => {
				if (getMaliciousExtensionsSet(report).has(extension.identifier.id)) {
					throw new ExtensionManagementError(INSTALL_ERROR_MALICIOUS, nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."));
				} else {
					return null;
				}
			});
S
Sandeep Somavarapu 已提交
381 382
	}

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

		return this.galleryService.loadCompatibleVersion(extension)
			.then(
M
Matt Bierner 已提交
392 393 394
				compatible => {
					if (compatible) {
						this.logService.trace('Started downloading extension:', extension.name);
395
						return this.galleryService.download(compatible, operation)
M
Matt Bierner 已提交
396 397
							.then(
								zipPath => {
S
Sandeep Somavarapu 已提交
398
									this.logService.info('Downloaded extension:', extension.name, zipPath);
M
Matt Bierner 已提交
399 400 401
									return validateLocalExtension(zipPath)
										.then(
											manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
S
Sandeep Somavarapu 已提交
402
											error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
M
Matt Bierner 已提交
403 404
										);
								},
S
Sandeep Somavarapu 已提交
405
								error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
M
Matt Bierner 已提交
406
					} else {
S
Sandeep Somavarapu 已提交
407
						return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
M
Matt Bierner 已提交
408 409
					}
				},
S
Sandeep Somavarapu 已提交
410
				error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
S
Sandeep Somavarapu 已提交
411 412
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
	private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {
		if (checked.indexOf(extension) !== -1) {
			return [];
		}
		checked.push(extension);
		if (!extension.manifest.extensionPack || extension.manifest.extensionPack.length === 0) {
			return [];
		}
		const packedExtensions = installed.filter(i => extension.manifest.extensionPack.some(id => areSameExtensions({ id }, i.galleryIdentifier)));
		const packOfPackedExtensions = [];
		for (const packedExtension of packedExtensions) {
			packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
		}
		return [...packedExtensions, ...packOfPackedExtensions];
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

880
		return this.reportedExtensions;
J
Joao Moreno 已提交
881 882
	}

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

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

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

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

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