diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 083e7c2cf0bceb59a9ff75f98fd9d42e4037cd6d..135e7e6aecdd271256146e93f0ab2fe083bac769 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -20,7 +20,8 @@ import { InstallExtensionResult, UninstallOptions, IGalleryMetadata, - StatisticType + StatisticType, + IExtensionManagementParticipant } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, ExtensionIdentifierWithVersion, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Event, Emitter } from 'vs/base/common/event'; @@ -76,6 +77,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl protected _onDidUninstallExtension = this._register(new Emitter()); onDidUninstallExtension: Event = this._onDidUninstallExtension.event; + private readonly participants: IExtensionManagementParticipant[] = []; + constructor( @IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService, @ITelemetryService protected readonly telemetryService: ITelemetryService, @@ -152,6 +155,10 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return this.reportedExtensions; } + registerParticipant(participant: IExtensionManagementParticipant): void { + this.participants.push(participant); + } + protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise { // only cache gallery extensions tasks if (!URI.isUri(extension)) { @@ -217,10 +224,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } // Install extensions in parallel and wait until all extensions are installed / failed - const result = await Promise.allSettled(extensionsToInstall.map(async ({ task }) => { + await this.joinAllSettled(extensionsToInstall.map(async ({ task }) => { const startTime = new Date().getTime(); try { const local = await task.run(); + await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, options, CancellationToken.None))); if (!URI.isUri(task.source)) { reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', getGalleryExtensionTelemetryData(task.source), new Date().getTime() - startTime, undefined); } @@ -234,11 +242,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl throw error; } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); } })); - - // Collect the errors - const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []); - // If there are errors, throw the error. - if (errors.length) { throw joinErrors(errors); } } installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id)); @@ -289,6 +292,22 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } } + private async joinAllSettled(promises: Promise[]): Promise { + const results: T[] = []; + const errors: any[] = []; + const promiseResults = await Promise.allSettled(promises); + for (const r of promiseResults) { + if (r.status === 'fulfilled') { + results.push(r.value); + } else { + errors.push(r.reason); + } + } + // If there are errors, throw the error. + if (errors.length) { throw joinErrors(errors); } + return results; + } + private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> { if (!this.galleryService.isEnabled()) { return []; @@ -413,9 +432,10 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } // Uninstall extensions in parallel and wait until all extensions are uninstalled / failed - const result = await Promise.allSettled(allTasks.map(async task => { + await this.joinAllSettled(allTasks.map(async task => { try { await task.run(); + await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, options, CancellationToken.None))); // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. if (task.extension.identifier.uuid) { try { @@ -432,11 +452,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } })); - // Collect the errors - const errors = result.reduce((errors, r) => { if (r.status === 'rejected') { errors.push(r.reason); } return errors; }, []); - // If there are errors, throw the error. - if (errors.length) { throw joinErrors(errors); } - } catch (e) { const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN); for (const task of allTasks) { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 7cf697e0d45e54ec48d55069746b4672c5416fe1..ad367542e2475cfe1dde19d417d37bfe9f9010e4 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -204,6 +204,11 @@ export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, d export type InstallVSIXOptions = InstallOptions & { installOnlyNewlyAddedFromExtensionPack?: boolean }; export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean }; +export interface IExtensionManagementParticipant { + postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions | InstallVSIXOptions, token: CancellationToken): Promise; + postUninstall(local: ILocalExtension, options: UninstallOptions, token: CancellationToken): Promise; +} + export const IExtensionManagementService = createDecorator('extensionManagementService'); export interface IExtensionManagementService { readonly _serviceBrand: undefined; @@ -226,6 +231,8 @@ export interface IExtensionManagementService { updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise; updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise; + + registerParticipant(pariticipant: IExtensionManagementParticipant): void; } export const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled'; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 8c7587e22ad80d69173d92b52a903235622f9c71..2c1e5ffed2b3b5a5e5b3f626354d70264f57ad93 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -162,6 +162,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt getExtensionsReport(): Promise { return Promise.resolve(this.channel.call('getExtensionsReport')); } + + registerParticipant() { throw new Error('Not Supported'); } } export class ExtensionTipsChannel implements IServerChannel { diff --git a/src/vs/platform/localizations/common/localizations.ts b/src/vs/platform/localizations/common/localizations.ts index 509c4ada830970b8e5c132c95a9716ab739fdf68..65e2dd9d4c4088637f0b1f669ce7b17860e5f881 100644 --- a/src/vs/platform/localizations/common/localizations.ts +++ b/src/vs/platform/localizations/common/localizations.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; export interface ILocalization { languageId: string; @@ -22,8 +21,6 @@ export interface ITranslation { export const ILocalizationsService = createDecorator('localizationsService'); export interface ILocalizationsService { readonly _serviceBrand: undefined; - - readonly onDidLanguagesChange: Event; getLanguageIds(): Promise; } diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 5320846a72624239674c901ebd1bf0c45d526c2c..6422dc19d95e695653922b1ac958dfbe44fa375a 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -5,7 +5,7 @@ import { Promises } from 'vs/base/node/pfs'; import { createHash } from 'crypto'; -import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Disposable } from 'vs/base/common/lifecycle'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { Queue } from 'vs/base/common/async'; @@ -13,7 +13,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { ILogService } from 'vs/platform/log/common/log'; import { isValidLocalization, ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { distinct, equals } from 'vs/base/common/arrays'; -import { Event, Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { join } from 'vs/base/common/path'; @@ -32,9 +31,6 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe private readonly cache: LanguagePacksCache; - private readonly _onDidLanguagesChange: Emitter = this._register(new Emitter()); - readonly onDidLanguagesChange: Event = this._onDidLanguagesChange.event; - constructor( @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @INativeEnvironmentService environmentService: INativeEnvironmentService, @@ -42,37 +38,36 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe ) { super(); this.cache = this._register(new LanguagePacksCache(environmentService, logService)); - - this._register(extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e))); - this._register(extensionManagementService.onDidUninstallExtension(({ identifier }) => this.onDidUninstallExtension(identifier))); + this.extensionManagementService.registerParticipant({ + postInstall: async (extension: ILocalExtension): Promise => { + return this.postInstallExtension(extension); + }, + postUninstall: async (extension: ILocalExtension): Promise => { + return this.postUninstallExtension(extension); + } + }); } - getLanguageIds(): Promise { - return this.cache.getLanguagePacks() - .then(languagePacks => { - // Contributed languages are those installed via extension packs, so does not include English - const languages = ['en', ...Object.keys(languagePacks)]; - return distinct(languages); - }); + async getLanguageIds(): Promise { + const languagePacks = await this.cache.getLanguagePacks(); + // Contributed languages are those installed via extension packs, so does not include English + const languages = ['en', ...Object.keys(languagePacks)]; + return distinct(languages); } - private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void { - for (const { local: extension } of results) { - if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) { - this.logService.debug('Adding language packs from the extension', extension.identifier.id); - this.update().then(changed => { if (changed) { this._onDidLanguagesChange.fire(); } }); - } + private async postInstallExtension(extension: ILocalExtension): Promise { + if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) { + this.logService.info('Adding language packs from the extension', extension.identifier.id); + await this.update(); } } - private onDidUninstallExtension(identifier: IExtensionIdentifier): void { - this.cache.getLanguagePacks() - .then(languagePacks => { - if (Object.keys(languagePacks).some(language => languagePacks[language] && languagePacks[language].extensions.some(e => areSameExtensions(e.extensionIdentifier, identifier)))) { - this.logService.debug('Removing language packs from the extension', identifier.id); - this.update().then(changed => { if (changed) { this._onDidLanguagesChange.fire(); } }); - } - }); + private async postUninstallExtension(extension: ILocalExtension): Promise { + const languagePacks = await this.cache.getLanguagePacks(); + if (Object.keys(languagePacks).some(language => languagePacks[language] && languagePacks[language].extensions.some(e => areSameExtensions(e.extensionIdentifier, extension.identifier)))) { + this.logService.info('Removing language packs from the extension', extension.identifier.id); + await this.update(); + } } async update(): Promise { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 5ea0d82065af709cb1b7ef8386920196f4da8163..82b0f085c38763f1a7fe8334bd3fc87d9f228b2f 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -382,4 +382,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return Promise.resolve(); } + + registerParticipant() { throw new Error('Not Supported'); } }