Path: blob/main/src/vs/platform/extensionManagement/node/extensionManagementService.ts
3296 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 * as fs from 'fs';6import { Promises, Queue } from '../../../base/common/async.js';7import { VSBuffer } from '../../../base/common/buffer.js';8import { CancellationToken } from '../../../base/common/cancellation.js';9import { IStringDictionary } from '../../../base/common/collections.js';10import { CancellationError, getErrorMessage } from '../../../base/common/errors.js';11import { Emitter } from '../../../base/common/event.js';12import { hash } from '../../../base/common/hash.js';13import { Disposable } from '../../../base/common/lifecycle.js';14import { ResourceMap, ResourceSet } from '../../../base/common/map.js';15import { Schemas } from '../../../base/common/network.js';16import * as path from '../../../base/common/path.js';17import { joinPath } from '../../../base/common/resources.js';18import * as semver from '../../../base/common/semver/semver.js';19import { isBoolean, isDefined, isUndefined } from '../../../base/common/types.js';20import { URI } from '../../../base/common/uri.js';21import { generateUuid } from '../../../base/common/uuid.js';22import * as pfs from '../../../base/node/pfs.js';23import { extract, IFile, zip } from '../../../base/node/zip.js';24import * as nls from '../../../nls.js';25import { IDownloadService } from '../../download/common/download.js';26import { INativeEnvironmentService } from '../../environment/common/environment.js';27import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, toExtensionManagementError, UninstallExtensionTaskOptions } from '../common/abstractExtensionManagementService.js';28import {29ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation,30Metadata, InstallOptions,31IProductVersion,32EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT,33ExtensionSignatureVerificationCode,34computeSize,35IAllowedExtensionsService,36VerifyExtensionSignatureConfigKey,37shouldRequireRepositorySignatureFor,38} from '../common/extensionManagement.js';39import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from '../common/extensionManagementUtil.js';40import { IExtensionsProfileScannerService, IScannedProfileExtension } from '../common/extensionsProfileScannerService.js';41import { IExtensionsScannerService, IScannedExtension, ManifestMetadata, UserExtensionsScanOptions } from '../common/extensionsScannerService.js';42import { ExtensionsDownloader } from './extensionDownloader.js';43import { ExtensionsLifecycle } from './extensionLifecycle.js';44import { fromExtractError, getManifest } from './extensionManagementUtil.js';45import { ExtensionsManifestCache } from './extensionsManifestCache.js';46import { DidChangeProfileExtensionsEvent, ExtensionsWatcher } from './extensionsWatcher.js';47import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js';48import { isEngineValid } from '../../extensions/common/extensionValidator.js';49import { FileChangesEvent, FileChangeType, FileOperationResult, IFileService, IFileStat, toFileOperationResult } from '../../files/common/files.js';50import { IInstantiationService, refineServiceDecorator } from '../../instantiation/common/instantiation.js';51import { ILogService } from '../../log/common/log.js';52import { IProductService } from '../../product/common/productService.js';53import { ITelemetryService } from '../../telemetry/common/telemetry.js';54import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';55import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';56import { IConfigurationService } from '../../configuration/common/configuration.js';57import { IExtensionGalleryManifestService } from '../common/extensionGalleryManifest.js';5859export const INativeServerExtensionManagementService = refineServiceDecorator<IExtensionManagementService, INativeServerExtensionManagementService>(IExtensionManagementService);60export interface INativeServerExtensionManagementService extends IExtensionManagementService {61readonly _serviceBrand: undefined;62scanAllUserInstalledExtensions(): Promise<ILocalExtension[]>;63scanInstalledExtensionAtLocation(location: URI): Promise<ILocalExtension | null>;64deleteExtensions(...extensions: IExtension[]): Promise<void>;65}6667type ExtractExtensionResult = { readonly local: ILocalExtension; readonly verificationStatus?: ExtensionSignatureVerificationCode };6869const DELETED_FOLDER_POSTFIX = '.vsctmp';7071export class ExtensionManagementService extends AbstractExtensionManagementService implements INativeServerExtensionManagementService {7273private readonly extensionsScanner: ExtensionsScanner;74private readonly manifestCache: ExtensionsManifestCache;75private readonly extensionsDownloader: ExtensionsDownloader;7677private readonly extractingGalleryExtensions = new Map<string, Promise<ExtractExtensionResult>>();7879constructor(80@IExtensionGalleryService galleryService: IExtensionGalleryService,81@ITelemetryService telemetryService: ITelemetryService,82@ILogService logService: ILogService,83@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,84@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,85@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,86@IDownloadService private downloadService: IDownloadService,87@IInstantiationService private readonly instantiationService: IInstantiationService,88@IFileService private readonly fileService: IFileService,89@IConfigurationService private readonly configurationService: IConfigurationService,90@IExtensionGalleryManifestService protected readonly extensionGalleryManifestService: IExtensionGalleryManifestService,91@IProductService productService: IProductService,92@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,93@IUriIdentityService uriIdentityService: IUriIdentityService,94@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService95) {96super(galleryService, telemetryService, uriIdentityService, logService, productService, allowedExtensionsService, userDataProfilesService);97const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle));98this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));99this.manifestCache = this._register(new ExtensionsManifestCache(userDataProfilesService, fileService, uriIdentityService, this, this.logService));100this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));101102const extensionsWatcher = this._register(new ExtensionsWatcher(this, this.extensionsScannerService, userDataProfilesService, extensionsProfileScannerService, uriIdentityService, fileService, logService));103this._register(extensionsWatcher.onDidChangeExtensionsByAnotherSource(e => this.onDidChangeExtensionsFromAnotherSource(e)));104this.watchForExtensionsNotInstalledBySystem();105}106107private _targetPlatformPromise: Promise<TargetPlatform> | undefined;108getTargetPlatform(): Promise<TargetPlatform> {109if (!this._targetPlatformPromise) {110this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService);111}112return this._targetPlatformPromise;113}114115async zip(extension: ILocalExtension): Promise<URI> {116this.logService.trace('ExtensionManagementService#zip', extension.identifier.id);117const files = await this.collectFiles(extension);118const location = await zip(joinPath(this.extensionsDownloader.extensionsDownloadDir, generateUuid()).fsPath, files);119return URI.file(location);120}121122async getManifest(vsix: URI): Promise<IExtensionManifest> {123const { location, cleanup } = await this.downloadVsix(vsix);124const zipPath = path.resolve(location.fsPath);125try {126return await getManifest(zipPath);127} finally {128await cleanup();129}130}131132getInstalled(type?: ExtensionType, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }, language?: string): Promise<ILocalExtension[]> {133return this.extensionsScanner.scanExtensions(type ?? null, profileLocation, productVersion, language);134}135136scanAllUserInstalledExtensions(): Promise<ILocalExtension[]> {137return this.extensionsScanner.scanAllUserExtensions();138}139140scanInstalledExtensionAtLocation(location: URI): Promise<ILocalExtension | null> {141return this.extensionsScanner.scanUserExtensionAtLocation(location);142}143144async install(vsix: URI, options: InstallOptions = {}): Promise<ILocalExtension> {145this.logService.trace('ExtensionManagementService#install', vsix.toString());146147const { location, cleanup } = await this.downloadVsix(vsix);148149try {150const manifest = await getManifest(path.resolve(location.fsPath));151const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);152if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, this.productService.version, this.productService.date)) {153throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version));154}155156const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extensionId, version: manifest.version, publisherDisplayName: undefined });157if (allowedToInstall !== true) {158throw new Error(nls.localize('notAllowed', "This extension cannot be installed because {0}", allowedToInstall.value));159}160161const results = await this.installExtensions([{ manifest, extension: location, options }]);162const result = results.find(({ identifier }) => areSameExtensions(identifier, { id: extensionId }));163if (result?.local) {164return result.local;165}166if (result?.error) {167throw result.error;168}169throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extensionId}`));170} finally {171await cleanup();172}173}174175async installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension> {176this.logService.trace('ExtensionManagementService#installFromLocation', location.toString());177const local = await this.extensionsScanner.scanUserExtensionAtLocation(location);178if (!local || !local.manifest.name || !local.manifest.version) {179throw new Error(`Cannot find a valid extension from the location ${location.toString()}`);180}181await this.addExtensionsToProfile([[local, { source: 'resource' }]], profileLocation);182this.logService.info('Successfully installed extension', local.identifier.id, profileLocation.toString());183return local;184}185186async installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]> {187this.logService.trace('ExtensionManagementService#installExtensionsFromProfile', extensions, fromProfileLocation.toString(), toProfileLocation.toString());188const extensionsToInstall = (await this.getInstalled(ExtensionType.User, fromProfileLocation)).filter(e => extensions.some(id => areSameExtensions(id, e.identifier)));189if (extensionsToInstall.length) {190const metadata = await Promise.all(extensionsToInstall.map(e => this.extensionsScanner.scanMetadata(e, fromProfileLocation)));191await this.addExtensionsToProfile(extensionsToInstall.map((e, index) => [e, metadata[index]]), toProfileLocation);192this.logService.info('Successfully installed extensions', extensionsToInstall.map(e => e.identifier.id), toProfileLocation.toString());193}194return extensionsToInstall;195}196197async updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>, profileLocation: URI): Promise<ILocalExtension> {198this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);199if (metadata.isPreReleaseVersion) {200metadata.preRelease = true;201metadata.hasPreReleaseVersion = true;202}203// unset if false204if (metadata.isMachineScoped === false) {205metadata.isMachineScoped = undefined;206}207if (metadata.isBuiltin === false) {208metadata.isBuiltin = undefined;209}210if (metadata.pinned === false) {211metadata.pinned = undefined;212}213local = await this.extensionsScanner.updateMetadata(local, metadata, profileLocation);214this.manifestCache.invalidate(profileLocation);215this._onDidUpdateExtensionMetadata.fire({ local, profileLocation });216return local;217}218219protected deleteExtension(extension: ILocalExtension): Promise<void> {220return this.extensionsScanner.deleteExtension(extension, 'remove');221}222223protected copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial<Metadata>): Promise<ILocalExtension> {224return this.extensionsScanner.copyExtension(extension, fromProfileLocation, toProfileLocation, metadata);225}226227protected moveExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial<Metadata>): Promise<ILocalExtension> {228return this.extensionsScanner.moveExtension(extension, fromProfileLocation, toProfileLocation, metadata);229}230231protected removeExtension(extension: ILocalExtension, fromProfileLocation: URI): Promise<void> {232return this.extensionsScanner.removeExtension(extension.identifier, fromProfileLocation);233}234235copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise<void> {236return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation, { version: this.productService.version, date: this.productService.date });237}238239deleteExtensions(...extensions: IExtension[]): Promise<void> {240return this.extensionsScanner.setExtensionsForRemoval(...extensions);241}242243async cleanUp(): Promise<void> {244this.logService.trace('ExtensionManagementService#cleanUp');245try {246await this.extensionsScanner.cleanUp();247} catch (error) {248this.logService.error(error);249}250}251252async download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise<URI> {253const { location } = await this.downloadExtension(extension, operation, !donotVerifySignature);254return location;255}256257private async downloadVsix(vsix: URI): Promise<{ location: URI; cleanup: () => Promise<void> }> {258if (vsix.scheme === Schemas.file) {259return { location: vsix, async cleanup() { } };260}261this.logService.trace('Downloading extension from', vsix.toString());262const location = joinPath(this.extensionsDownloader.extensionsDownloadDir, generateUuid());263await this.downloadService.download(vsix, location);264this.logService.info('Downloaded extension to', location.toString());265const cleanup = async () => {266try {267await this.fileService.del(location);268} catch (error) {269this.logService.error(error);270}271};272return { location, cleanup };273}274275protected getCurrentExtensionsManifestLocation(): URI {276return this.userDataProfilesService.defaultProfile.extensionsResource;277}278279protected createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask {280const extensionKey = extension instanceof URI ? new ExtensionKey({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, manifest.version) : ExtensionKey.create(extension);281return this.instantiationService.createInstance(InstallExtensionInProfileTask, extensionKey, manifest, extension, options, (operation, token) => {282if (extension instanceof URI) {283return this.extractVSIX(extensionKey, extension, options, token);284}285let promise = this.extractingGalleryExtensions.get(extensionKey.toString());286if (!promise) {287this.extractingGalleryExtensions.set(extensionKey.toString(), promise = this.downloadAndExtractGalleryExtension(extensionKey, extension, operation, options, token));288promise.finally(() => this.extractingGalleryExtensions.delete(extensionKey.toString()));289}290return promise;291}, this.extensionsScanner);292}293294protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask {295return new UninstallExtensionInProfileTask(extension, options, this.extensionsProfileScannerService);296}297298private async downloadAndExtractGalleryExtension(extensionKey: ExtensionKey, gallery: IGalleryExtension, operation: InstallOperation, options: InstallExtensionTaskOptions, token: CancellationToken): Promise<ExtractExtensionResult> {299const { verificationStatus, location } = await this.downloadExtension(gallery, operation, !options.donotVerifySignature, options.context?.[EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT]);300try {301302if (token.isCancellationRequested) {303throw new CancellationError();304}305306// validate manifest307const manifest = await getManifest(location.fsPath);308if (!new ExtensionKey(gallery.identifier, gallery.version).equals(new ExtensionKey({ id: getGalleryExtensionId(manifest.publisher, manifest.name) }, manifest.version))) {309throw new ExtensionManagementError(nls.localize('invalidManifest', "Cannot install '{0}' extension because of manifest mismatch with Marketplace", gallery.identifier.id), ExtensionManagementErrorCode.Invalid);310}311312const local = await this.extensionsScanner.extractUserExtension(313extensionKey,314location.fsPath,315false,316token);317318if (verificationStatus !== ExtensionSignatureVerificationCode.Success && this.environmentService.isBuilt) {319try {320await this.extensionsDownloader.delete(location);321} catch (e) {322/* Ignore */323this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e));324}325}326327return { local, verificationStatus };328} catch (error) {329try {330await this.extensionsDownloader.delete(location);331} catch (e) {332/* Ignore */333this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e));334}335throw toExtensionManagementError(error);336}337}338339private async downloadExtension(extension: IGalleryExtension, operation: InstallOperation, verifySignature: boolean, clientTargetPlatform?: TargetPlatform): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionSignatureVerificationCode | undefined }> {340if (verifySignature) {341const value = this.configurationService.getValue(VerifyExtensionSignatureConfigKey);342verifySignature = isBoolean(value) ? value : true;343}344const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform);345const shouldRequireSignature = shouldRequireRepositorySignatureFor(extension.private, await this.extensionGalleryManifestService.getExtensionGalleryManifest());346347if (348verificationStatus !== ExtensionSignatureVerificationCode.Success349&& !(verificationStatus === ExtensionSignatureVerificationCode.NotSigned && !shouldRequireSignature)350&& verifySignature351&& this.environmentService.isBuilt352&& (await this.getTargetPlatform()) !== TargetPlatform.LINUX_ARMHF353) {354try {355await this.extensionsDownloader.delete(location);356} catch (e) {357/* Ignore */358this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e));359}360361if (!verificationStatus) {362throw new ExtensionManagementError(nls.localize('signature verification not executed', "Signature verification was not executed."), ExtensionManagementErrorCode.SignatureVerificationInternal);363}364365switch (verificationStatus) {366case ExtensionSignatureVerificationCode.PackageIntegrityCheckFailed:367case ExtensionSignatureVerificationCode.SignatureIsInvalid:368case ExtensionSignatureVerificationCode.SignatureManifestIsInvalid:369case ExtensionSignatureVerificationCode.SignatureIntegrityCheckFailed:370case ExtensionSignatureVerificationCode.EntryIsMissing:371case ExtensionSignatureVerificationCode.EntryIsTampered:372case ExtensionSignatureVerificationCode.Untrusted:373case ExtensionSignatureVerificationCode.CertificateRevoked:374case ExtensionSignatureVerificationCode.SignatureIsNotValid:375case ExtensionSignatureVerificationCode.SignatureArchiveHasTooManyEntries:376case ExtensionSignatureVerificationCode.NotSigned:377throw new ExtensionManagementError(nls.localize('signature verification failed', "Signature verification failed with '{0}' error.", verificationStatus), ExtensionManagementErrorCode.SignatureVerificationFailed);378}379380throw new ExtensionManagementError(nls.localize('signature verification failed', "Signature verification failed with '{0}' error.", verificationStatus), ExtensionManagementErrorCode.SignatureVerificationInternal);381}382383return { location, verificationStatus };384}385386private async extractVSIX(extensionKey: ExtensionKey, location: URI, options: InstallExtensionTaskOptions, token: CancellationToken): Promise<ExtractExtensionResult> {387const local = await this.extensionsScanner.extractUserExtension(388extensionKey,389path.resolve(location.fsPath),390isBoolean(options.keepExisting) ? !options.keepExisting : true,391token);392return { local };393}394395private async collectFiles(extension: ILocalExtension): Promise<IFile[]> {396397const collectFilesFromDirectory = async (dir: string): Promise<string[]> => {398let entries = await pfs.Promises.readdir(dir);399entries = entries.map(e => path.join(dir, e));400const stats = await Promise.all(entries.map(e => fs.promises.stat(e)));401let promise: Promise<string[]> = Promise.resolve([]);402stats.forEach((stat, index) => {403const entry = entries[index];404if (stat.isFile()) {405promise = promise.then(result => ([...result, entry]));406}407if (stat.isDirectory()) {408promise = promise409.then(result => collectFilesFromDirectory(entry)410.then(files => ([...result, ...files])));411}412});413return promise;414};415416const files = await collectFilesFromDirectory(extension.location.fsPath);417return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f }));418}419420private async onDidChangeExtensionsFromAnotherSource({ added, removed }: DidChangeProfileExtensionsEvent): Promise<void> {421if (removed) {422const removedExtensions = added && this.uriIdentityService.extUri.isEqual(removed.profileLocation, added.profileLocation)423? removed.extensions.filter(e => added.extensions.every(identifier => !areSameExtensions(identifier, e)))424: removed.extensions;425for (const identifier of removedExtensions) {426this.logService.info('Extensions removed from another source', identifier.id, removed.profileLocation.toString());427this._onDidUninstallExtension.fire({ identifier, profileLocation: removed.profileLocation });428}429}430if (added) {431const extensions = await this.getInstalled(ExtensionType.User, added.profileLocation);432const addedExtensions = extensions.filter(e => added.extensions.some(identifier => areSameExtensions(identifier, e.identifier)));433this._onDidInstallExtensions.fire(addedExtensions.map(local => {434this.logService.info('Extensions added from another source', local.identifier.id, added.profileLocation.toString());435return { identifier: local.identifier, local, profileLocation: added.profileLocation, operation: InstallOperation.None };436}));437}438}439440private readonly knownDirectories = new ResourceSet();441private async watchForExtensionsNotInstalledBySystem(): Promise<void> {442this._register(this.extensionsScanner.onExtract(resource => this.knownDirectories.add(resource)));443const stat = await this.fileService.resolve(this.extensionsScannerService.userExtensionsLocation);444for (const childStat of stat.children ?? []) {445if (childStat.isDirectory) {446this.knownDirectories.add(childStat.resource);447}448}449this._register(this.fileService.watch(this.extensionsScannerService.userExtensionsLocation));450this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e)));451}452453private async onDidFilesChange(e: FileChangesEvent): Promise<void> {454if (!e.affects(this.extensionsScannerService.userExtensionsLocation, FileChangeType.ADDED)) {455return;456}457458const added: ILocalExtension[] = [];459for (const resource of e.rawAdded) {460// Check if this is a known directory461if (this.knownDirectories.has(resource)) {462continue;463}464465// Is not immediate child of extensions resource466if (!this.uriIdentityService.extUri.isEqual(this.uriIdentityService.extUri.dirname(resource), this.extensionsScannerService.userExtensionsLocation)) {467continue;468}469470// .obsolete file changed471if (this.uriIdentityService.extUri.isEqual(resource, this.uriIdentityService.extUri.joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete'))) {472continue;473}474475// Ignore changes to files starting with `.`476if (this.uriIdentityService.extUri.basename(resource).startsWith('.')) {477continue;478}479480// Ignore changes to the deleted folder481if (this.uriIdentityService.extUri.basename(resource).endsWith(DELETED_FOLDER_POSTFIX)) {482continue;483}484485try {486// Check if this is a directory487if (!(await this.fileService.stat(resource)).isDirectory) {488continue;489}490} catch (error) {491if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {492this.logService.error(error);493}494continue;495}496497// Check if this is an extension added by another source498// Extension added by another source will not have installed timestamp499const extension = await this.extensionsScanner.scanUserExtensionAtLocation(resource);500if (extension && extension.installedTimestamp === undefined) {501this.knownDirectories.add(resource);502added.push(extension);503}504}505506if (added.length) {507await this.addExtensionsToProfile(added.map(e => [e, undefined]), this.userDataProfilesService.defaultProfile.extensionsResource);508this.logService.info('Added extensions to default profile from external source', added.map(e => e.identifier.id));509}510}511512private async addExtensionsToProfile(extensions: [ILocalExtension, Metadata | undefined][], profileLocation: URI): Promise<void> {513const localExtensions = extensions.map(e => e[0]);514await this.extensionsScanner.unsetExtensionsForRemoval(...localExtensions.map(extension => ExtensionKey.create(extension)));515await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, profileLocation);516this._onDidInstallExtensions.fire(localExtensions.map(local => ({ local, identifier: local.identifier, operation: InstallOperation.None, profileLocation })));517}518}519520type UpdateMetadataErrorClassification = {521owner: 'sandy081';522comment: 'Update metadata error';523extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension identifier' };524code?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code' };525isProfile?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Is writing into profile' };526};527type UpdateMetadataErrorEvent = {528extensionId: string;529code?: string;530isProfile?: boolean;531};532533export class ExtensionsScanner extends Disposable {534535private readonly obsoletedResource: URI;536private readonly obsoleteFileLimiter: Queue<any>;537538private readonly _onExtract = this._register(new Emitter<URI>());539readonly onExtract = this._onExtract.event;540541private scanAllExtensionPromise = new ResourceMap<Promise<IScannedExtension[]>>();542private scanUserExtensionsPromise = new ResourceMap<Promise<IScannedExtension[]>>();543544constructor(545private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise<void>,546@IFileService private readonly fileService: IFileService,547@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,548@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,549@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,550@ITelemetryService private readonly telemetryService: ITelemetryService,551@ILogService private readonly logService: ILogService,552) {553super();554this.obsoletedResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete');555this.obsoleteFileLimiter = new Queue();556}557558async cleanUp(): Promise<void> {559await this.removeTemporarilyDeletedFolders();560await this.deleteExtensionsMarkedForRemoval();561//TODO: Remove this initiialization after coupe of releases562await this.initializeExtensionSize();563}564565async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion, language?: string): Promise<ILocalExtension[]> {566try {567const cacheKey: URI = profileLocation.with({ query: language });568const userScanOptions: UserExtensionsScanOptions = { includeInvalid: true, profileLocation, productVersion, language };569let scannedExtensions: IScannedExtension[] = [];570if (type === null || type === ExtensionType.System) {571let scanAllExtensionsPromise = this.scanAllExtensionPromise.get(cacheKey);572if (!scanAllExtensionsPromise) {573scanAllExtensionsPromise = this.extensionsScannerService.scanAllExtensions({ language }, userScanOptions)574.finally(() => this.scanAllExtensionPromise.delete(cacheKey));575this.scanAllExtensionPromise.set(cacheKey, scanAllExtensionsPromise);576}577scannedExtensions.push(...await scanAllExtensionsPromise);578} else if (type === ExtensionType.User) {579let scanUserExtensionsPromise = this.scanUserExtensionsPromise.get(cacheKey);580if (!scanUserExtensionsPromise) {581scanUserExtensionsPromise = this.extensionsScannerService.scanUserExtensions(userScanOptions)582.finally(() => this.scanUserExtensionsPromise.delete(cacheKey));583this.scanUserExtensionsPromise.set(cacheKey, scanUserExtensionsPromise);584}585scannedExtensions.push(...await scanUserExtensionsPromise);586}587scannedExtensions = type !== null ? scannedExtensions.filter(r => r.type === type) : scannedExtensions;588return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension)));589} catch (error) {590throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning);591}592}593594async scanAllUserExtensions(): Promise<ILocalExtension[]> {595try {596const scannedExtensions = await this.extensionsScannerService.scanAllUserExtensions();597return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension)));598} catch (error) {599throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning);600}601}602603async scanUserExtensionAtLocation(location: URI): Promise<ILocalExtension | null> {604try {605const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, ExtensionType.User, { includeInvalid: true });606if (scannedExtension) {607return await this.toLocalExtension(scannedExtension);608}609} catch (error) {610this.logService.error(error);611}612return null;613}614615async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, removeIfExists: boolean, token: CancellationToken): Promise<ILocalExtension> {616const folderName = extensionKey.toString();617const tempLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, `.${generateUuid()}`));618const extensionLocation = URI.file(path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, folderName));619620if (await this.fileService.exists(extensionLocation)) {621if (!removeIfExists) {622try {623return await this.scanLocalExtension(extensionLocation, ExtensionType.User);624} catch (error) {625this.logService.warn(`Error while scanning the existing extension at ${extensionLocation.path}. Deleting the existing extension and extracting it.`, getErrorMessage(error));626}627}628629try {630await this.deleteExtensionFromLocation(extensionKey.id, extensionLocation, 'removeExisting');631} catch (error) {632throw new ExtensionManagementError(nls.localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionLocation.fsPath, extensionKey.id), ExtensionManagementErrorCode.Delete);633}634}635636try {637if (token.isCancellationRequested) {638throw new CancellationError();639}640641// Extract642try {643this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionLocation.fsPath}`);644await extract(zipPath, tempLocation.fsPath, { sourcePath: 'extension', overwrite: true }, token);645this.logService.info(`Extracted extension to ${extensionLocation}:`, extensionKey.id);646} catch (e) {647throw fromExtractError(e);648}649650const metadata: ManifestMetadata = { installedTimestamp: Date.now(), targetPlatform: extensionKey.targetPlatform };651try {652metadata.size = await computeSize(tempLocation, this.fileService);653} catch (error) {654// Log & ignore655this.logService.warn(`Error while getting the size of the extracted extension : ${tempLocation.fsPath}`, getErrorMessage(error));656}657658try {659await this.extensionsScannerService.updateManifestMetadata(tempLocation, metadata);660} catch (error) {661this.telemetryService.publicLog2<UpdateMetadataErrorEvent, UpdateMetadataErrorClassification>('extension:extract', { extensionId: extensionKey.id, code: `${toFileOperationResult(error)}` });662throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata);663}664665if (token.isCancellationRequested) {666throw new CancellationError();667}668669// Rename670try {671this.logService.trace(`Started renaming the extension from ${tempLocation.fsPath} to ${extensionLocation.fsPath}`);672await this.rename(tempLocation.fsPath, extensionLocation.fsPath);673this.logService.info('Renamed to', extensionLocation.fsPath);674} catch (error) {675if (error.code === 'ENOTEMPTY') {676this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, extensionKey.id);677try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ }678} else {679this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempLocation);680throw error;681}682}683684this._onExtract.fire(extensionLocation);685686} catch (error) {687try { await this.fileService.del(tempLocation, { recursive: true }); } catch (e) { /* ignore */ }688throw error;689}690691return this.scanLocalExtension(extensionLocation, ExtensionType.User);692}693694async scanMetadata(local: ILocalExtension, profileLocation: URI): Promise<Metadata | undefined> {695const extension = await this.getScannedExtension(local, profileLocation);696return extension?.metadata;697}698699private async getScannedExtension(local: ILocalExtension, profileLocation: URI): Promise<IScannedProfileExtension | undefined> {700const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(profileLocation);701return extensions.find(e => areSameExtensions(e.identifier, local.identifier));702}703704async updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>, profileLocation: URI): Promise<ILocalExtension> {705try {706await this.extensionsProfileScannerService.updateMetadata([[local, metadata]], profileLocation);707} catch (error) {708this.telemetryService.publicLog2<UpdateMetadataErrorEvent, UpdateMetadataErrorClassification>('extension:extract', { extensionId: local.identifier.id, code: `${toFileOperationResult(error)}`, isProfile: !!profileLocation });709throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata);710}711return this.scanLocalExtension(local.location, local.type, profileLocation);712}713714async setExtensionsForRemoval(...extensions: IExtension[]): Promise<void> {715const extensionsToRemove = [];716for (const extension of extensions) {717if (await this.fileService.exists(extension.location)) {718extensionsToRemove.push(extension);719}720}721const extensionKeys: ExtensionKey[] = extensionsToRemove.map(e => ExtensionKey.create(e));722await this.withRemovedExtensions(removedExtensions =>723extensionKeys.forEach(extensionKey => {724removedExtensions[extensionKey.toString()] = true;725this.logService.info('Marked extension as removed', extensionKey.toString());726}));727}728729async unsetExtensionsForRemoval(...extensionKeys: ExtensionKey[]): Promise<boolean[]> {730try {731const results: boolean[] = [];732await this.withRemovedExtensions(removedExtensions =>733extensionKeys.forEach(extensionKey => {734if (removedExtensions[extensionKey.toString()]) {735results.push(true);736delete removedExtensions[extensionKey.toString()];737} else {738results.push(false);739}740}));741return results;742} catch (error) {743throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetRemoved);744}745}746747async deleteExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise<void> {748if (this.uriIdentityService.extUri.isEqualOrParent(extension.location, this.extensionsScannerService.userExtensionsLocation)) {749await this.deleteExtensionFromLocation(extension.identifier.id, extension.location, type);750await this.unsetExtensionsForRemoval(ExtensionKey.create(extension));751}752}753754async copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial<Metadata>): Promise<ILocalExtension> {755const source = await this.getScannedExtension(extension, fromProfileLocation);756const target = await this.getScannedExtension(extension, toProfileLocation);757metadata = { ...source?.metadata, ...metadata };758759if (target) {760if (this.uriIdentityService.extUri.isEqual(target.location, extension.location)) {761await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation);762} else {763const targetExtension = await this.scanLocalExtension(target.location, extension.type, toProfileLocation);764await this.extensionsProfileScannerService.removeExtensionsFromProfile([targetExtension.identifier], toProfileLocation);765await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, { ...target.metadata, ...metadata }]], toProfileLocation);766}767} else {768await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, metadata]], toProfileLocation);769}770771return this.scanLocalExtension(extension.location, extension.type, toProfileLocation);772}773774async moveExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial<Metadata>): Promise<ILocalExtension> {775const source = await this.getScannedExtension(extension, fromProfileLocation);776const target = await this.getScannedExtension(extension, toProfileLocation);777metadata = { ...source?.metadata, ...metadata };778779if (target) {780if (this.uriIdentityService.extUri.isEqual(target.location, extension.location)) {781await this.extensionsProfileScannerService.updateMetadata([[extension, { ...target.metadata, ...metadata }]], toProfileLocation);782} else {783const targetExtension = await this.scanLocalExtension(target.location, extension.type, toProfileLocation);784await this.removeExtension(targetExtension.identifier, toProfileLocation);785await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, { ...target.metadata, ...metadata }]], toProfileLocation);786}787} else {788await this.extensionsProfileScannerService.addExtensionsToProfile([[extension, metadata]], toProfileLocation);789if (source) {790await this.removeExtension(source.identifier, fromProfileLocation);791}792}793794return this.scanLocalExtension(extension.location, extension.type, toProfileLocation);795}796797async removeExtension(identifier: IExtensionIdentifier, fromProfileLocation: URI): Promise<void> {798await this.extensionsProfileScannerService.removeExtensionsFromProfile([identifier], fromProfileLocation);799}800801async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, productVersion: IProductVersion): Promise<void> {802const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation, productVersion);803const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions804.filter(e => !e.isApplicationScoped) /* remove application scoped extensions */805.map(async e => ([e, await this.scanMetadata(e, fromProfileLocation)])));806await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, toProfileLocation);807}808809private async deleteExtensionFromLocation(id: string, location: URI, type: string): Promise<void> {810this.logService.trace(`Deleting ${type} extension from disk`, id, location.fsPath);811const renamedLocation = this.uriIdentityService.extUri.joinPath(this.uriIdentityService.extUri.dirname(location), `${this.uriIdentityService.extUri.basename(location)}.${hash(generateUuid()).toString(16)}${DELETED_FOLDER_POSTFIX}`);812await this.rename(location.fsPath, renamedLocation.fsPath);813await this.fileService.del(renamedLocation, { recursive: true });814this.logService.info(`Deleted ${type} extension from disk`, id, location.fsPath);815}816817private withRemovedExtensions(updateFn?: (removed: IStringDictionary<boolean>) => void): Promise<IStringDictionary<boolean>> {818return this.obsoleteFileLimiter.queue(async () => {819let raw: string | undefined;820try {821const content = await this.fileService.readFile(this.obsoletedResource, 'utf8');822raw = content.value.toString();823} catch (error) {824if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {825throw error;826}827}828829let removed = {};830if (raw) {831try {832removed = JSON.parse(raw);833} catch (e) { /* ignore */ }834}835836if (updateFn) {837updateFn(removed);838if (Object.keys(removed).length) {839await this.fileService.writeFile(this.obsoletedResource, VSBuffer.fromString(JSON.stringify(removed)));840} else {841try {842await this.fileService.del(this.obsoletedResource);843} catch (error) {844if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {845throw error;846}847}848}849}850851return removed;852});853}854855private async rename(extractPath: string, renamePath: string): Promise<void> {856try {857await pfs.Promises.rename(extractPath, renamePath, 2 * 60 * 1000 /* Retry for 2 minutes */);858} catch (error) {859throw toExtensionManagementError(error, ExtensionManagementErrorCode.Rename);860}861}862863async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise<ILocalExtension> {864try {865if (profileLocation) {866const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation });867const scannedExtension = scannedExtensions.find(e => this.uriIdentityService.extUri.isEqual(e.location, location));868if (scannedExtension) {869return await this.toLocalExtension(scannedExtension);870}871} else {872const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, type, { includeInvalid: true });873if (scannedExtension) {874return await this.toLocalExtension(scannedExtension);875}876}877throw new ExtensionManagementError(nls.localize('cannot read', "Cannot read the extension from {0}", location.path), ExtensionManagementErrorCode.ScanningExtension);878} catch (error) {879throw toExtensionManagementError(error, ExtensionManagementErrorCode.ScanningExtension);880}881}882883private async toLocalExtension(extension: IScannedExtension): Promise<ILocalExtension> {884let stat: IFileStat | undefined;885try {886stat = await this.fileService.resolve(extension.location);887} catch (error) {/* ignore */ }888889let readmeUrl: URI | undefined;890let changelogUrl: URI | undefined;891if (stat?.children) {892readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource;893changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource;894}895return {896identifier: extension.identifier,897type: extension.type,898isBuiltin: extension.isBuiltin || !!extension.metadata?.isBuiltin,899location: extension.location,900manifest: extension.manifest,901targetPlatform: extension.targetPlatform,902validations: extension.validations,903isValid: extension.isValid,904readmeUrl,905changelogUrl,906publisherDisplayName: extension.metadata?.publisherDisplayName,907publisherId: extension.metadata?.publisherId || null,908isApplicationScoped: !!extension.metadata?.isApplicationScoped,909isMachineScoped: !!extension.metadata?.isMachineScoped,910isPreReleaseVersion: !!extension.metadata?.isPreReleaseVersion,911hasPreReleaseVersion: !!extension.metadata?.hasPreReleaseVersion,912preRelease: extension.preRelease,913installedTimestamp: extension.metadata?.installedTimestamp,914updated: !!extension.metadata?.updated,915pinned: !!extension.metadata?.pinned,916private: !!extension.metadata?.private,917isWorkspaceScoped: false,918source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix'),919size: extension.metadata?.size ?? 0,920};921}922923private async initializeExtensionSize(): Promise<void> {924const extensions = await this.extensionsScannerService.scanAllUserExtensions();925await Promise.all(extensions.map(async extension => {926// set size if not set before927if (isDefined(extension.metadata?.installedTimestamp) && isUndefined(extension.metadata?.size)) {928const size = await computeSize(extension.location, this.fileService);929await this.extensionsScannerService.updateManifestMetadata(extension.location, { size });930}931}));932}933934private async deleteExtensionsMarkedForRemoval(): Promise<void> {935let removed: IStringDictionary<boolean>;936try {937removed = await this.withRemovedExtensions();938} catch (error) {939throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadRemoved);940}941942if (Object.keys(removed).length === 0) {943this.logService.debug(`No extensions are marked as removed.`);944return;945}946947this.logService.debug(`Deleting extensions marked as removed:`, Object.keys(removed));948949const extensions = await this.scanAllUserExtensions();950const installed: Set<string> = new Set<string>();951for (const e of extensions) {952if (!removed[ExtensionKey.create(e).toString()]) {953installed.add(e.identifier.id.toLowerCase());954}955}956957try {958// running post uninstall tasks for extensions that are not installed anymore959const byExtension = groupByExtension(extensions, e => e.identifier);960await Promises.settled(byExtension.map(async e => {961const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0];962if (!installed.has(latest.identifier.id.toLowerCase())) {963await this.beforeRemovingExtension(latest);964}965}));966} catch (error) {967this.logService.error(error);968}969970const toRemove = extensions.filter(e => e.installedTimestamp /* Installed by System */ && removed[ExtensionKey.create(e).toString()]);971await Promise.allSettled(toRemove.map(e => this.deleteExtension(e, 'marked for removal')));972}973974private async removeTemporarilyDeletedFolders(): Promise<void> {975this.logService.trace('ExtensionManagementService#removeTempDeleteFolders');976977let stat;978try {979stat = await this.fileService.resolve(this.extensionsScannerService.userExtensionsLocation);980} catch (error) {981if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {982this.logService.error(error);983}984return;985}986987if (!stat?.children) {988return;989}990991try {992await Promise.allSettled(stat.children.map(async child => {993if (!child.isDirectory || !child.name.endsWith(DELETED_FOLDER_POSTFIX)) {994return;995}996this.logService.trace('Deleting the temporarily deleted folder', child.resource.toString());997try {998await this.fileService.del(child.resource, { recursive: true });999this.logService.trace('Deleted the temporarily deleted folder', child.resource.toString());1000} catch (error) {1001if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {1002this.logService.error(error);1003}1004}1005}));1006} catch (error) { /* ignore */ }1007}10081009}10101011class InstallExtensionInProfileTask extends AbstractExtensionTask<ILocalExtension> implements IInstallExtensionTask {10121013private _operation = InstallOperation.Install;1014get operation() { return this.options.operation ?? this._operation; }10151016private _verificationStatus: ExtensionSignatureVerificationCode | undefined;1017get verificationStatus() { return this._verificationStatus; }10181019readonly identifier: IExtensionIdentifier;10201021constructor(1022private readonly extensionKey: ExtensionKey,1023readonly manifest: IExtensionManifest,1024readonly source: IGalleryExtension | URI,1025readonly options: InstallExtensionTaskOptions,1026private readonly extractExtensionFn: (operation: InstallOperation, token: CancellationToken) => Promise<ExtractExtensionResult>,1027private readonly extensionsScanner: ExtensionsScanner,1028@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,1029@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,1030@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,1031@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,1032@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,1033@ILogService private readonly logService: ILogService,1034) {1035super();1036this.identifier = this.extensionKey.identifier;1037}10381039protected async doRun(token: CancellationToken): Promise<ILocalExtension> {1040const installed = await this.extensionsScanner.scanExtensions(ExtensionType.User, this.options.profileLocation, this.options.productVersion);1041const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.identifier));1042if (existingExtension) {1043this._operation = InstallOperation.Update;1044}10451046const metadata: Metadata = {1047isApplicationScoped: this.options.isApplicationScoped || existingExtension?.isApplicationScoped,1048isMachineScoped: this.options.isMachineScoped || existingExtension?.isMachineScoped,1049isBuiltin: this.options.isBuiltin || existingExtension?.isBuiltin,1050isSystem: existingExtension?.type === ExtensionType.System ? true : undefined,1051installedTimestamp: Date.now(),1052pinned: this.options.installGivenVersion ? true : (this.options.pinned ?? existingExtension?.pinned),1053source: this.source instanceof URI ? 'vsix' : 'gallery',1054};10551056let local: ILocalExtension | undefined;10571058// VSIX1059if (this.source instanceof URI) {1060if (existingExtension) {1061if (this.extensionKey.equals(new ExtensionKey(existingExtension.identifier, existingExtension.manifest.version))) {1062try {1063await this.extensionsScanner.deleteExtension(existingExtension, 'existing');1064} catch (e) {1065throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name));1066}1067}1068}1069// Remove the extension with same version if it is already uninstalled.1070// Installing a VSIX extension shall replace the existing extension always.1071const existingWithSameVersion = await this.unsetIfRemoved(this.extensionKey);1072if (existingWithSameVersion) {1073try {1074await this.extensionsScanner.deleteExtension(existingWithSameVersion, 'existing');1075} catch (e) {1076throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name));1077}1078}10791080}10811082// Gallery1083else {1084metadata.id = this.source.identifier.uuid;1085metadata.publisherId = this.source.publisherId;1086metadata.publisherDisplayName = this.source.publisherDisplayName;1087metadata.targetPlatform = this.source.properties.targetPlatform;1088metadata.updated = !!existingExtension;1089metadata.private = this.source.private;1090metadata.isPreReleaseVersion = this.source.properties.isPreReleaseVersion;1091metadata.hasPreReleaseVersion = existingExtension?.hasPreReleaseVersion || this.source.properties.isPreReleaseVersion;1092metadata.preRelease = isBoolean(this.options.preRelease)1093? this.options.preRelease1094: this.options.installPreReleaseVersion || this.source.properties.isPreReleaseVersion || existingExtension?.preRelease;10951096if (existingExtension && existingExtension.type !== ExtensionType.System && existingExtension.manifest.version === this.source.version) {1097return this.extensionsScanner.updateMetadata(existingExtension, metadata, this.options.profileLocation);1098}10991100// Unset if the extension is uninstalled and return the unset extension.1101local = await this.unsetIfRemoved(this.extensionKey);1102}11031104if (token.isCancellationRequested) {1105throw toExtensionManagementError(new CancellationError());1106}11071108if (!local) {1109const result = await this.extractExtensionFn(this.operation, token);1110local = result.local;1111this._verificationStatus = result.verificationStatus;1112}11131114if (this.uriIdentityService.extUri.isEqual(this.userDataProfilesService.defaultProfile.extensionsResource, this.options.profileLocation)) {1115try {1116await this.extensionsScannerService.initializeDefaultProfileExtensions();1117} catch (error) {1118throw toExtensionManagementError(error, ExtensionManagementErrorCode.IntializeDefaultProfile);1119}1120}11211122if (token.isCancellationRequested) {1123throw toExtensionManagementError(new CancellationError());1124}11251126try {1127await this.extensionsProfileScannerService.addExtensionsToProfile([[local, metadata]], this.options.profileLocation, !local.isValid);1128} catch (error) {1129throw toExtensionManagementError(error, ExtensionManagementErrorCode.AddToProfile);1130}11311132const result = await this.extensionsScanner.scanLocalExtension(local.location, ExtensionType.User, this.options.profileLocation);1133if (!result) {1134throw new ExtensionManagementError('Cannot find the installed extension', ExtensionManagementErrorCode.InstalledExtensionNotFound);1135}11361137if (this.source instanceof URI) {1138this.updateMetadata(local, token);1139}11401141return result;1142}11431144private async unsetIfRemoved(extensionKey: ExtensionKey): Promise<ILocalExtension | undefined> {1145// If the same version of extension is marked as removed, remove it from there and return the local.1146const [removed] = await this.extensionsScanner.unsetExtensionsForRemoval(extensionKey);1147if (removed) {1148this.logService.info('Removed the extension from removed list:', extensionKey.id);1149const userExtensions = await this.extensionsScanner.scanAllUserExtensions();1150return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey));1151}1152return undefined;1153}11541155private async updateMetadata(extension: ILocalExtension, token: CancellationToken): Promise<void> {1156try {1157let [galleryExtension] = await this.galleryService.getExtensions([{ id: extension.identifier.id, version: extension.manifest.version }], token);1158if (!galleryExtension) {1159[galleryExtension] = await this.galleryService.getExtensions([{ id: extension.identifier.id }], token);1160}1161if (galleryExtension) {1162const metadata = {1163id: galleryExtension.identifier.uuid,1164publisherDisplayName: galleryExtension.publisherDisplayName,1165publisherId: galleryExtension.publisherId,1166isPreReleaseVersion: galleryExtension.properties.isPreReleaseVersion,1167hasPreReleaseVersion: extension.hasPreReleaseVersion || galleryExtension.properties.isPreReleaseVersion,1168preRelease: galleryExtension.properties.isPreReleaseVersion || this.options.installPreReleaseVersion1169};1170await this.extensionsScanner.updateMetadata(extension, metadata, this.options.profileLocation);1171}1172} catch (error) {1173/* Ignore Error */1174}1175}1176}11771178class UninstallExtensionInProfileTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask {11791180constructor(1181readonly extension: ILocalExtension,1182readonly options: UninstallExtensionTaskOptions,1183private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,1184) {1185super();1186}11871188protected doRun(token: CancellationToken): Promise<void> {1189return this.extensionsProfileScannerService.removeExtensionsFromProfile([this.extension.identifier], this.options.profileLocation);1190}11911192}119311941195