diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 55a1e34ba60e5..95438e4a7642c 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -17,7 +17,7 @@ import { ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation, IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, InstallOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError, - IProductVersion + IProductVersion, ExtensionGalleryErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -290,26 +290,10 @@ export abstract class AbstractExtensionManagementService extends Disposable impl // Install extensions in parallel and wait until all extensions are installed / failed await this.joinAllSettled([...installingExtensionsMap.entries()].map(async ([key, { task }]) => { const startTime = new Date().getTime(); + let local: ILocalExtension; try { - const local = await task.run(); - await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None))); - if (!URI.isUri(task.source)) { - const isUpdate = task.operation === InstallOperation.Update; - const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; - reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { - extensionData: getGalleryExtensionTelemetryData(task.source), - verificationStatus: task.verificationStatus, - duration: new Date().getTime() - startTime, - durationSinceUpdate - }); - // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. - if (isWeb && task.operation !== InstallOperation.Update) { - try { - await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install); - } catch (error) { /* ignore */ } - } - } - installExtensionResultsMap.set(key, { local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped }); + local = await task.run(); + await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None)), ExtensionManagementErrorCode.PostInstall); } catch (e) { const error = toExtensionManagementError(e); if (!URI.isUri(task.source)) { @@ -319,6 +303,23 @@ export abstract class AbstractExtensionManagementService extends Disposable impl this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error)); throw error; } + if (!URI.isUri(task.source)) { + const isUpdate = task.operation === InstallOperation.Update; + const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; + reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { + extensionData: getGalleryExtensionTelemetryData(task.source), + verificationStatus: task.verificationStatus, + duration: new Date().getTime() - startTime, + durationSinceUpdate + }); + // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. + if (isWeb && task.operation !== InstallOperation.Update) { + try { + await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install); + } catch (error) { /* ignore */ } + } + } + installExtensionResultsMap.set(key, { local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped }); })); if (alreadyRequestedInstallations.length) { @@ -428,36 +429,35 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return true; } - private async joinAllSettled(promises: Promise[]): Promise { + private async joinAllSettled(promises: Promise[], errorCode?: ExtensionManagementErrorCode): Promise { const results: T[] = []; - const errors: any[] = []; + const errors: ExtensionManagementError[] = []; const promiseResults = await Promise.allSettled(promises); for (const r of promiseResults) { if (r.status === 'fulfilled') { results.push(r.value); } else { - errors.push(r.reason); + errors.push(toExtensionManagementError(r.reason, errorCode)); } } - // Throw if there are errors - if (errors.length) { - if (errors.length === 1) { - throw errors[0]; - } + if (!errors.length) { + return results; + } - let error = new ExtensionManagementError('', ExtensionManagementErrorCode.Unknown); - for (const current of errors) { - const code = current instanceof ExtensionManagementError ? current.code : ExtensionManagementErrorCode.Unknown; - error = new ExtensionManagementError( - current.message ? `${current.message}, ${error.message}` : error.message, - code !== ExtensionManagementErrorCode.Unknown && code !== ExtensionManagementErrorCode.Internal ? code : error.code - ); - } - throw error; + // Throw if there are errors + if (errors.length === 1) { + throw errors[0]; } - return results; + let error = new ExtensionManagementError('', ExtensionManagementErrorCode.Unknown); + for (const current of errors) { + error = new ExtensionManagementError( + error.message ? `${error.message}, ${current.message}` : current.message, + current.code !== ExtensionManagementErrorCode.Unknown && current.code !== ExtensionManagementErrorCode.Internal ? current.code : error.code + ); + } + throw error; } private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined, productVersion: IProductVersion): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> { @@ -787,18 +787,18 @@ export abstract class AbstractExtensionManagementService extends Disposable impl protected abstract copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata?: Partial): Promise; } -export function toExtensionManagementError(error: Error): ExtensionManagementError { +export function toExtensionManagementError(error: Error, code?: ExtensionManagementErrorCode): ExtensionManagementError { if (error instanceof ExtensionManagementError) { return error; } + let extensionManagementError: ExtensionManagementError; if (error instanceof ExtensionGalleryError) { - const e = new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Gallery); - e.stack = error.stack; - return e; + extensionManagementError = new ExtensionManagementError(error.message, error.code === ExtensionGalleryErrorCode.DownloadFailedWriting ? ExtensionManagementErrorCode.DownloadFailedWriting : ExtensionManagementErrorCode.Gallery); + } else { + extensionManagementError = new ExtensionManagementError(error.message, isCancellationError(error) ? ExtensionManagementErrorCode.Cancelled : (code ?? ExtensionManagementErrorCode.Internal)); } - const e = new ExtensionManagementError(error.message, isCancellationError(error) ? ExtensionManagementErrorCode.Cancelled : ExtensionManagementErrorCode.Internal); - e.stack = error.stack; - return e; + extensionManagementError.stack = error.stack; + return extensionManagementError; } function reportTelemetry(telemetryService: ITelemetryService, eventName: string, { extensionData, verificationStatus, duration, error, durationSinceUpdate }: { extensionData: any; verificationStatus?: ExtensionVerificationStatus; duration?: number; durationSinceUpdate?: number; error?: ExtensionManagementError | ExtensionGalleryError }): void { diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index ebdb1bb50b89a..cc587b5770e28 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -1029,16 +1029,6 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi this.logService.trace('ExtensionGalleryService#download', extension.identifier.id); const data = getGalleryExtensionTelemetryData(extension); const startTime = new Date().getTime(); - /* __GDPR__ - "galleryService:downloadVSIX" : { - "owner": "sandy081", - "duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "${include}": [ - "${GalleryExtensionTelemetryData}" - ] - } - */ - const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration }); const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : ''; const downloadAsset = operationParam ? { @@ -1048,8 +1038,29 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const headers: IHeaders | undefined = extension.queryContext?.[ACTIVITY_HEADER_NAME] ? { [ACTIVITY_HEADER_NAME]: extension.queryContext[ACTIVITY_HEADER_NAME] } : undefined; const context = await this.getAsset(extension.identifier.id, downloadAsset, AssetType.VSIX, headers ? { headers } : undefined); - await this.fileService.writeFile(location, context.stream); - log(new Date().getTime() - startTime); + + try { + await this.fileService.writeFile(location, context.stream); + } catch (error) { + try { + await this.fileService.del(location); + } catch (e) { + /* ignore */ + this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e)); + } + throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting); + } + + /* __GDPR__ + "galleryService:downloadVSIX" : { + "owner": "sandy081", + "duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ + this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration: new Date().getTime() - startTime }); } async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise { @@ -1060,7 +1071,18 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi this.logService.trace('ExtensionGalleryService#downloadSignatureArchive', extension.identifier.id); const context = await this.getAsset(extension.identifier.id, extension.assets.signature, AssetType.Signature); - await this.fileService.writeFile(location, context.stream); + try { + await this.fileService.writeFile(location, context.stream); + } catch (error) { + try { + await this.fileService.del(location); + } catch (e) { + /* ignore */ + this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e)); + } + throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting); + } + } async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index d69233901b8be..fa9e1b0983da8 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -407,7 +407,21 @@ export interface DidUninstallExtensionEvent { readonly workspaceScoped?: boolean; } -export enum ExtensionManagementErrorCode { +export const enum ExtensionGalleryErrorCode { + Timeout = 'Timeout', + Cancelled = 'Cancelled', + Failed = 'Failed', + DownloadFailedWriting = 'DownloadFailedWriting', +} + +export class ExtensionGalleryError extends Error { + constructor(message: string, readonly code: ExtensionGalleryErrorCode) { + super(message); + this.name = code; + } +} + +export const enum ExtensionManagementErrorCode { Unsupported = 'Unsupported', Deprecated = 'Deprecated', Malicious = 'Malicious', @@ -417,11 +431,18 @@ export enum ExtensionManagementErrorCode { Invalid = 'Invalid', Download = 'Download', DownloadSignature = 'DownloadSignature', + DownloadFailedWriting = ExtensionGalleryErrorCode.DownloadFailedWriting, UpdateMetadata = 'UpdateMetadata', Extract = 'Extract', Scanning = 'Scanning', + ScanningExtension = 'ScanningExtension', + ReadUninstalled = 'ReadUninstalled', + UnsetUninstalled = 'UnsetUninstalled', Delete = 'Delete', Rename = 'Rename', + IntializeDefaultProfile = 'IntializeDefaultProfile', + AddToProfile = 'AddToProfile', + PostInstall = 'PostInstall', CorruptZip = 'CorruptZip', IncompleteZip = 'IncompleteZip', Signature = 'Signature', @@ -439,19 +460,6 @@ export class ExtensionManagementError extends Error { } } -export enum ExtensionGalleryErrorCode { - Timeout = 'Timeout', - Cancelled = 'Cancelled', - Failed = 'Failed' -} - -export class ExtensionGalleryError extends Error { - constructor(message: string, readonly code: ExtensionGalleryErrorCode) { - super(message); - this.name = code; - } -} - export type InstallOptions = { isBuiltin?: boolean; isWorkspaceScoped?: boolean; diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts index 85fb4668e7994..0a96fee799d88 100644 --- a/src/vs/platform/extensionManagement/node/extensionDownloader.ts +++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -16,7 +16,7 @@ import { Promises as FSPromises } from 'vs/base/node/pfs'; import { CorruptZipMessage } from 'vs/base/node/zip'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ExtensionVerificationStatus } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; +import { ExtensionVerificationStatus, toExtensionManagementError } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; import { ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionKey, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionSignatureVerificationError, ExtensionSignatureVerificationCode, IExtensionSignatureVerificationService } from 'vs/platform/extensionManagement/node/extensionSignatureVerificationService'; @@ -52,21 +52,33 @@ export class ExtensionsDownloader extends Disposable { try { await this.downloadFile(extension, location, location => this.extensionGalleryService.download(extension, location, operation)); } catch (error) { - throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Download); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Download); } let verificationStatus: ExtensionVerificationStatus = false; if (verifySignature && this.shouldVerifySignature(extension)) { - const signatureArchiveLocation = await this.downloadSignatureArchive(extension); + + let signatureArchiveLocation; + try { + signatureArchiveLocation = await this.downloadSignatureArchive(extension); + } catch (error) { + try { + // Delete the downloaded VSIX if signature archive download fails + await this.delete(location); + } catch (error) { + this.logService.error(error); + } + throw error; + } + try { verificationStatus = await this.extensionSignatureVerificationService.verify(extension.identifier.id, location.fsPath, signatureArchiveLocation.fsPath); } catch (error) { - const sigError = error as ExtensionSignatureVerificationError; - verificationStatus = sigError.code; + verificationStatus = (error as ExtensionSignatureVerificationError).code; if (verificationStatus === ExtensionSignatureVerificationCode.PackageIsInvalidZip || verificationStatus === ExtensionSignatureVerificationCode.SignatureArchiveIsInvalidZip) { try { - // Delete the downloaded vsix before throwing the error + // Delete the downloaded vsix if VSIX or signature archive is invalid await this.delete(location); } catch (error) { this.logService.error(error); @@ -103,7 +115,7 @@ export class ExtensionsDownloader extends Disposable { try { await this.downloadFile(extension, location, location => this.extensionGalleryService.downloadSignatureArchive(extension, location)); } catch (error) { - throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.DownloadSignature); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.DownloadSignature); } return location; } @@ -122,8 +134,13 @@ export class ExtensionsDownloader extends Disposable { // Download to temporary location first only if file does not exist const tempLocation = joinPath(this.extensionsDownloadDir, `.${generateUuid()}`); - if (!await this.fileService.exists(tempLocation)) { + try { await downloadFn(tempLocation); + } catch (error) { + try { + await this.fileService.del(tempLocation); + } catch (e) { /* ignore */ } + throw error; } try { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 53630b739fe42..122a6ed9b7bae 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -451,20 +451,28 @@ export class ExtensionsScanner extends Disposable { } async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise { - const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion }; - let scannedExtensions: IScannedExtension[] = []; - if (type === null || type === ExtensionType.System) { - scannedExtensions.push(...await this.extensionsScannerService.scanAllExtensions({ includeInvalid: true }, userScanOptions, false)); - } else if (type === ExtensionType.User) { - scannedExtensions.push(...await this.extensionsScannerService.scanUserExtensions(userScanOptions)); + try { + const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation, productVersion }; + let scannedExtensions: IScannedExtension[] = []; + if (type === null || type === ExtensionType.System) { + scannedExtensions.push(...await this.extensionsScannerService.scanAllExtensions({ includeInvalid: true }, userScanOptions, false)); + } else if (type === ExtensionType.User) { + scannedExtensions.push(...await this.extensionsScannerService.scanUserExtensions(userScanOptions)); + } + scannedExtensions = type !== null ? scannedExtensions.filter(r => r.type === type) : scannedExtensions; + return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); } - scannedExtensions = type !== null ? scannedExtensions.filter(r => r.type === type) : scannedExtensions; - return Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); } async scanAllUserExtensions(excludeOutdated: boolean): Promise { - const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: !excludeOutdated, includeInvalid: true }); - return Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); + try { + const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: !excludeOutdated, includeInvalid: true }); + return await Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension))); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); + } } async scanUserExtensionAtLocation(location: URI): Promise { @@ -496,7 +504,11 @@ export class ExtensionsScanner extends Disposable { } if (exists) { - await this.extensionsScannerService.updateMetadata(extensionLocation, metadata); + try { + await this.extensionsScannerService.updateMetadata(extensionLocation, metadata); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); + } } else { try { // Extract @@ -513,10 +525,14 @@ export class ExtensionsScanner extends Disposable { errorCode = ExtensionManagementErrorCode.IncompleteZip; } } - throw new ExtensionManagementError(e.message, errorCode); + throw toExtensionManagementError(e, errorCode); } - await this.extensionsScannerService.updateMetadata(tempLocation, metadata); + try { + await this.extensionsScannerService.updateMetadata(tempLocation, metadata); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); + } // Rename try { @@ -558,16 +574,24 @@ export class ExtensionsScanner extends Disposable { } async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { - if (profileLocation) { - await this.extensionsProfileScannerService.updateMetadata([[local, metadata]], profileLocation); - } else { - await this.extensionsScannerService.updateMetadata(local.location, metadata); + try { + if (profileLocation) { + await this.extensionsProfileScannerService.updateMetadata([[local, metadata]], profileLocation); + } else { + await this.extensionsScannerService.updateMetadata(local.location, metadata); + } + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UpdateMetadata); } return this.scanLocalExtension(local.location, local.type, profileLocation); } - getUninstalledExtensions(): Promise> { - return this.withUninstalledExtensions(); + async getUninstalledExtensions(): Promise> { + try { + return await this.withUninstalledExtensions(); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadUninstalled); + } } async setUninstalled(...extensions: IExtension[]): Promise { @@ -580,7 +604,11 @@ export class ExtensionsScanner extends Disposable { } async setInstalled(extensionKey: ExtensionKey): Promise { - await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); + try { + await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.UnsetUninstalled); + } } async removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { @@ -630,7 +658,7 @@ export class ExtensionsScanner extends Disposable { this.logService.info(`Deleted ${type} extension from disk`, id, location.fsPath); } - private async withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { + private withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { return this.uninstalledFileLimiter.queue(async () => { let raw: string | undefined; try { @@ -666,24 +694,28 @@ export class ExtensionsScanner extends Disposable { try { await pfs.Promises.rename(extractPath, renamePath, 2 * 60 * 1000 /* Retry for 2 minutes */); } catch (error) { - throw new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || ExtensionManagementErrorCode.Rename); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Rename); } } private async scanLocalExtension(location: URI, type: ExtensionType, profileLocation?: URI): Promise { - if (profileLocation) { - const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation }); - const scannedExtension = scannedExtensions.find(e => this.uriIdentityService.extUri.isEqual(e.location, location)); - if (scannedExtension) { - return this.toLocalExtension(scannedExtension); - } - } else { - const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, type, { includeInvalid: true }); - if (scannedExtension) { - return this.toLocalExtension(scannedExtension); + try { + if (profileLocation) { + const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ profileLocation }); + const scannedExtension = scannedExtensions.find(e => this.uriIdentityService.extUri.isEqual(e.location, location)); + if (scannedExtension) { + return await this.toLocalExtension(scannedExtension); + } + } else { + const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, type, { includeInvalid: true }); + if (scannedExtension) { + return await this.toLocalExtension(scannedExtension); + } } + throw new ExtensionManagementError(nls.localize('cannot read', "Cannot read the extension from {0}", location.path), ExtensionManagementErrorCode.ScanningExtension); + } catch (error) { + throw toExtensionManagementError(error, ExtensionManagementErrorCode.ScanningExtension); } - throw new Error(nls.localize('cannot read', "Cannot read the extension from {0}", location.path)); } private async toLocalExtension(extension: IScannedExtension): Promise { @@ -821,9 +853,17 @@ abstract class InstallExtensionTask extends AbstractExtensionTask { - const isUninstalled = await this.isUninstalled(extensionKey); - if (!isUninstalled) { + const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); + if (!uninstalled[extensionKey.toString()]) { return undefined; } @@ -854,11 +894,6 @@ abstract class InstallExtensionTask extends AbstractExtensionTask ExtensionKey.create(i).equals(extensionKey)); } - private async isUninstalled(extensionId: ExtensionKey): Promise { - const uninstalled = await this.extensionsScanner.getUninstalledExtensions(); - return !!uninstalled[extensionId.toString()]; - } - protected abstract install(token: CancellationToken): Promise<[ILocalExtension, Metadata]>; } @@ -882,13 +917,7 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask { } protected async install(token: CancellationToken): Promise<[ILocalExtension, Metadata]> { - let installed; - try { - installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation, this.options.productVersion); - } catch (error) { - throw new ExtensionManagementError(error, ExtensionManagementErrorCode.Scanning); - } - + const installed = await this.extensionsScanner.scanExtensions(null, this.options.profileLocation, this.options.productVersion); const existingExtension = installed.find(i => areSameExtensions(i.identifier, this.gallery.identifier)); if (existingExtension) { this._operation = InstallOperation.Update; @@ -915,64 +944,56 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask { }; if (existingExtension && existingExtension.type !== ExtensionType.System && existingExtension.manifest.version === this.gallery.version) { - try { - const local = await this.extensionsScanner.updateMetadata(existingExtension, metadata); - return [local, metadata]; - } catch (error) { - throw new ExtensionManagementError(getErrorMessage(error), ExtensionManagementErrorCode.UpdateMetadata); - } + const local = await this.extensionsScanner.updateMetadata(existingExtension, metadata); + return [local, metadata]; } + const { verificationStatus, location } = await this.download(metadata, token); try { - return await this.downloadAndInstallExtension(metadata, token); + this._verificationStatus = verificationStatus; + await this.validateManifest(location.fsPath); + const local = await this.extractExtension({ zipPath: location.fsPath, key: ExtensionKey.create(this.gallery), metadata }, false, token); + return [local, metadata]; } catch (error) { - if (error instanceof ExtensionManagementError && (error.code === ExtensionManagementErrorCode.CorruptZip || error.code === ExtensionManagementErrorCode.IncompleteZip)) { - this.logService.info(`Downloaded VSIX is invalid. Trying to download and install again...`, this.gallery.identifier.id); - type RetryInstallingInvalidVSIXClassification = { - owner: 'sandy081'; - comment: 'Event reporting the retry of installing an invalid VSIX'; - extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension Id' }; - succeeded?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Success value' }; - }; - type RetryInstallingInvalidVSIXEvent = { - extensionId: string; - succeeded: boolean; - }; - try { - const result = await this.downloadAndInstallExtension(metadata, token); - this.telemetryService.publicLog2('extensiongallery:install:retry', { - extensionId: this.gallery.identifier.id, - succeeded: true - }); - return result; - } catch (error) { - this.telemetryService.publicLog2('extensiongallery:install:retry', { - extensionId: this.gallery.identifier.id, - succeeded: false - }); - throw error; - } - } else { - throw error; + try { + await this.extensionsDownloader.delete(location); + } catch (e) { + /* Ignore */ + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); } + throw error; } } - private async downloadAndInstallExtension(metadata: Metadata, token: CancellationToken): Promise<[ILocalExtension, Metadata]> { - const { location, verificationStatus } = await this.extensionsDownloader.download(this.gallery, this._operation, !this.options.donotVerifySignature); + private async download(metadata: Metadata, token: CancellationToken): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionVerificationStatus }> { try { - this._verificationStatus = verificationStatus; - this.validateManifest(location.fsPath); - const local = await this.extractExtension({ zipPath: location.fsPath, key: ExtensionKey.create(this.gallery), metadata }, false, token); - return [local, metadata]; + return await this.extensionsDownloader.download(this.gallery, this._operation, !this.options.donotVerifySignature); } catch (error) { + this.logService.info(`Failed downloading. Retry again...`, this.gallery.identifier.id); + type RetryDownloadingVSIXClassification = { + owner: 'sandy081'; + comment: 'Event reporting the retry of downloading the VSIX'; + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension Id' }; + succeeded?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Success value' }; + }; + type RetryDownloadingVSIXEvent = { + extensionId: string; + succeeded: boolean; + }; try { - await this.extensionsDownloader.delete(location); + const result = await this.download(metadata, token); + this.telemetryService.publicLog2('extensiongallery:download:retry', { + extensionId: this.gallery.identifier.id, + succeeded: true + }); + return result; } catch (error) { - /* Ignore */ - this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(error)); + this.telemetryService.publicLog2('extensiongallery:download:retry', { + extensionId: this.gallery.identifier.id, + succeeded: false + }); + throw error; } - throw error; } } @@ -980,7 +1001,7 @@ export class InstallGalleryExtensionTask extends InstallExtensionTask { try { await getManifest(zipPath); } catch (error) { - throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Invalid); + throw toExtensionManagementError(error, ExtensionManagementErrorCode.Invalid); } }