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

'use strict';

import nls = require('vs/nls');
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
11
import * as errors from 'vs/base/common/errors';
E
Erich Gamma 已提交
12
import { assign } from 'vs/base/common/objects';
13
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
14
import { flatten, distinct } from 'vs/base/common/arrays';
E
Erich Gamma 已提交
15
import { extract, buffer } from 'vs/base/node/zip';
16
import { TPromise } from 'vs/base/common/winjs.base';
J
Johannes Rieken 已提交
17 18
import {
	IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
19
	IGalleryExtension, IExtensionManifest, IGalleryMetadata,
J
Joao Moreno 已提交
20
	InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
21
	StatisticType
J
Joao Moreno 已提交
22
} from 'vs/platform/extensionManagement/common/extensionManagement';
S
Sandeep Somavarapu 已提交
23
import { getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest, getGalleryExtensionIdFromLocal, getIdAndVersionFromLocalExtensionId, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
24
import { localizeManifest } from '../common/extensionNls';
J
Joao Moreno 已提交
25
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
E
Erich Gamma 已提交
26
import { Limiter } from 'vs/base/common/async';
J
Joao Moreno 已提交
27
import Event, { Emitter } from 'vs/base/common/event';
J
Joao Moreno 已提交
28
import * as semver from 'semver';
29
import { groupBy, values } from 'vs/base/common/collections';
J
João Moreno 已提交
30
import URI from 'vs/base/common/uri';
31
import { IChoiceService, Severity } from 'vs/platform/message/common/message';
E
Erich Gamma 已提交
32

J
Joao Moreno 已提交
33
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
34 35 36
const INSTALL_ERROR_OBSOLETE = 'obsolete';
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
J
Joao Moreno 已提交
37

J
Joao Moreno 已提交
38
function parseManifest(raw: string): TPromise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
39
	return new TPromise((c, e) => {
E
Erich Gamma 已提交
40
		try {
J
Joao Moreno 已提交
41 42 43 44
			const manifest = JSON.parse(raw);
			const metadata = manifest.__metadata || null;
			delete manifest.__metadata;
			c({ manifest, metadata });
E
Erich Gamma 已提交
45 46 47 48 49 50
		} catch (err) {
			e(new Error(nls.localize('invalidManifest', "Extension invalid: package.json is not a JSON file.")));
		}
	});
}

51
function validate(zipPath: string): TPromise<IExtensionManifest> {
E
Erich Gamma 已提交
52 53
	return buffer(zipPath, 'extension/package.json')
		.then(buffer => parseManifest(buffer.toString('utf8')))
54
		.then(({ manifest }) => TPromise.as(manifest));
E
Erich Gamma 已提交
55 56
}

57 58 59 60 61
function readManifest(extensionPath: string): TPromise<{ manifest: IExtensionManifest; metadata: IGalleryMetadata; }> {
	const promises = [
		pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8')
			.then(raw => parseManifest(raw)),
		pfs.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
R
Ron Buckton 已提交
62
			.then(null, err => err.code !== 'ENOENT' ? TPromise.wrapError<string>(err) : '{}')
63 64 65 66 67 68 69 70 71 72 73
			.then(raw => JSON.parse(raw))
	];

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

S
Sandeep Somavarapu 已提交
74 75 76 77 78 79
interface InstallableExtension {
	zipPath: string;
	id: string;
	metadata: IGalleryMetadata;
}

J
Joao Moreno 已提交
80
export class ExtensionManagementService implements IExtensionManagementService {
E
Erich Gamma 已提交
81

82
	_serviceBrand: any;
E
Erich Gamma 已提交
83 84

	private extensionsPath: string;
85 86
	private obsoletePath: string;
	private obsoleteFileLimiter: Limiter<void>;
87
	private disposables: IDisposable[] = [];
E
Erich Gamma 已提交
88

J
Joao Moreno 已提交
89 90
	private _onInstallExtension = new Emitter<InstallExtensionEvent>();
	onInstallExtension: Event<InstallExtensionEvent> = this._onInstallExtension.event;
E
Erich Gamma 已提交
91

J
Joao Moreno 已提交
92 93
	private _onDidInstallExtension = new Emitter<DidInstallExtensionEvent>();
	onDidInstallExtension: Event<DidInstallExtensionEvent> = this._onDidInstallExtension.event;
E
Erich Gamma 已提交
94

J
Joao Moreno 已提交
95 96
	private _onUninstallExtension = new Emitter<string>();
	onUninstallExtension: Event<string> = this._onUninstallExtension.event;
E
Erich Gamma 已提交
97

S
Sandeep Somavarapu 已提交
98 99
	private _onDidUninstallExtension = new Emitter<DidUninstallExtensionEvent>();
	onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
E
Erich Gamma 已提交
100 101

	constructor(
102
		@IEnvironmentService private environmentService: IEnvironmentService,
103
		@IChoiceService private choiceService: IChoiceService,
104
		@IExtensionGalleryService private galleryService: IExtensionGalleryService
E
Erich Gamma 已提交
105
	) {
J
Joao Moreno 已提交
106
		this.extensionsPath = environmentService.extensionsPath;
107 108
		this.obsoletePath = path.join(this.extensionsPath, '.obsolete');
		this.obsoleteFileLimiter = new Limiter(1);
E
Erich Gamma 已提交
109 110
	}

111
	install(zipPath: string): TPromise<void> {
112 113
		zipPath = path.resolve(zipPath);

114
		return validate(zipPath).then<void>(manifest => {
115
			const id = getLocalExtensionIdFromManifest(manifest);
116

117 118
			return this.isObsolete(id).then(isObsolete => {
				if (isObsolete) {
S
Sandeep Somavarapu 已提交
119
					return TPromise.wrapError(new Error(nls.localize('restartCodeLocal', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name)));
120
				}
J
Joao Moreno 已提交
121

122
				this._onInstallExtension.fire({ id, zipPath });
J
Joao Moreno 已提交
123

S
Sandeep Somavarapu 已提交
124
				return this.installExtension({ zipPath, id, metadata: null })
125
					.then(
J
Johannes Rieken 已提交
126 127
					local => this._onDidInstallExtension.fire({ id, zipPath, local }),
					error => { this._onDidInstallExtension.fire({ id, zipPath, error }); return TPromise.wrapError(error); }
128
					);
129
			});
130
		});
E
Erich Gamma 已提交
131 132
	}

S
Sandeep Somavarapu 已提交
133
	installFromGallery(extension: IGalleryExtension): TPromise<void> {
S
Sandeep Somavarapu 已提交
134 135 136 137
		return this.prepareAndCollectExtensionsToInstall(extension)
			.then(extensionsToInstall => this.downloadAndInstallExtensions(extensionsToInstall)
				.then(local => this.onDidInstallExtensions(extensionsToInstall, local)));
	}
J
Joao Moreno 已提交
138

S
Sandeep Somavarapu 已提交
139 140 141 142 143
	private prepareAndCollectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
		this.onInstallExtensions([extension]);
		return this.collectExtensionsToInstall(extension)
			.then(
			extensionsToInstall => this.checkForObsolete(extensionsToInstall)
S
Sandeep Somavarapu 已提交
144
				.then(
S
Sandeep Somavarapu 已提交
145 146 147 148 149 150
				extensionsToInstall => {
					if (extensionsToInstall.length > 1) {
						this.onInstallExtensions(extensionsToInstall.slice(1));
					}
					return extensionsToInstall;
				},
151
				error => this.onDidInstallExtensions([extension], null, INSTALL_ERROR_OBSOLETE, error)
S
Sandeep Somavarapu 已提交
152
				),
153
			error => this.onDidInstallExtensions([extension], null, INSTALL_ERROR_GALLERY, error)
S
Sandeep Somavarapu 已提交
154
			);
155 156
	}

S
Sandeep Somavarapu 已提交
157 158 159 160
	private downloadAndInstallExtensions(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
		return TPromise.join(extensions.map(extensionToInstall => this.downloadInstallableExtension(extensionToInstall)))
			.then(
			installableExtensions => TPromise.join(installableExtensions.map(installableExtension => this.installExtension(installableExtension)))
161 162
				.then(null, error => this.rollback(extensions).then(() => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_LOCAL, error))),
			error => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_GALLERY, error));
S
Sandeep Somavarapu 已提交
163 164 165
	}

	private collectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
166
		return this.galleryService.loadCompatibleVersion(extension)
S
Sandeep Somavarapu 已提交
167 168 169
			.then(extensionToInstall => this.galleryService.getAllDependencies(extension)
				.then(allDependencies => this.filterDependenciesToInstall(extension, allDependencies))
				.then(dependenciesToInstall => [extensionToInstall, ...dependenciesToInstall]));
170 171
	}

S
Sandeep Somavarapu 已提交
172 173 174
	private checkForObsolete(extensionsToInstall: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
		return this.filterObsolete(...extensionsToInstall.map(i => getLocalExtensionIdFromGallery(i, i.version)))
			.then(obsolete => obsolete.length ? TPromise.wrapError<IGalleryExtension[]>(new Error(nls.localize('restartCodeGallery', "Please restart Code before reinstalling."))) : extensionsToInstall);
175 176
	}

S
Sandeep Somavarapu 已提交
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
	private downloadInstallableExtension(extension: IGalleryExtension): TPromise<InstallableExtension> {
		const id = getLocalExtensionIdFromGallery(extension, extension.version);
		const metadata = <IGalleryMetadata>{
			id: extension.uuid,
			publisherId: extension.publisherId,
			publisherDisplayName: extension.publisherDisplayName,
		};
		return this.galleryService.download(extension)
			.then(zipPath => validate(zipPath).then(() => ({ zipPath, id, metadata })));
	}

	private rollback(extensions: IGalleryExtension[]): TPromise<void> {
		return this.filterOutUninstalled(extensions)
			.then(installed => TPromise.join(installed.map(local => this.uninstallExtension(local.id))))
			.then(() => null, () => null);
192 193
	}

S
Sandeep Somavarapu 已提交
194 195 196 197
	private onInstallExtensions(extensions: IGalleryExtension[]): void {
		for (const extension of extensions) {
			const id = getLocalExtensionIdFromGallery(extension, extension.version);
			this._onInstallExtension.fire({ id, gallery: extension });
198
		}
199 200
	}

201
	private onDidInstallExtensions(extensions: IGalleryExtension[], local: ILocalExtension[], errorCode?: string, error?: any): TPromise<any> {
S
Sandeep Somavarapu 已提交
202 203 204 205 206 207 208 209 210
		extensions.forEach((gallery, index) => {
			const id = getLocalExtensionIdFromGallery(gallery, gallery.version);
			if (errorCode) {
				this._onDidInstallExtension.fire({ id, gallery, error: errorCode });
			} else {
				this._onDidInstallExtension.fire({ id, gallery, local: local[index] });
			}
		});
		return error ? TPromise.wrapError(Array.isArray(error) ? this.joinErrors(error) : error) : TPromise.as(null);
211 212
	}

213
	private filterDependenciesToInstall(extension: IGalleryExtension, dependencies: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
J
Joao Moreno 已提交
214 215
		return this.getInstalled()
			.then(local => {
216
				return dependencies.filter(d => {
217
					if (extension.uuid === d.uuid) {
218 219
						return false;
					}
220
					const extensionId = getLocalExtensionIdFromGallery(d, d.version);
J
Joao Moreno 已提交
221 222
					return local.every(local => local.id !== extensionId);
				});
223
			});
224 225
	}

J
Joao Moreno 已提交
226 227 228
	private filterOutUninstalled(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
		return this.getInstalled()
			.then(installed => installed.filter(local => !!this.getGalleryExtensionForLocalExtension(extensions, local)));
229
	}
230

231
	private getGalleryExtensionForLocalExtension(galleryExtensions: IGalleryExtension[], localExtension: ILocalExtension): IGalleryExtension {
232
		const filtered = galleryExtensions.filter(galleryExtension => getLocalExtensionIdFromGallery(galleryExtension, galleryExtension.version) === localExtension.id);
233 234 235
		return filtered.length ? filtered[0] : null;
	}

S
Sandeep Somavarapu 已提交
236
	private installExtension({ zipPath, id, metadata }: InstallableExtension): TPromise<ILocalExtension> {
J
Joao Moreno 已提交
237 238
		const extensionPath = path.join(this.extensionsPath, id);

J
João Moreno 已提交
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
		return pfs.rimraf(extensionPath).then(() => {
			return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true })
				.then(() => readManifest(extensionPath))
				.then(({ manifest }) => {
					return pfs.readdir(extensionPath).then(children => {
						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;
						const type = LocalExtensionType.User;

						const local: ILocalExtension = { type, id, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
						const manifestPath = path.join(extensionPath, 'package.json');

						return pfs.readFile(manifestPath, 'utf8')
							.then(raw => parseManifest(raw))
							.then(({ manifest }) => assign(manifest, { __metadata: metadata }))
							.then(manifest => pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')))
							.then(() => local);
					});
J
Joao Moreno 已提交
259
				});
J
João Moreno 已提交
260
		});
E
Erich Gamma 已提交
261 262
	}

263
	uninstall(extension: ILocalExtension, force = false): TPromise<void> {
S
Sandeep Somavarapu 已提交
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
		return this.removeOutdatedExtensions()
			.then(() =>
				this.scanUserExtensions()
					.then(installed => {
						const promises = installed
							.filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name)
							.map(e => this.checkForDependenciesAndUninstall(e, installed, force));
						return TPromise.join(promises).then(null, errors => TPromise.wrapError(this.joinErrors(errors)));
					}))
			.then(() => { /* drop resolved value */ });
	}

	private joinErrors(errors: (Error | string)[]): Error {
		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 已提交
282
			return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
S
Sandeep Somavarapu 已提交
283
		}, new Error(''));
J
Joao Moreno 已提交
284 285
	}

286
	private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], force: boolean): TPromise<void> {
J
Joao Moreno 已提交
287
		return this.preUninstallExtension(extension)
288
			.then(() => this.hasDependencies(extension, installed) ? this.promptForDependenciesAndUninstall(extension, installed, force) : this.promptAndUninstall(extension, installed, force))
289
			.then(() => this.postUninstallExtension(extension),
290
			error => {
291
				this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL);
292 293
				return TPromise.wrapError(error);
			});
S
Sandeep Somavarapu 已提交
294 295
	}

296 297
	private hasDependencies(extension: ILocalExtension, installed: ILocalExtension[]): boolean {
		if (extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length) {
S
Sandeep Somavarapu 已提交
298
			return installed.some(i => extension.manifest.extensionDependencies.indexOf(getGalleryExtensionIdFromLocal(i)) !== -1);
299 300 301 302
		}
		return false;
	}

303 304 305 306 307 308
	private promptForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], force: boolean): TPromise<void> {
		if (force) {
			const dependencies = distinct(this.getDependenciesToUninstallRecursively(extension, installed, [])).filter(e => e !== extension);
			return this.uninstallWithDependencies(extension, dependencies, installed);
		}

309
		const message = nls.localize('uninstallDependeciesConfirmation', "Would you like to uninstall '{0}' only or its dependencies also?", extension.manifest.displayName || extension.manifest.name);
S
Sandeep Somavarapu 已提交
310 311 312 313 314
		const options = [
			nls.localize('uninstallOnly', "Only"),
			nls.localize('uninstallAll', "All"),
			nls.localize('cancel', "Cancel")
		];
315
		return this.choiceService.choose(Severity.Info, message, options, 2, true)
S
Sandeep Somavarapu 已提交
316 317
			.then<void>(value => {
				if (value === 0) {
318
					return this.uninstallWithDependencies(extension, [], installed);
S
Sandeep Somavarapu 已提交
319 320
				}
				if (value === 1) {
321 322
					const dependencies = distinct(this.getDependenciesToUninstallRecursively(extension, installed, [])).filter(e => e !== extension);
					return this.uninstallWithDependencies(extension, dependencies, installed);
S
Sandeep Somavarapu 已提交
323 324 325 326 327
				}
				return TPromise.wrapError(errors.canceled());
			}, error => TPromise.wrapError(errors.canceled()));
	}

328 329 330 331 332
	private promptAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], force: boolean): TPromise<void> {
		if (force) {
			return this.uninstallWithDependencies(extension, [], installed);
		}

S
Sandeep Somavarapu 已提交
333 334
		const message = nls.localize('uninstallConfirmation', "Are you sure you want to uninstall '{0}'?", extension.manifest.displayName || extension.manifest.name);
		const options = [
D
David Hewson 已提交
335
			nls.localize('ok', "OK"),
S
Sandeep Somavarapu 已提交
336 337
			nls.localize('cancel', "Cancel")
		];
338
		return this.choiceService.choose(Severity.Info, message, options, 1, true)
S
Sandeep Somavarapu 已提交
339 340 341 342 343 344 345 346
			.then<void>(value => {
				if (value === 0) {
					return this.uninstallWithDependencies(extension, [], installed);
				}
				return TPromise.wrapError(errors.canceled());
			}, error => TPromise.wrapError(errors.canceled()));
	}

347 348
	private uninstallWithDependencies(extension: ILocalExtension, dependencies: ILocalExtension[], installed: ILocalExtension[]): TPromise<void> {
		const dependenciesToUninstall = this.filterDependents(extension, dependencies, installed);
349
		let dependents = this.getDependents(extension, installed).filter(dependent => extension !== dependent && dependenciesToUninstall.indexOf(dependent) === -1);
350
		if (dependents.length) {
351
			return TPromise.wrapError<void>(new Error(this.getDependentsErrorMessage(extension, dependents)));
352
		}
J
Joao Moreno 已提交
353
		return TPromise.join([this.uninstallExtension(extension.id), ...dependenciesToUninstall.map(d => this.doUninstall(d))]).then(() => null);
354 355
	}

S
Sandeep Somavarapu 已提交
356 357 358 359 360 361 362 363 364 365 366 367 368
	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);
	}

369 370 371 372 373 374 375 376
	private getDependenciesToUninstallRecursively(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[]): ILocalExtension[] {
		if (checked.indexOf(extension) !== -1) {
			return [];
		}
		checked.push(extension);
		if (!extension.manifest.extensionDependencies || extension.manifest.extensionDependencies.length === 0) {
			return [];
		}
S
Sandeep Somavarapu 已提交
377
		const dependenciesToUninstall = installed.filter(i => extension.manifest.extensionDependencies.indexOf(getGalleryExtensionIdFromLocal(i)) !== -1);
378 379 380 381 382 383 384 385 386 387 388 389
		const depsOfDeps = [];
		for (const dep of dependenciesToUninstall) {
			depsOfDeps.push(...this.getDependenciesToUninstallRecursively(dep, installed, checked));
		}
		return [...dependenciesToUninstall, ...depsOfDeps];
	}

	private filterDependents(extension: ILocalExtension, dependencies: ILocalExtension[], installed: ILocalExtension[]): ILocalExtension[] {
		installed = installed.filter(i => i !== extension && i.manifest.extensionDependencies && i.manifest.extensionDependencies.length > 0);
		let result = dependencies.slice(0);
		for (let i = 0; i < dependencies.length; i++) {
			const dep = dependencies[i];
390
			const dependents = this.getDependents(dep, installed).filter(e => dependencies.indexOf(e) === -1);
391 392 393
			if (dependents.length) {
				result.splice(i - (dependencies.length - result.length), 1);
			}
S
Sandeep Somavarapu 已提交
394
		}
395
		return result;
S
Sandeep Somavarapu 已提交
396 397
	}

398
	private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {
S
Sandeep Somavarapu 已提交
399
		return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.indexOf(getGalleryExtensionIdFromLocal(extension)) !== -1);
400 401
	}

J
Joao Moreno 已提交
402 403 404
	private doUninstall(extension: ILocalExtension): TPromise<void> {
		return this.preUninstallExtension(extension)
			.then(() => this.uninstallExtension(extension.id))
405
			.then(() => this.postUninstallExtension(extension),
S
Sandeep Somavarapu 已提交
406
			error => {
407
				this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL);
S
Sandeep Somavarapu 已提交
408 409 410
				return TPromise.wrapError(error);
			});
	}
E
Erich Gamma 已提交
411

J
Joao Moreno 已提交
412 413
	private preUninstallExtension(extension: ILocalExtension): TPromise<void> {
		const extensionPath = path.join(this.extensionsPath, extension.id);
E
Erich Gamma 已提交
414
		return pfs.exists(extensionPath)
415
			.then(exists => exists ? null : TPromise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
416
			.then(() => this._onUninstallExtension.fire(extension.id));
S
Sandeep Somavarapu 已提交
417 418 419 420 421
	}

	private uninstallExtension(id: string): TPromise<void> {
		const extensionPath = path.join(this.extensionsPath, id);
		return this.setObsolete(id)
E
Erich Gamma 已提交
422
			.then(() => pfs.rimraf(extensionPath))
S
Sandeep Somavarapu 已提交
423 424 425
			.then(() => this.unsetObsolete(id));
	}

426
	private async postUninstallExtension(extension: ILocalExtension, error?: string): TPromise<void> {
427 428 429 430 431
		if (!error) {
			await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
		}

		this._onDidUninstallExtension.fire({ id: extension.id, error });
E
Erich Gamma 已提交
432 433
	}

J
Joao Moreno 已提交
434 435 436 437 438 439 440 441 442 443 444
	getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {
		const promises = [];

		if (type === null || type === LocalExtensionType.System) {
			promises.push(this.scanSystemExtensions());
		}

		if (type === null || type === LocalExtensionType.User) {
			promises.push(this.scanUserExtensions());
		}

445
		return TPromise.join<ILocalExtension[]>(promises).then(flatten);
J
Joao Moreno 已提交
446 447 448 449 450 451 452 453
	}

	private scanSystemExtensions(): TPromise<ILocalExtension[]> {
		return this.scanExtensions(SystemExtensionsRoot, LocalExtensionType.System);
	}

	private scanUserExtensions(): TPromise<ILocalExtension[]> {
		return this.scanExtensions(this.extensionsPath, LocalExtensionType.User).then(extensions => {
454
			const byId = values(groupBy(extensions, p => getGalleryExtensionIdFromLocal(p)));
J
Joao Moreno 已提交
455
			return byId.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
J
Joao Moreno 已提交
456 457 458
		});
	}

J
Joao Moreno 已提交
459
	private scanExtensions(root: string, type: LocalExtensionType): TPromise<ILocalExtension[]> {
E
Erich Gamma 已提交
460 461
		const limiter = new Limiter(10);

J
Joao Moreno 已提交
462
		return this.scanExtensionFolders(root)
463
			.then(extensionIds => TPromise.join(extensionIds.map(id => {
J
Joao Moreno 已提交
464 465 466 467 468 469 470 471 472
				const extensionPath = path.join(root, id);

				const each = () => pfs.readdir(extensionPath).then(children => {
					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;

					return readManifest(extensionPath)
S
Sandeep Somavarapu 已提交
473 474 475 476 477 478
						.then<ILocalExtension>(({ manifest, metadata }) => {
							if (manifest.extensionDependencies) {
								manifest.extensionDependencies = manifest.extensionDependencies.map(id => adoptToGalleryExtensionId(id));
							}
							return { type, id, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
						});
J
Joao Moreno 已提交
479 480 481 482 483 484 485 486
				}).then(null, () => null);

				return limiter.queue(each);
			})))
			.then(result => result.filter(a => !!a));
	}

	private scanExtensionFolders(root: string): TPromise<string[]> {
487
		return this.getObsoleteExtensions()
J
Joao Moreno 已提交
488
			.then(obsolete => pfs.readdir(root).then(extensions => extensions.filter(id => !obsolete[id])));
E
Erich Gamma 已提交
489 490
	}

J
Joao Moreno 已提交
491 492 493 494 495 496
	removeDeprecatedExtensions(): TPromise<any> {
		return TPromise.join([
			this.removeOutdatedExtensions(),
			this.removeObsoleteExtensions()
		]);
	}
497

J
Joao Moreno 已提交
498 499 500 501
	private removeOutdatedExtensions(): TPromise<any> {
		return this.getOutdatedExtensionIds()
			.then(extensionIds => this.removeExtensions(extensionIds));
	}
502

J
Joao Moreno 已提交
503 504 505 506 507 508 509 510 511 512 513
	private removeObsoleteExtensions(): TPromise<any> {
		return this.getObsoleteExtensions()
			.then(obsolete => Object.keys(obsolete))
			.then(extensionIds => this.removeExtensions(extensionIds));
	}

	private removeExtensions(extensionsIds: string[]): TPromise<any> {
		return TPromise.join(extensionsIds.map(id => {
			return pfs.rimraf(path.join(this.extensionsPath, id))
				.then(() => this.withObsoleteExtensions(obsolete => delete obsolete[id]));
		}));
514 515
	}

J
Joao Moreno 已提交
516 517 518 519
	private getOutdatedExtensionIds(): TPromise<string[]> {
		return this.scanExtensionFolders(this.extensionsPath)
			.then(folders => {
				const galleryFolders = folders
520 521
					.map(folder => (assign({ folder }, getIdAndVersionFromLocalExtensionId(folder))))
					.filter(({ id, version }) => !!id && !!version);
J
Joao Moreno 已提交
522 523 524 525 526 527

				const byId = values(groupBy(galleryFolders, p => p.id));

				return flatten(byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version)).slice(1)))
					.map(a => a.folder);
			});
J
Joao Moreno 已提交
528 529
	}

J
Joao Moreno 已提交
530
	private isObsolete(id: string): TPromise<boolean> {
531 532 533 534 535 536 537 538 539 540 541 542 543
		return this.filterObsolete(id).then(obsolete => obsolete.length === 1);
	}

	private filterObsolete(...ids: string[]): TPromise<string[]> {
		return this.withObsoleteExtensions(allObsolete => {
			const obsolete = [];
			for (const id of ids) {
				if (!!allObsolete[id]) {
					obsolete.push(id);
				}
			}
			return obsolete;
		});
544 545
	}

J
Joao Moreno 已提交
546
	private setObsolete(id: string): TPromise<void> {
J
Joao Moreno 已提交
547
		return this.withObsoleteExtensions(obsolete => assign(obsolete, { [id]: true }));
548 549
	}

J
Joao Moreno 已提交
550
	private unsetObsolete(id: string): TPromise<void> {
J
Joao Moreno 已提交
551
		return this.withObsoleteExtensions<void>(obsolete => delete obsolete[id]);
552 553
	}

J
Johannes Rieken 已提交
554
	private getObsoleteExtensions(): TPromise<{ [id: string]: boolean; }> {
J
Joao Moreno 已提交
555
		return this.withObsoleteExtensions(obsolete => obsolete);
556 557
	}

J
Johannes Rieken 已提交
558
	private withObsoleteExtensions<T>(fn: (obsolete: { [id: string]: boolean; }) => T): TPromise<T> {
559 560 561
		return this.obsoleteFileLimiter.queue(() => {
			let result: T = null;
			return pfs.readFile(this.obsoletePath, 'utf8')
R
Ron Buckton 已提交
562
				.then(null, err => err.code === 'ENOENT' ? TPromise.as('{}') : TPromise.wrapError(err))
J
Johannes Rieken 已提交
563
				.then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
564 565 566 567 568 569 570 571 572 573 574 575
				.then(obsolete => { result = fn(obsolete); return obsolete; })
				.then(obsolete => {
					if (Object.keys(obsolete).length === 0) {
						return pfs.rimraf(this.obsoletePath);
					} else {
						const raw = JSON.stringify(obsolete);
						return pfs.writeFile(this.obsoletePath, raw);
					}
				})
				.then(() => result);
		});
	}
576

577 578 579
	dispose() {
		this.disposables = dispose(this.disposables);
	}
E
Erich Gamma 已提交
580
}