Path: blob/main/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
5241 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { distinct, isNonEmptyArray } from '../../../base/common/arrays.js';6import { Barrier, CancelablePromise, createCancelablePromise } from '../../../base/common/async.js';7import { CancellationToken } from '../../../base/common/cancellation.js';8import { CancellationError, getErrorMessage, isCancellationError } from '../../../base/common/errors.js';9import { Emitter, Event } from '../../../base/common/event.js';10import { Disposable, toDisposable } from '../../../base/common/lifecycle.js';11import { ResourceMap } from '../../../base/common/map.js';12import { isWeb } from '../../../base/common/platform.js';13import { URI } from '../../../base/common/uri.js';14import * as nls from '../../../nls.js';15import {16ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation,17IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode,18InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError,19IProductVersion, ExtensionGalleryErrorCode,20EXTENSION_INSTALL_SOURCE_CONTEXT,21DidUpdateExtensionMetadata,22UninstallExtensionInfo,23ExtensionSignatureVerificationCode,24IAllowedExtensionsService25} from './extensionManagement.js';26import { areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, isMalicious } from './extensionManagementUtil.js';27import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from '../../extensions/common/extensions.js';28import { areApiProposalsCompatible } from '../../extensions/common/extensionValidator.js';29import { ILogService } from '../../log/common/log.js';30import { IProductService } from '../../product/common/productService.js';31import { ITelemetryService } from '../../telemetry/common/telemetry.js';32import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';33import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';34import { IMarkdownString, MarkdownString } from '../../../base/common/htmlContent.js';3536export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions };3738export type InstallExtensionTaskOptions = InstallOptions & { readonly profileLocation: URI; readonly productVersion: IProductVersion };39export interface IInstallExtensionTask {40readonly manifest: IExtensionManifest;41readonly identifier: IExtensionIdentifier;42readonly source: IGalleryExtension | URI;43readonly operation: InstallOperation;44readonly options: InstallExtensionTaskOptions;45readonly verificationStatus?: ExtensionSignatureVerificationCode;46run(): Promise<ILocalExtension>;47waitUntilTaskIsFinished(): Promise<ILocalExtension>;48cancel(): void;49}5051export type UninstallExtensionTaskOptions = UninstallOptions & { readonly profileLocation: URI };52export interface IUninstallExtensionTask {53readonly options: UninstallExtensionTaskOptions;54readonly extension: ILocalExtension;55run(): Promise<void>;56waitUntilTaskIsFinished(): Promise<void>;57cancel(): void;58}5960export abstract class CommontExtensionManagementService extends Disposable implements IExtensionManagementService {6162_serviceBrand: undefined;6364readonly preferPreReleases: boolean;6566constructor(67@IProductService protected readonly productService: IProductService,68@IAllowedExtensionsService protected readonly allowedExtensionsService: IAllowedExtensionsService,69) {70super();71this.preferPreReleases = this.productService.quality !== 'stable';72}7374async canInstall(extension: IGalleryExtension): Promise<true | IMarkdownString> {75const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName });76if (allowedToInstall !== true) {77return new MarkdownString(nls.localize('not allowed to install', "This extension cannot be installed because {0}", allowedToInstall.value));78}7980if (!(await this.isExtensionPlatformCompatible(extension))) {81const learnLink = isWeb ? 'https://aka.ms/vscode-web-extensions-guide' : 'https://aka.ms/vscode-platform-specific-extensions';82return new MarkdownString(`${nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for the {2} platform.",83extension.displayName ?? extension.identifier.id, this.productService.nameLong, TargetPlatformToString(await this.getTargetPlatform()))} [${nls.localize('learn why', "Learn Why")}](${learnLink})`);84}8586return true;87}8889protected async isExtensionPlatformCompatible(extension: IGalleryExtension): Promise<boolean> {90const currentTargetPlatform = await this.getTargetPlatform();91return extension.allTargetPlatforms.some(targetPlatform => isTargetPlatformCompatible(targetPlatform, extension.allTargetPlatforms, currentTargetPlatform));92}9394abstract readonly onInstallExtension: Event<InstallExtensionEvent>;95abstract readonly onDidInstallExtensions: Event<readonly InstallExtensionResult[]>;96abstract readonly onUninstallExtension: Event<UninstallExtensionEvent>;97abstract readonly onDidUninstallExtension: Event<DidUninstallExtensionEvent>;98abstract readonly onDidUpdateExtensionMetadata: Event<DidUpdateExtensionMetadata>;99abstract installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;100abstract installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]>;101abstract uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;102abstract uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise<void>;103abstract toggleApplicationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise<ILocalExtension>;104abstract getExtensionsControlManifest(): Promise<IExtensionsControlManifest>;105abstract resetPinnedStateForAllUserExtensions(pinned: boolean): Promise<void>;106abstract registerParticipant(pariticipant: IExtensionManagementParticipant): void;107abstract getTargetPlatform(): Promise<TargetPlatform>;108abstract zip(extension: ILocalExtension): Promise<URI>;109abstract getManifest(vsix: URI): Promise<IExtensionManifest>;110abstract install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension>;111abstract installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension>;112abstract installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]>;113abstract getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]>;114abstract copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise<void>;115abstract download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise<URI>;116abstract cleanUp(): Promise<void>;117abstract updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>, profileLocation: URI): Promise<ILocalExtension>;118}119120export abstract class AbstractExtensionManagementService extends CommontExtensionManagementService implements IExtensionManagementService {121122declare readonly _serviceBrand: undefined;123124private extensionsControlManifest: Promise<IExtensionsControlManifest> | undefined;125private lastReportTimestamp = 0;126private readonly installingExtensions = new Map<string, { task: IInstallExtensionTask; waitingTasks: IInstallExtensionTask[] }>();127private readonly uninstallingExtensions = new Map<string, IUninstallExtensionTask>();128129private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());130get onInstallExtension() { return this._onInstallExtension.event; }131132protected readonly _onDidInstallExtensions = this._register(new Emitter<InstallExtensionResult[]>());133get onDidInstallExtensions() { return this._onDidInstallExtensions.event; }134135protected readonly _onUninstallExtension = this._register(new Emitter<UninstallExtensionEvent>());136get onUninstallExtension() { return this._onUninstallExtension.event; }137138protected _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());139get onDidUninstallExtension() { return this._onDidUninstallExtension.event; }140141protected readonly _onDidUpdateExtensionMetadata = this._register(new Emitter<DidUpdateExtensionMetadata>());142get onDidUpdateExtensionMetadata() { return this._onDidUpdateExtensionMetadata.event; }143144private readonly participants: IExtensionManagementParticipant[] = [];145146constructor(147@IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService,148@ITelemetryService protected readonly telemetryService: ITelemetryService,149@IUriIdentityService protected readonly uriIdentityService: IUriIdentityService,150@ILogService protected readonly logService: ILogService,151@IProductService productService: IProductService,152@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,153@IUserDataProfilesService protected readonly userDataProfilesService: IUserDataProfilesService,154) {155super(productService, allowedExtensionsService);156this._register(toDisposable(() => {157this.installingExtensions.forEach(({ task }) => task.cancel());158this.uninstallingExtensions.forEach(promise => promise.cancel());159this.installingExtensions.clear();160this.uninstallingExtensions.clear();161}));162}163164async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {165try {166const results = await this.installGalleryExtensions([{ extension, options }]);167const result = results.find(({ identifier }) => areSameExtensions(identifier, extension.identifier));168if (result?.local) {169return result?.local;170}171if (result?.error) {172throw result.error;173}174throw new ExtensionManagementError(`Unknown error while installing extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Unknown);175} catch (error) {176throw toExtensionManagementError(error);177}178}179180async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {181if (!this.galleryService.isEnabled()) {182throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.NotAllowed);183}184185const results: InstallExtensionResult[] = [];186const installableExtensions: InstallableExtension[] = [];187188await Promise.allSettled(extensions.map(async ({ extension, options }) => {189try {190const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion, options.productVersion ?? { version: this.productService.version, date: this.productService.date });191installableExtensions.push({ ...compatible, options });192} catch (error) {193results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error, profileLocation: options.profileLocation ?? this.getCurrentExtensionsManifestLocation() });194}195}));196197if (installableExtensions.length) {198results.push(...await this.installExtensions(installableExtensions));199}200201return results;202}203204async uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {205this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);206return this.uninstallExtensions([{ extension, options }]);207}208209async toggleApplicationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise<ILocalExtension> {210if (isApplicationScopedExtension(extension.manifest) || extension.isBuiltin) {211return extension;212}213214if (extension.isApplicationScoped) {215let local = await this.updateMetadata(extension, { isApplicationScoped: false }, this.userDataProfilesService.defaultProfile.extensionsResource);216if (!this.uriIdentityService.extUri.isEqual(fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) {217local = await this.copyExtension(extension, this.userDataProfilesService.defaultProfile.extensionsResource, fromProfileLocation);218}219220for (const profile of this.userDataProfilesService.profiles) {221const existing = (await this.getInstalled(ExtensionType.User, profile.extensionsResource))222.find(e => areSameExtensions(e.identifier, extension.identifier));223if (existing) {224this._onDidUpdateExtensionMetadata.fire({ local: existing, profileLocation: profile.extensionsResource });225} else {226this._onDidUninstallExtension.fire({ identifier: extension.identifier, profileLocation: profile.extensionsResource });227}228}229return local;230}231232else {233const local = this.uriIdentityService.extUri.isEqual(fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)234? await this.updateMetadata(extension, { isApplicationScoped: true }, this.userDataProfilesService.defaultProfile.extensionsResource)235: await this.copyExtension(extension, fromProfileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, { isApplicationScoped: true });236237this._onDidInstallExtensions.fire([{ identifier: local.identifier, operation: InstallOperation.Install, local, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource, applicationScoped: true }]);238return local;239}240241}242243getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {244const now = new Date().getTime();245246if (!this.extensionsControlManifest || now - this.lastReportTimestamp > 1000 * 60 * 5) { // 5 minute cache freshness247this.extensionsControlManifest = this.updateControlCache();248this.lastReportTimestamp = now;249}250251return this.extensionsControlManifest;252}253254registerParticipant(participant: IExtensionManagementParticipant): void {255this.participants.push(participant);256}257258async resetPinnedStateForAllUserExtensions(pinned: boolean): Promise<void> {259try {260await this.joinAllSettled(this.userDataProfilesService.profiles.map(261async profile => {262const extensions = await this.getInstalled(ExtensionType.User, profile.extensionsResource);263await this.joinAllSettled(extensions.map(264async extension => {265if (extension.pinned !== pinned) {266await this.updateMetadata(extension, { pinned }, profile.extensionsResource);267}268}));269}));270} catch (error) {271this.logService.error('Error while resetting pinned state for all user extensions', getErrorMessage(error));272throw error;273}274}275276protected async installExtensions(extensions: InstallableExtension[]): Promise<InstallExtensionResult[]> {277const installExtensionResultsMap = new Map<string, InstallExtensionResult & { profileLocation: URI }>();278const installingExtensionsMap = new Map<string, { task: IInstallExtensionTask; root: IInstallExtensionTask | undefined; uninstallTaskToWaitFor?: IUninstallExtensionTask }>();279const alreadyRequestedInstallations: Promise<ILocalExtension>[] = [];280281const getInstallExtensionTaskKey = (extension: IGalleryExtension, profileLocation: URI) => `${ExtensionKey.create(extension).toString()}-${profileLocation.toString()}`;282const createInstallExtensionTask = (manifest: IExtensionManifest, extension: IGalleryExtension | URI, options: InstallExtensionTaskOptions, root: IInstallExtensionTask | undefined): void => {283let uninstallTaskToWaitFor;284if (!URI.isUri(extension)) {285if (installingExtensionsMap.has(`${extension.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`)) {286return;287}288const existingInstallingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension, options.profileLocation));289if (existingInstallingExtension) {290if (root && this.canWaitForTask(root, existingInstallingExtension.task)) {291const identifier = existingInstallingExtension.task.identifier;292this.logService.info('Waiting for already requested installing extension', identifier.id, root.identifier.id, options.profileLocation.toString());293existingInstallingExtension.waitingTasks.push(root);294// add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension295alreadyRequestedInstallations.push(296Event.toPromise(297Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier)))298).then(results => {299this.logService.info('Finished waiting for already requested installing extension', identifier.id, root.identifier.id, options.profileLocation.toString());300const result = results.find(result => areSameExtensions(result.identifier, identifier));301if (!result?.local) {302// Extension failed to install303throw new Error(`Extension ${identifier.id} is not installed`);304}305return result.local;306}));307}308return;309}310uninstallTaskToWaitFor = this.uninstallingExtensions.get(this.getUninstallExtensionTaskKey(extension.identifier, options.profileLocation));311}312const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);313const key = `${getGalleryExtensionId(manifest.publisher, manifest.name)}-${options.profileLocation.toString()}`;314installingExtensionsMap.set(key, { task: installExtensionTask, root, uninstallTaskToWaitFor });315this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation });316this.logService.info('Installing extension:', installExtensionTask.identifier.id, options);317// only cache gallery extensions tasks318if (!URI.isUri(extension)) {319this.installingExtensions.set(getInstallExtensionTaskKey(extension, options.profileLocation), { task: installExtensionTask, waitingTasks: [] });320}321};322323try {324// Start installing extensions325for (const { manifest, extension, options } of extensions) {326const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest);327const installExtensionTaskOptions: InstallExtensionTaskOptions = {328...options,329isApplicationScoped,330profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation(),331productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date }332};333334const existingInstallExtensionTask = !URI.isUri(extension) ? this.installingExtensions.get(getInstallExtensionTaskKey(extension, installExtensionTaskOptions.profileLocation)) : undefined;335if (existingInstallExtensionTask) {336this.logService.info('Extension is already requested to install', existingInstallExtensionTask.task.identifier.id, installExtensionTaskOptions.profileLocation.toString());337alreadyRequestedInstallations.push(existingInstallExtensionTask.task.waitUntilTaskIsFinished());338} else {339createInstallExtensionTask(manifest, extension, installExtensionTaskOptions, undefined);340}341}342343// collect and start installing all dependencies and pack extensions344await Promise.all([...installingExtensionsMap.values()].map(async ({ task }) => {345if (task.options.donotIncludePackAndDependencies) {346this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id);347} else {348try {349let preferPreRelease = this.preferPreReleases;350if (task.options.installPreReleaseVersion) {351preferPreRelease = true;352} else if (!URI.isUri(task.source) && task.source.hasPreReleaseVersion) {353// Explicitly asked to install the release version354preferPreRelease = false;355}356const installed = await this.getInstalled(undefined, task.options.profileLocation, task.options.productVersion);357const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, task.manifest, preferPreRelease, task.options.productVersion, installed);358const options: InstallExtensionTaskOptions = { ...task.options, pinned: false, installGivenVersion: false, context: { ...task.options.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } };359for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) {360const existing = installed.find(e => areSameExtensions(e.identifier, gallery.identifier));361// Skip if the extension is already installed and has the same application scope362if (existing && existing.isApplicationScoped === !!options.isApplicationScoped) {363continue;364}365createInstallExtensionTask(manifest, gallery, options, task);366}367} catch (error) {368// Installing through VSIX369if (URI.isUri(task.source)) {370// Ignore installing dependencies and packs371if (isNonEmptyArray(task.manifest.extensionDependencies)) {372this.logService.warn(`Cannot install dependencies of extension:`, task.identifier.id, error.message);373}374if (isNonEmptyArray(task.manifest.extensionPack)) {375this.logService.warn(`Cannot install packed extensions of extension:`, task.identifier.id, error.message);376}377} else {378this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', task.identifier.id);379throw error;380}381}382}383}));384385const otherProfilesToUpdate = await this.getOtherProfilesToUpdateExtension([...installingExtensionsMap.values()].map(({ task }) => task));386for (const [profileLocation, task] of otherProfilesToUpdate) {387createInstallExtensionTask(task.manifest, task.source, { ...task.options, profileLocation }, undefined);388}389390// Install extensions in parallel and wait until all extensions are installed / failed391await this.joinAllSettled([...installingExtensionsMap.entries()].map(async ([key, { task, uninstallTaskToWaitFor }]) => {392const startTime = new Date().getTime();393let local: ILocalExtension;394try {395if (uninstallTaskToWaitFor) {396this.logService.info('Waiting for existing uninstall task to complete before installing', task.identifier.id);397try {398await uninstallTaskToWaitFor.waitUntilTaskIsFinished();399this.logService.info('Finished waiting for uninstall task, proceeding with install', task.identifier.id);400} catch (error) {401this.logService.info('Uninstall task failed, proceeding with install anyway', task.identifier.id, getErrorMessage(error));402}403}404405local = await task.run();406await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None)), ExtensionManagementErrorCode.PostInstall);407} catch (e) {408const error = toExtensionManagementError(e);409if (!URI.isUri(task.source)) {410reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', {411extensionData: getGalleryExtensionTelemetryData(task.source),412error,413source: task.options.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] as string | undefined414});415}416installExtensionResultsMap.set(key, { error, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, applicationScoped: task.options.isApplicationScoped });417this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error), task.options.profileLocation.toString());418throw error;419}420if (!URI.isUri(task.source)) {421const isUpdate = task.operation === InstallOperation.Update;422const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000;423reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', {424extensionData: getGalleryExtensionTelemetryData(task.source),425verificationStatus: task.verificationStatus,426duration: new Date().getTime() - startTime,427durationSinceUpdate,428source: task.options.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] as string | undefined429});430}431installExtensionResultsMap.set(key, { local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, applicationScoped: local.isApplicationScoped });432}));433434if (alreadyRequestedInstallations.length) {435await this.joinAllSettled(alreadyRequestedInstallations);436}437} catch (error) {438const getAllDepsAndPacks = (extension: ILocalExtension, profileLocation: URI, allDepsOrPacks: string[]) => {439const depsOrPacks = [];440if (extension.manifest.extensionDependencies?.length) {441depsOrPacks.push(...extension.manifest.extensionDependencies);442}443if (extension.manifest.extensionPack?.length) {444depsOrPacks.push(...extension.manifest.extensionPack);445}446for (const id of depsOrPacks) {447if (allDepsOrPacks.includes(id.toLowerCase())) {448continue;449}450allDepsOrPacks.push(id.toLowerCase());451const installed = installExtensionResultsMap.get(`${id.toLowerCase()}-${profileLocation.toString()}`);452if (installed?.local) {453allDepsOrPacks = getAllDepsAndPacks(installed.local, profileLocation, allDepsOrPacks);454}455}456return allDepsOrPacks;457};458const getErrorResult = (task: IInstallExtensionTask) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: task.options.context, profileLocation: task.options.profileLocation, error });459460const rollbackTasks: IUninstallExtensionTask[] = [];461for (const [key, { task, root }] of installingExtensionsMap) {462const result = installExtensionResultsMap.get(key);463if (!result) {464task.cancel();465installExtensionResultsMap.set(key, getErrorResult(task));466}467// If the extension is installed by a root task and the root task is failed, then uninstall the extension468else if (result.local && root && !installExtensionResultsMap.get(`${root.identifier.id.toLowerCase()}-${task.options.profileLocation.toString()}`)?.local) {469rollbackTasks.push(this.createUninstallExtensionTask(result.local, { versionOnly: true, profileLocation: task.options.profileLocation }));470installExtensionResultsMap.set(key, getErrorResult(task));471}472}473for (const [key, { task }] of installingExtensionsMap) {474const result = installExtensionResultsMap.get(key);475if (!result?.local) {476continue;477}478if (task.options.donotIncludePackAndDependencies) {479continue;480}481const depsOrPacks = getAllDepsAndPacks(result.local, task.options.profileLocation, [result.local.identifier.id.toLowerCase()]).slice(1);482if (depsOrPacks.some(depOrPack => installingExtensionsMap.has(`${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`) && !installExtensionResultsMap.get(`${depOrPack.toLowerCase()}-${task.options.profileLocation.toString()}`)?.local)) {483rollbackTasks.push(this.createUninstallExtensionTask(result.local, { versionOnly: true, profileLocation: task.options.profileLocation }));484installExtensionResultsMap.set(key, getErrorResult(task));485}486}487488if (rollbackTasks.length) {489await Promise.allSettled(rollbackTasks.map(async rollbackTask => {490try {491await rollbackTask.run();492this.logService.info('Rollback: Uninstalled extension', rollbackTask.extension.identifier.id);493} catch (error) {494this.logService.warn('Rollback: Error while uninstalling extension', rollbackTask.extension.identifier.id, getErrorMessage(error));495}496}));497}498} finally {499// Finally, remove all the tasks from the cache500for (const { task } of installingExtensionsMap.values()) {501if (task.source && !URI.isUri(task.source)) {502this.installingExtensions.delete(getInstallExtensionTaskKey(task.source, task.options.profileLocation));503}504}505}506const results = [...installExtensionResultsMap.values()];507for (const result of results) {508if (result.local) {509this.logService.info(`Extension installed successfully:`, result.identifier.id, result.profileLocation.toString());510}511}512this._onDidInstallExtensions.fire(results);513return results;514}515516private async getOtherProfilesToUpdateExtension(tasks: IInstallExtensionTask[]): Promise<[URI, IInstallExtensionTask][]> {517const otherProfilesToUpdate: [URI, IInstallExtensionTask][] = [];518const profileExtensionsCache = new ResourceMap<ILocalExtension[]>();519for (const task of tasks) {520if (task.operation !== InstallOperation.Update521|| task.options.isApplicationScoped522|| task.options.pinned523|| task.options.installGivenVersion524|| URI.isUri(task.source)525) {526continue;527}528for (const profile of this.userDataProfilesService.profiles) {529if (this.uriIdentityService.extUri.isEqual(profile.extensionsResource, task.options.profileLocation)) {530continue;531}532let installedExtensions = profileExtensionsCache.get(profile.extensionsResource);533if (!installedExtensions) {534installedExtensions = await this.getInstalled(ExtensionType.User, profile.extensionsResource);535profileExtensionsCache.set(profile.extensionsResource, installedExtensions);536}537const installedExtension = installedExtensions.find(e => areSameExtensions(e.identifier, task.identifier));538if (installedExtension && !installedExtension.pinned) {539otherProfilesToUpdate.push([profile.extensionsResource, task]);540}541}542}543return otherProfilesToUpdate;544}545546private canWaitForTask(taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask): boolean {547for (const [, { task, waitingTasks }] of this.installingExtensions.entries()) {548if (task === taskToWait) {549// Cannot be waited, If taskToWaitFor is waiting for taskToWait550if (waitingTasks.includes(taskToWaitFor)) {551return false;552}553// Cannot be waited, If taskToWaitFor is waiting for tasks waiting for taskToWait554if (waitingTasks.some(waitingTask => this.canWaitForTask(waitingTask, taskToWaitFor))) {555return false;556}557}558// Cannot be waited, if the taskToWait cannot be waited for the task created the taskToWaitFor559// Because, the task waits for the tasks it created560if (task === taskToWaitFor && waitingTasks[0] && !this.canWaitForTask(taskToWait, waitingTasks[0])) {561return false;562}563}564return true;565}566567private async joinAllSettled<T>(promises: Promise<T>[], errorCode?: ExtensionManagementErrorCode): Promise<T[]> {568const results: T[] = [];569const errors: ExtensionManagementError[] = [];570const promiseResults = await Promise.allSettled(promises);571for (const r of promiseResults) {572if (r.status === 'fulfilled') {573results.push(r.value);574} else {575errors.push(toExtensionManagementError(r.reason, errorCode));576}577}578579if (!errors.length) {580return results;581}582583// Throw if there are errors584if (errors.length === 1) {585throw errors[0];586}587588let error = new ExtensionManagementError('', ExtensionManagementErrorCode.Unknown);589for (const current of errors) {590error = new ExtensionManagementError(591error.message ? `${error.message}, ${current.message}` : current.message,592current.code !== ExtensionManagementErrorCode.Unknown && current.code !== ExtensionManagementErrorCode.Internal ? current.code : error.code593);594}595throw error;596}597598private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, preferPreRelease: boolean, productVersion: IProductVersion, installed: ILocalExtension[]): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> {599if (!this.galleryService.isEnabled()) {600return [];601}602603const knownIdentifiers: IExtensionIdentifier[] = [];604605const allDependenciesAndPacks: { gallery: IGalleryExtension; manifest: IExtensionManifest }[] = [];606const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise<void> => {607knownIdentifiers.push(extensionIdentifier);608const dependecies: string[] = manifest.extensionDependencies ? manifest.extensionDependencies.filter(dep => !installed.some(e => areSameExtensions(e.identifier, { id: dep }))) : [];609const dependenciesAndPackExtensions = [...dependecies];610if (manifest.extensionPack) {611const existing = installed.find(e => areSameExtensions(e.identifier, extensionIdentifier));612for (const extension of manifest.extensionPack) {613// add only those extensions which are new in currently installed extension614if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {615if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {616dependenciesAndPackExtensions.push(extension);617}618}619}620}621622if (dependenciesAndPackExtensions.length) {623// filter out known extensions624const ids = dependenciesAndPackExtensions.filter(id => knownIdentifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id })));625if (ids.length) {626const galleryExtensions = await this.galleryService.getExtensions(ids.map(id => ({ id, preRelease: preferPreRelease })), CancellationToken.None);627for (const galleryExtension of galleryExtensions) {628if (knownIdentifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {629continue;630}631const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier));632let compatible;633try {634compatible = await this.checkAndGetCompatibleVersion(galleryExtension, false, preferPreRelease, productVersion);635} catch (error) {636if (!isDependency) {637this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id, getErrorMessage(error));638continue;639} else {640throw error;641}642}643allDependenciesAndPacks.push({ gallery: compatible.extension, manifest: compatible.manifest });644await collectDependenciesAndPackExtensionsToInstall(compatible.extension.identifier, compatible.manifest);645}646}647}648};649650await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest);651return allDependenciesAndPacks;652}653654private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean, productVersion: IProductVersion): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> {655let compatibleExtension: IGalleryExtension | null;656657const extensionsControlManifest = await this.getExtensionsControlManifest();658if (isMalicious(extension.identifier, extensionsControlManifest.malicious)) {659throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious);660}661662const deprecationInfo = extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()];663if (deprecationInfo?.extension?.autoMigrate) {664this.logService.info(`The '${extension.identifier.id}' extension is deprecated, fetching the compatible '${deprecationInfo.extension.id}' extension instead.`);665compatibleExtension = (await this.galleryService.getExtensions([{ id: deprecationInfo.extension.id, preRelease: deprecationInfo.extension.preRelease }], { targetPlatform: await this.getTargetPlatform(), compatible: true, productVersion }, CancellationToken.None))[0];666if (!compatibleExtension) {667throw new ExtensionManagementError(nls.localize('notFoundDeprecatedReplacementExtension', "Can't install '{0}' extension since it was deprecated and the replacement extension '{1}' can't be found.", extension.identifier.id, deprecationInfo.extension.id), ExtensionManagementErrorCode.Deprecated);668}669}670671else {672if (await this.canInstall(extension) !== true) {673const targetPlatform = await this.getTargetPlatform();674throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for the {2} platform.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform);675}676677compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease, productVersion);678if (!compatibleExtension) {679const incompatibleApiProposalsMessages: string[] = [];680if (!areApiProposalsCompatible(extension.properties.enabledApiProposals ?? [], incompatibleApiProposalsMessages)) {681throw new ExtensionManagementError(nls.localize('incompatibleAPI', "Can't install '{0}' extension. {1}", extension.displayName ?? extension.identifier.id, incompatibleApiProposalsMessages[0]), ExtensionManagementErrorCode.IncompatibleApi);682}683/** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */684if (!installPreRelease && extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) {685throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.displayName ?? extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound);686}687throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);688}689}690691this.logService.info('Getting Manifest...', compatibleExtension.identifier.id);692const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);693if (manifest === null) {694throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, ExtensionManagementErrorCode.Invalid);695}696697if (manifest.version !== compatibleExtension.version) {698throw new ExtensionManagementError(`Cannot install '${compatibleExtension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid);699}700701return { extension: compatibleExtension, manifest };702}703704protected async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean, productVersion: IProductVersion): Promise<IGalleryExtension | null> {705const targetPlatform = await this.getTargetPlatform();706let compatibleExtension: IGalleryExtension | null = null;707708if (!sameVersion && extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) {709compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: includePreRelease }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null;710}711712if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform, productVersion)) {713compatibleExtension = extension;714}715716if (!compatibleExtension) {717if (sameVersion) {718compatibleExtension = (await this.galleryService.getExtensions([{ ...extension.identifier, version: extension.version }], { targetPlatform, compatible: true, productVersion }, CancellationToken.None))[0] || null;719} else {720compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform, productVersion);721}722}723724return compatibleExtension;725}726727private getUninstallExtensionTaskKey(identifier: IExtensionIdentifier, profileLocation: URI, version?: string): string {728return `${identifier.id.toLowerCase()}${version ? `-${version}` : ''}@${profileLocation.toString()}`;729}730731async uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise<void> {732733const getUninstallExtensionTaskKey = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions) => this.getUninstallExtensionTaskKey(extension.identifier, uninstallOptions.profileLocation, uninstallOptions.versionOnly ? extension.manifest.version : undefined);734735const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions): void => {736let installTaskToWaitFor: IInstallExtensionTask | undefined;737for (const { task } of this.installingExtensions.values()) {738if (!(task.source instanceof URI) && areSameExtensions(task.identifier, extension.identifier) && this.uriIdentityService.extUri.isEqual(task.options.profileLocation, uninstallOptions.profileLocation)) {739installTaskToWaitFor = task;740break;741}742}743const task = this.createUninstallExtensionTask(extension, uninstallOptions);744this.uninstallingExtensions.set(getUninstallExtensionTaskKey(task.extension, uninstallOptions), task);745this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());746this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });747allTasks.push({ task, installTaskToWaitFor });748};749750const postUninstallExtension = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions, error?: ExtensionManagementError): void => {751if (error) {752this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString(), error.message);753} else {754this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());755}756reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error });757this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });758};759760const allTasks: { task: IUninstallExtensionTask; installTaskToWaitFor?: IInstallExtensionTask }[] = [];761const processedTasks: IUninstallExtensionTask[] = [];762const alreadyRequestedUninstalls: Promise<void>[] = [];763const extensionsToRemove: ILocalExtension[] = [];764765const installedExtensionsMap = new ResourceMap<ILocalExtension[]>();766const getInstalledExtensions = async (profileLocation: URI) => {767let installed = installedExtensionsMap.get(profileLocation);768if (!installed) {769installedExtensionsMap.set(profileLocation, installed = await this.getInstalled(ExtensionType.User, profileLocation));770}771return installed;772};773774for (const { extension, options } of extensions) {775const uninstallOptions: UninstallExtensionTaskOptions = {776...options,777profileLocation: extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options?.profileLocation ?? this.getCurrentExtensionsManifestLocation()778};779const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension, uninstallOptions));780if (uninstallExtensionTask) {781this.logService.info('Extensions is already requested to uninstall', extension.identifier.id);782alreadyRequestedUninstalls.push(uninstallExtensionTask.waitUntilTaskIsFinished());783} else {784createUninstallExtensionTask(extension, uninstallOptions);785}786787if (uninstallOptions.remove || extension.isApplicationScoped) {788if (uninstallOptions.remove) {789extensionsToRemove.push(extension);790}791for (const profile of this.userDataProfilesService.profiles) {792if (this.uriIdentityService.extUri.isEqual(profile.extensionsResource, uninstallOptions.profileLocation)) {793continue;794}795const installed = await getInstalledExtensions(profile.extensionsResource);796const profileExtension = installed.find(e => areSameExtensions(e.identifier, extension.identifier));797if (profileExtension) {798const uninstallOptionsWithProfile = { ...uninstallOptions, profileLocation: profile.extensionsResource };799const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(profileExtension, uninstallOptionsWithProfile));800if (uninstallExtensionTask) {801this.logService.info('Extensions is already requested to uninstall', profileExtension.identifier.id);802alreadyRequestedUninstalls.push(uninstallExtensionTask.waitUntilTaskIsFinished());803} else {804createUninstallExtensionTask(profileExtension, uninstallOptionsWithProfile);805}806}807}808}809}810811try {812for (const { task } of allTasks.slice(0)) {813const installed = await getInstalledExtensions(task.options.profileLocation);814815if (task.options.donotIncludePack) {816this.logService.info('Uninstalling the extension without including packed extension', `${task.extension.identifier.id}@${task.extension.manifest.version}`);817} else {818const packedExtensions = this.getAllPackExtensionsToUninstall(task.extension, installed);819for (const packedExtension of packedExtensions) {820if (this.uninstallingExtensions.has(getUninstallExtensionTaskKey(packedExtension, task.options))) {821this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id);822} else {823createUninstallExtensionTask(packedExtension, task.options);824}825}826}827if (task.options.donotCheckDependents) {828this.logService.info('Uninstalling the extension without checking dependents', `${task.extension.identifier.id}@${task.extension.manifest.version}`);829} else {830this.checkForDependents(allTasks.map(({ task }) => task.extension), installed, task.extension);831}832}833834// Uninstall extensions in parallel and wait until all extensions are uninstalled / failed835await this.joinAllSettled(allTasks.map(async ({ task, installTaskToWaitFor }) => {836try {837// Wait for opposite task if it exists838if (installTaskToWaitFor) {839this.logService.info('Waiting for existing install task to complete before uninstalling', task.extension.identifier.id);840try {841await installTaskToWaitFor.waitUntilTaskIsFinished();842this.logService.info('Finished waiting for install task, proceeding with uninstall', task.extension.identifier.id);843} catch (error) {844this.logService.info('Install task failed, proceeding with uninstall anyway', task.extension.identifier.id, getErrorMessage(error));845}846}847848await task.run();849await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, task.options, CancellationToken.None)));850// only report if extension has a mapped gallery extension and not in web. UUID identifies the gallery extension.851if (task.extension.identifier.uuid && !isWeb) {852try {853await this.galleryService.reportStatistic(task.extension.manifest.publisher, task.extension.manifest.name, task.extension.manifest.version, StatisticType.Uninstall);854} catch (error) { /* ignore */ }855}856} catch (e) {857const error = toExtensionManagementError(e);858postUninstallExtension(task.extension, task.options, error);859throw error;860} finally {861processedTasks.push(task);862}863}));864865if (alreadyRequestedUninstalls.length) {866await this.joinAllSettled(alreadyRequestedUninstalls);867}868869for (const { task } of allTasks) {870postUninstallExtension(task.extension, task.options);871}872873if (extensionsToRemove.length) {874await this.joinAllSettled(extensionsToRemove.map(extension => this.deleteExtension(extension)));875}876} catch (e) {877const error = toExtensionManagementError(e);878for (const { task } of allTasks) {879// cancel the tasks880try { task.cancel(); } catch (error) { /* ignore */ }881if (!processedTasks.includes(task)) {882postUninstallExtension(task.extension, task.options, error);883}884}885throw error;886} finally {887// Remove tasks from cache888for (const { task } of allTasks) {889if (!this.uninstallingExtensions.delete(getUninstallExtensionTaskKey(task.extension, task.options))) {890this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id);891}892}893}894}895896private checkForDependents(extensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], extensionToUninstall: ILocalExtension): void {897for (const extension of extensionsToUninstall) {898const dependents = this.getDependents(extension, installed);899if (dependents.length) {900const remainingDependents = dependents.filter(dependent => !extensionsToUninstall.some(e => areSameExtensions(e.identifier, dependent.identifier)));901if (remainingDependents.length) {902throw new Error(this.getDependentsErrorMessage(extension, remainingDependents, extensionToUninstall));903}904}905}906}907908private getDependentsErrorMessage(dependingExtension: ILocalExtension, dependents: ILocalExtension[], extensionToUninstall: ILocalExtension): string {909if (extensionToUninstall === dependingExtension) {910if (dependents.length === 1) {911return nls.localize('singleDependentError', "Cannot uninstall '{0}' extension. '{1}' extension depends on this.",912extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);913}914if (dependents.length === 2) {915return nls.localize('twoDependentsError', "Cannot uninstall '{0}' extension. '{1}' and '{2}' extensions depend on this.",916extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);917}918return nls.localize('multipleDependentsError', "Cannot uninstall '{0}' extension. '{1}', '{2}' and other extension depend on this.",919extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);920}921if (dependents.length === 1) {922return nls.localize('singleIndirectDependentError', "Cannot uninstall '{0}' extension . It includes uninstalling '{1}' extension and '{2}' extension depends on this.",923extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName924|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);925}926if (dependents.length === 2) {927return nls.localize('twoIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}' and '{3}' extensions depend on this.",928extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName929|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);930}931return nls.localize('multipleIndirectDependentsError', "Cannot uninstall '{0}' extension. It includes uninstalling '{1}' extension and '{2}', '{3}' and other extensions depend on this.",932extensionToUninstall.manifest.displayName || extensionToUninstall.manifest.name, dependingExtension.manifest.displayName933|| dependingExtension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);934935}936937private getAllPackExtensionsToUninstall(extension: ILocalExtension, installed: ILocalExtension[], checked: ILocalExtension[] = []): ILocalExtension[] {938if (checked.indexOf(extension) !== -1) {939return [];940}941if (areSameExtensions(extension.identifier, { id: this.productService.defaultChatAgent.extensionId })) {942return [];943}944checked.push(extension);945const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];946if (extensionsPack.length) {947const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));948const packOfPackedExtensions: ILocalExtension[] = [];949for (const packedExtension of packedExtensions) {950packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));951}952return [...packedExtensions, ...packOfPackedExtensions];953}954return [];955}956957private getDependents(extension: ILocalExtension, installed: ILocalExtension[]): ILocalExtension[] {958return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));959}960961private async updateControlCache(): Promise<IExtensionsControlManifest> {962try {963this.logService.trace('ExtensionManagementService.updateControlCache');964return await this.galleryService.getExtensionsControlManifest();965} catch (err) {966this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest', getErrorMessage(err));967return { malicious: [], deprecated: {}, search: [] };968}969}970971protected abstract getCurrentExtensionsManifestLocation(): URI;972protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask;973protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask;974protected abstract copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata?: Partial<Metadata>): Promise<ILocalExtension>;975protected abstract moveExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata?: Partial<Metadata>): Promise<ILocalExtension>;976protected abstract removeExtension(extension: ILocalExtension, fromProfileLocation: URI): Promise<void>;977protected abstract deleteExtension(extension: ILocalExtension): Promise<void>;978}979980export function toExtensionManagementError(error: Error, code?: ExtensionManagementErrorCode): ExtensionManagementError {981if (error instanceof ExtensionManagementError) {982return error;983}984let extensionManagementError: ExtensionManagementError;985if (error instanceof ExtensionGalleryError) {986extensionManagementError = new ExtensionManagementError(error.message, error.code === ExtensionGalleryErrorCode.DownloadFailedWriting ? ExtensionManagementErrorCode.DownloadFailedWriting : ExtensionManagementErrorCode.Gallery);987} else {988extensionManagementError = new ExtensionManagementError(error.message, isCancellationError(error) ? ExtensionManagementErrorCode.Cancelled : (code ?? ExtensionManagementErrorCode.Internal));989}990extensionManagementError.stack = error.stack;991return extensionManagementError;992}993994function reportTelemetry(telemetryService: ITelemetryService, eventName: string,995{996extensionData,997verificationStatus,998duration,999error,1000source,1001durationSinceUpdate1002}: {1003extensionData: object;1004verificationStatus?: ExtensionSignatureVerificationCode;1005duration?: number;1006durationSinceUpdate?: number;1007source?: string;1008error?: ExtensionManagementError | ExtensionGalleryError;1009}): void {10101011/* __GDPR__1012"extensionGallery:install" : {1013"owner": "sandy081",1014"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1015"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1016"durationSinceUpdate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },1017"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },1018"recommendationReason": { "retiredFromVersion": "1.23.0", "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },1019"verificationStatus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },1020"source": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },1021"${include}": [1022"${GalleryExtensionTelemetryData}"1023]1024}1025*/1026/* __GDPR__1027"extensionGallery:uninstall" : {1028"owner": "sandy081",1029"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1030"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1031"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },1032"${include}": [1033"${GalleryExtensionTelemetryData}"1034]1035}1036*/1037/* __GDPR__1038"extensionGallery:update" : {1039"owner": "sandy081",1040"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1041"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1042"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },1043"verificationStatus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },1044"source": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },1045"${include}": [1046"${GalleryExtensionTelemetryData}"1047]1048}1049*/1050telemetryService.publicLog(eventName, {1051...extensionData,1052source,1053duration,1054durationSinceUpdate,1055success: !error,1056errorcode: error?.code,1057verificationStatus: verificationStatus === ExtensionSignatureVerificationCode.Success ? 'Verified' : (verificationStatus ?? 'Unverified')1058});1059}10601061export abstract class AbstractExtensionTask<T> {10621063private readonly barrier = new Barrier();1064private cancellablePromise: CancelablePromise<T> | undefined;10651066async waitUntilTaskIsFinished(): Promise<T> {1067await this.barrier.wait();1068return this.cancellablePromise!;1069}10701071run(): Promise<T> {1072if (!this.cancellablePromise) {1073this.cancellablePromise = createCancelablePromise(token => this.doRun(token));1074}1075this.barrier.open();1076return this.cancellablePromise;1077}10781079cancel(): void {1080if (!this.cancellablePromise) {1081this.cancellablePromise = createCancelablePromise(token => {1082return new Promise((c, e) => {1083const disposable = token.onCancellationRequested(() => {1084disposable.dispose();1085e(new CancellationError());1086});1087});1088});1089this.barrier.open();1090}1091this.cancellablePromise.cancel();1092}10931094protected abstract doRun(token: CancellationToken): Promise<T>;1095}109610971098