extensionManagementService.ts 19.3 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';
J
Joao Moreno 已提交
14
import { flatten } from 'vs/base/common/arrays';
E
Erich Gamma 已提交
15 16
import { extract, buffer } from 'vs/base/node/zip';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
17 18 19 20
import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
	IGalleryExtension, IExtensionIdentity, IExtensionManifest, IGalleryMetadata,
	InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType
} from 'vs/platform/extensionManagement/common/extensionManagement';
21
import { localizeManifest } from '../common/extensionNls';
J
Joao Moreno 已提交
22
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
E
Erich Gamma 已提交
23
import { Limiter } from 'vs/base/common/async';
J
Joao Moreno 已提交
24
import Event, { Emitter } from 'vs/base/common/event';
J
Joao Moreno 已提交
25
import * as semver from 'semver';
26
import { groupBy, values } from 'vs/base/common/collections';
J
João Moreno 已提交
27
import URI from 'vs/base/common/uri';
28
import { IChoiceService, Severity } from 'vs/platform/message/common/message';
E
Erich Gamma 已提交
29

J
Joao Moreno 已提交
30 31
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));

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

J
Joao Moreno 已提交
45
function validate(zipPath: string, extension?: IExtensionIdentity, version?: string): TPromise<IExtensionManifest> {
E
Erich Gamma 已提交
46 47
	return buffer(zipPath, 'extension/package.json')
		.then(buffer => parseManifest(buffer.toString('utf8')))
J
Joao Moreno 已提交
48
		.then(({ manifest }) => {
E
Erich Gamma 已提交
49 50 51 52 53 54 55 56 57
			if (extension) {
				if (extension.name !== manifest.name) {
					return Promise.wrapError(Error(nls.localize('invalidName', "Extension invalid: manifest name mismatch.")));
				}

				if (extension.publisher !== manifest.publisher) {
					return Promise.wrapError(Error(nls.localize('invalidPublisher', "Extension invalid: manifest publisher mismatch.")));
				}

J
Joao Moreno 已提交
58
				if (version !== manifest.version) {
E
Erich Gamma 已提交
59 60 61 62
					return Promise.wrapError(Error(nls.localize('invalidVersion', "Extension invalid: manifest version mismatch.")));
				}
			}

A
Alex Dima 已提交
63
			return TPromise.as(manifest);
E
Erich Gamma 已提交
64 65 66
		});
}

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
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')
			.then<string>(null, err => err.code !== 'ENOENT' ? TPromise.wrapError(err) : '{}')
			.then(raw => JSON.parse(raw))
	];

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

J
Joao Moreno 已提交
84
function getExtensionId(extension: IExtensionIdentity, version: string): string {
J
Joao Moreno 已提交
85
	return `${ extension.publisher }.${ extension.name }-${ version }`;
86 87
}

J
Joao Moreno 已提交
88
export class ExtensionManagementService implements IExtensionManagementService {
E
Erich Gamma 已提交
89

90
	_serviceBrand: any;
E
Erich Gamma 已提交
91 92

	private extensionsPath: string;
93 94
	private obsoletePath: string;
	private obsoleteFileLimiter: Limiter<void>;
95
	private disposables: IDisposable[];
E
Erich Gamma 已提交
96

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

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

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

J
Joao Moreno 已提交
106 107
	private _onDidUninstallExtension = new Emitter<string>();
	onDidUninstallExtension: Event<string> = this._onDidUninstallExtension.event;
E
Erich Gamma 已提交
108 109

	constructor(
110
		@IEnvironmentService private environmentService: IEnvironmentService,
111
		@IChoiceService private choiceService: IChoiceService,
112
		@IExtensionGalleryService private galleryService: IExtensionGalleryService
E
Erich Gamma 已提交
113
	) {
J
Joao Moreno 已提交
114
		this.extensionsPath = environmentService.extensionsPath;
115 116
		this.obsoletePath = path.join(this.extensionsPath, '.obsolete');
		this.obsoleteFileLimiter = new Limiter(1);
E
Erich Gamma 已提交
117 118
	}

119
	install(zipPath: string): TPromise<void> {
120 121
		zipPath = path.resolve(zipPath);

122 123
		return validate(zipPath).then<void>(manifest => {
			const id = getExtensionId(manifest, manifest.version);
124

125 126 127 128
			return this.isObsolete(id).then(isObsolete => {
				if (isObsolete) {
					return TPromise.wrapError(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name)));
				}
J
Joao Moreno 已提交
129

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

132 133
				return this.installExtension(zipPath, id)
					.then(
134 135
						local => this._onDidInstallExtension.fire({ id, zipPath, local }),
						error => { this._onDidInstallExtension.fire({ id, zipPath, error }); return TPromise.wrapError(error); }
136
					);
137
			});
138
		});
E
Erich Gamma 已提交
139 140
	}

141
	installFromGallery(extension: IGalleryExtension): TPromise<void> {
142
		const id = getExtensionId(extension, extension.version);
J
Joao Moreno 已提交
143

144 145 146 147 148
		return this.isObsolete(id).then(isObsolete => {
			if (isObsolete) {
				return TPromise.wrapError<void>(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", extension.displayName || extension.name)));
			}
			this._onInstallExtension.fire({ id, gallery: extension });
149
			return this.getLocalExtension(extension)
S
Sandeep Somavarapu 已提交
150 151 152 153 154 155 156 157 158 159 160 161
				.then(local => {
					const installDependencies = local ? local.metadata && local.metadata.dependenciesInstalled : true;
					const promptToInstallDependencies = !local;
					return this.installCompatibleVersion(extension, installDependencies, promptToInstallDependencies);
				})
				.then(
					local => this._onDidInstallExtension.fire({ id, local, gallery: extension }),
					error => {
						this._onDidInstallExtension.fire({ id, gallery: extension, error });
						return TPromise.wrapError(error);
					}
				);
162 163 164
		});
	}

165
	private installCompatibleVersion(extension: IGalleryExtension, installDependencies: boolean, promptToInstallDependencies: boolean): TPromise<ILocalExtension> {
166
		return this.galleryService.loadCompatibleVersion(extension)
167
			.then(compatibleVersion => this.getDependenciesToInstall(extension, installDependencies)
168 169
				.then(dependencies => {
					if (!dependencies.length) {
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
						return this.downloadAndInstall(compatibleVersion, installDependencies && !!compatibleVersion.properties.dependencies.length);
					}
					if (promptToInstallDependencies) {
						const message = nls.localize('installDependecies', "Would you also like to install dependencies of {0}?", extension.displayName);
						const options = [
							nls.localize('installWithDependenices', "Yes"),
							nls.localize('installWithoutDependenices', "No"),
							nls.localize('cancel', "Cancel")
						];
						return this.choiceService.choose(Severity.Info, message, options)
							.then(value => {
								switch (value) {
									case 0:
										return this.installWithDependencies(compatibleVersion);
									case 1:
										return this.downloadAndInstall(compatibleVersion, false);
									default:
										return TPromise.wrapError(errors.canceled());
								}
							}, error => TPromise.wrapError(errors.canceled()));
					} else {
						return this.installWithDependencies(compatibleVersion);
192 193 194
					}
				})
		);
195 196
	}

197 198 199 200 201 202
	private getLocalExtension(extension: IGalleryExtension): TPromise<ILocalExtension> {
		const extensionName = `${extension.publisher}.${extension.name}`;
		return this.getInstalled().then(installed => installed.filter(local => `${local.manifest.publisher}.${local.manifest.name}` === extensionName))
			.then(local => local.length ? local[0] : null);
	}

203 204 205 206 207
	private getDependenciesToInstall(extension: IGalleryExtension, checkDependecies: boolean): TPromise<string[]> {
		if (!checkDependecies) {
			return TPromise.wrap([]);
		}
		// Filter out self
208
		const extensionName = `${extension.publisher}.${extension.name}`;
209 210 211 212 213 214 215 216
		const dependencies = extension.properties.dependencies ? extension.properties.dependencies.filter(name => name !== extensionName) : [];
		if (!dependencies.length) {
			return TPromise.wrap([]);
		}
		// Filter out installed dependencies
		return this.getInstalled().then(installed => {
			return dependencies.filter(dep => installed.every(i => `${i.manifest.publisher}.${i.manifest.name}` !== dep));
		});
217 218 219 220 221
	}

	private installWithDependencies(extension: IGalleryExtension): TPromise<ILocalExtension> {
		return this.galleryService.getAllDependencies(extension)
			.then(allDependencies => this.filterOutInstalled(allDependencies))
222 223 224 225 226 227 228 229
			.then(toInstall => this.filterObsolete(...toInstall.map(i => getExtensionId(i, i.version)))
				.then((obsolete) => {
					if (obsolete.length) {
						return TPromise.wrapError<ILocalExtension>(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", extension.displayName || extension.name)));
					}
					return this.bulkInstallWithDependencies(extension, toInstall);
				})
			);
230 231 232
	}

	private bulkInstallWithDependencies(extension: IGalleryExtension, dependecies: IGalleryExtension[]): TPromise<ILocalExtension> {
233 234 235 236
		for (const dependency of dependecies) {
			const id = getExtensionId(dependency, dependency.version);
			this._onInstallExtension.fire({ id, gallery: dependency });
		}
237
		return this.downloadAndInstall(extension, true)
238
			.then(localExtension => {
239
				return TPromise.join(dependecies.map((dep) => this.installCompatibleVersion(dep, false, false)))
240
					.then(installedLocalExtensions => {
241 242 243 244 245
						for (const installedLocalExtension of installedLocalExtensions) {
							const gallery = this.getGalleryExtensionForLocalExtension(dependecies, installedLocalExtension);
							this._onDidInstallExtension.fire({ id: installedLocalExtension.id, local: installedLocalExtension, gallery });
						}
						return localExtension;
246
					}, error => {
247 248 249 250
						return this.rollback(localExtension, dependecies).then(() => {
							return TPromise.wrapError(Array.isArray(error) ? error[error.length - 1] : error);
						});
					});
251 252 253 254 255 256
			})
			.then(localExtension => localExtension, error => {
				for (const dependency of dependecies) {
					this._onDidInstallExtension.fire({ id: getExtensionId(dependency, dependency.version), gallery: dependency, error });
				}
				return TPromise.wrapError(error);
257
			});
258 259 260 261 262 263 264 265 266 267 268
	}

	private rollback(localExtension: ILocalExtension, dependecies: IGalleryExtension[]): TPromise<void> {
		return this.uninstall(localExtension)
					.then(() => this.filterOutUnInstalled(dependecies))
					.then(installed => TPromise.join(installed.map((i) => this.uninstall(i))))
					.then(() => null);
	}

	private filterOutInstalled(extensions: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
		return this.getInstalled().then(local => {
269 270 271 272
			return extensions.filter(extension => {
				const extensionId = getExtensionId(extension, extension.version);
				return local.every(local => local.id !== extensionId);
			});
273 274 275 276 277
		});
	}

	private filterOutUnInstalled(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
		return this.getInstalled().then(installed => {
278
			return installed.filter(local => {
279
				return !!this.getGalleryExtensionForLocalExtension(extensions, local);
280
			});
281 282
		});
	}
283

284 285 286 287 288
	private getGalleryExtensionForLocalExtension(galleryExtensions: IGalleryExtension[], localExtension: ILocalExtension): IGalleryExtension {
		const filtered = galleryExtensions.filter(galleryExtension => getExtensionId(galleryExtension, galleryExtension.version) === localExtension.id);
		return filtered.length ? filtered[0] : null;
	}

289
	private downloadAndInstall(extension: IGalleryExtension, dependenciesInstalled: boolean): TPromise<ILocalExtension> {
290 291
		const id = getExtensionId(extension, extension.version);
		const metadata = {
292 293
				id: extension.id,
				publisherId: extension.publisherId,
294 295
				publisherDisplayName: extension.publisherDisplayName,
				dependenciesInstalled: dependenciesInstalled
296
			};
297 298 299
		return this.galleryService.download(extension)
			.then(zipPath => validate(zipPath).then(() => zipPath))
			.then(zipPath => this.installExtension(zipPath, id, metadata));
300
	}
J
Joao Moreno 已提交
301

302
	private installExtension(zipPath: string, id: string, metadata: IGalleryMetadata = null): TPromise<ILocalExtension> {
J
Joao Moreno 已提交
303 304 305
		const extensionPath = path.join(this.extensionsPath, id);

		return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true })
306
			.then(() => readManifest(extensionPath))
J
Joao Moreno 已提交
307
			.then(({ manifest }) => {
J
Joao Moreno 已提交
308 309
				return pfs.readdir(extensionPath).then(children => {
					const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
J
João Moreno 已提交
310
					const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null;
311 312
					const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
					const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)).toString() : null;
J
Joao Moreno 已提交
313
					const type = LocalExtensionType.User;
314

J
Joao Moreno 已提交
315
					const local: ILocalExtension = { type, id, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
316
					const manifestPath = path.join(extensionPath, 'package.json');
J
Joao Moreno 已提交
317

318 319 320 321
					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')))
322
						.then(() => local);
J
Joao Moreno 已提交
323
				});
324
			});
E
Erich Gamma 已提交
325 326
	}

J
Joao Moreno 已提交
327
	uninstall(extension: ILocalExtension): TPromise<void> {
J
Joao Moreno 已提交
328
		return this.scanUserExtensions().then<void>(installed => {
J
Joao Moreno 已提交
329 330 331 332 333 334 335 336 337
			const promises = installed
				.filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name)
				.map(({ id }) => this.uninstallExtension(id));

			return TPromise.join(promises);
		});
	}

	private uninstallExtension(id: string): TPromise<void> {
J
Joao Moreno 已提交
338
		const extensionPath = path.join(this.extensionsPath, id);
E
Erich Gamma 已提交
339 340 341

		return pfs.exists(extensionPath)
			.then(exists => exists ? null : Promise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
J
Joao Moreno 已提交
342
			.then(() => this._onUninstallExtension.fire(id))
J
Joao Moreno 已提交
343
			.then(() => this.setObsolete(id))
E
Erich Gamma 已提交
344
			.then(() => pfs.rimraf(extensionPath))
J
Joao Moreno 已提交
345
			.then(() => this.unsetObsolete(id))
J
Joao Moreno 已提交
346
			.then(() => this._onDidUninstallExtension.fire(id));
E
Erich Gamma 已提交
347 348
	}

J
Joao Moreno 已提交
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
	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());
		}

		return TPromise.join(promises).then(flatten);
	}

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

	private scanUserExtensions(): TPromise<ILocalExtension[]> {
		return this.scanExtensions(this.extensionsPath, LocalExtensionType.User).then(extensions => {
J
Joao Moreno 已提交
369 370
			const byId = values(groupBy(extensions, p => `${ p.manifest.publisher }.${ p.manifest.name }`));
			return byId.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]);
J
Joao Moreno 已提交
371 372 373
		});
	}

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

377 378
		return this.getObsoleteExtensions()
			.then(obsolete => {
J
Joao Moreno 已提交
379
				return pfs.readdir(root)
J
Joao Moreno 已提交
380
					.then(extensions => extensions.filter(id => !obsolete[id]))
J
Joao Moreno 已提交
381
					.then<ILocalExtension[]>(extensionIds => Promise.join(extensionIds.map(id => {
J
Joao Moreno 已提交
382
						const extensionPath = path.join(root, id);
383

J
Joao Moreno 已提交
384 385
						const each = () => pfs.readdir(extensionPath).then(children => {
							const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
J
João Moreno 已提交
386
							const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null;
387 388 389
							const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
							const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)).toString() : null;

390
							return readManifest(extensionPath)
J
Joao Moreno 已提交
391
								.then<ILocalExtension>(({ manifest, metadata }) => ({ type, id, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl }));
J
Joao Moreno 已提交
392 393 394
						}).then(null, () => null);

						return limiter.queue(each);
395 396 397
					})))
					.then(result => result.filter(a => !!a));
			});
E
Erich Gamma 已提交
398 399
	}

J
Joao Moreno 已提交
400
	removeDeprecatedExtensions(): TPromise<void> {
J
Joao Moreno 已提交
401
		const outdated = this.getOutdatedExtensionIds()
J
Joao Moreno 已提交
402
			.then(extensions => extensions.map(e => getExtensionId(e.manifest, e.manifest.version)));
403 404 405 406 407 408 409 410 411

		const obsolete = this.getObsoleteExtensions()
			.then(obsolete => Object.keys(obsolete));

		return TPromise.join([outdated, obsolete])
			.then(result => flatten(result))
			.then<void>(extensionsIds => {
				return TPromise.join(extensionsIds.map(id => {
					return pfs.rimraf(path.join(this.extensionsPath, id))
J
Joao Moreno 已提交
412
						.then(() => this.withObsoleteExtensions(obsolete => delete obsolete[id]));
413 414 415 416
				}));
			});
	}

J
Joao Moreno 已提交
417
	private getOutdatedExtensionIds(): TPromise<ILocalExtension[]> {
J
Joao Moreno 已提交
418
		return this.scanUserExtensions()
J
Joao Moreno 已提交
419 420
			.then(extensions => values(groupBy(extensions, p => `${ p.manifest.publisher }.${ p.manifest.name }`)))
			.then(versions => flatten(versions.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version)).slice(1))));
J
Joao Moreno 已提交
421 422
	}

J
Joao Moreno 已提交
423
	private isObsolete(id: string): TPromise<boolean> {
424 425 426 427 428 429 430 431 432 433 434 435 436
		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;
		});
437 438
	}

J
Joao Moreno 已提交
439
	private setObsolete(id: string): TPromise<void> {
J
Joao Moreno 已提交
440
		return this.withObsoleteExtensions(obsolete => assign(obsolete, { [id]: true }));
441 442
	}

J
Joao Moreno 已提交
443
	private unsetObsolete(id: string): TPromise<void> {
J
Joao Moreno 已提交
444
		return this.withObsoleteExtensions<void>(obsolete => delete obsolete[id]);
445 446 447
	}

	private getObsoleteExtensions(): TPromise<{ [id:string]: boolean; }> {
J
Joao Moreno 已提交
448
		return this.withObsoleteExtensions(obsolete => obsolete);
449 450
	}

J
Joao Moreno 已提交
451
	private withObsoleteExtensions<T>(fn: (obsolete: { [id:string]: boolean; }) => T): TPromise<T> {
452 453 454
		return this.obsoleteFileLimiter.queue(() => {
			let result: T = null;
			return pfs.readFile(this.obsoletePath, 'utf8')
D
Dirk Baeumer 已提交
455
				.then<string>(null, err => err.code === 'ENOENT' ? TPromise.as('{}') : TPromise.wrapError(err))
J
Joao Moreno 已提交
456
				.then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; }})
457 458 459 460 461 462 463 464 465 466 467 468
				.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);
		});
	}
469

470 471 472
	dispose() {
		this.disposables = dispose(this.disposables);
	}
E
Erich Gamma 已提交
473
}