More extension install error codes (#212161)

This commit is contained in:
Sandeep Somavarapu 2024-05-07 12:36:52 +02:00 committed by GitHub
parent a812bde8e5
commit f8c7fec0c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 236 additions and 183 deletions

View file

@ -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<T>(promises: Promise<T>[]): Promise<T[]> {
private async joinAllSettled<T>(promises: Promise<T>[], errorCode?: ExtensionManagementErrorCode): Promise<T[]> {
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));
}
}
if (!errors.length) {
return results;
}
// Throw if there are errors
if (errors.length) {
if (errors.length === 1) {
throw errors[0];
}
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;
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<Metadata>): Promise<ILocalExtension>;
}
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 {

View file

@ -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<void> {
@ -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<string> {

View file

@ -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;

View file

@ -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,7 +52,7 @@ 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;
@ -62,23 +62,20 @@ export class ExtensionsDownloader extends Disposable {
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
await this.delete(location);
} catch (error) {
this.logService.error(error);
}
throw new ExtensionManagementError(CorruptZipMessage, ExtensionManagementErrorCode.CorruptZip);
}
} finally {
try {
// Delete signature archive always
await this.delete(signatureArchiveLocation);
// Delete downloaded files
await Promise.allSettled([
this.delete(location),
this.delete(signatureArchiveLocation)
]);
} catch (error) {
this.logService.error(error);
// Ignore error
this.logService.warn(`Error while deleting downloaded files: ${getErrorMessage(error)}`);
}
}
}
@ -103,7 +100,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 +119,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 {

View file

@ -451,20 +451,28 @@ export class ExtensionsScanner extends Disposable {
}
async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise<ILocalExtension[]> {
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<ILocalExtension[]> {
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<ILocalExtension | null> {
@ -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<Metadata>, profileLocation?: URI): Promise<ILocalExtension> {
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<IStringDictionary<boolean>> {
return this.withUninstalledExtensions();
async getUninstalledExtensions(): Promise<IStringDictionary<boolean>> {
try {
return await this.withUninstalledExtensions();
} catch (error) {
throw toExtensionManagementError(error, ExtensionManagementErrorCode.ReadUninstalled);
}
}
async setUninstalled(...extensions: IExtension[]): Promise<void> {
@ -580,7 +604,11 @@ export class ExtensionsScanner extends Disposable {
}
async setInstalled(extensionKey: ExtensionKey): Promise<void> {
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<void> {
@ -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<boolean>) => void): Promise<IStringDictionary<boolean>> {
private withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary<boolean>) => void): Promise<IStringDictionary<boolean>> {
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<ILocalExtension> {
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<ILocalExtension> {
@ -821,9 +853,17 @@ abstract class InstallExtensionTask extends AbstractExtensionTask<ILocalExtensio
const [local, metadata] = await this.install(token);
this._profileLocation = local.isBuiltin || local.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : this.options.profileLocation;
if (this.uriIdentityService.extUri.isEqual(this.userDataProfilesService.defaultProfile.extensionsResource, this._profileLocation)) {
await this.extensionsScannerService.initializeDefaultProfileExtensions();
try {
await this.extensionsScannerService.initializeDefaultProfileExtensions();
} catch (error) {
throw toExtensionManagementError(error, ExtensionManagementErrorCode.IntializeDefaultProfile);
}
}
try {
await this.extensionsProfileScannerService.addExtensionsToProfile([[local, metadata]], this._profileLocation, !local.isValid);
} catch (error) {
throw toExtensionManagementError(error, ExtensionManagementErrorCode.AddToProfile);
}
await this.extensionsProfileScannerService.addExtensionsToProfile([[local, metadata]], this._profileLocation, !local.isValid);
return local;
}
@ -840,8 +880,8 @@ abstract class InstallExtensionTask extends AbstractExtensionTask<ILocalExtensio
}
protected async unsetIfUninstalled(extensionKey: ExtensionKey): Promise<ILocalExtension | undefined> {
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<ILocalExtensio
return userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey));
}
private async isUninstalled(extensionId: ExtensionKey): Promise<boolean> {
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,72 +944,64 @@ 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];
}
try {
return await this.downloadAndInstallExtension(metadata, token);
} 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<RetryInstallingInvalidVSIXEvent, RetryInstallingInvalidVSIXClassification>('extensiongallery:install:retry', {
extensionId: this.gallery.identifier.id,
succeeded: true
});
return result;
} catch (error) {
this.telemetryService.publicLog2<RetryInstallingInvalidVSIXEvent, RetryInstallingInvalidVSIXClassification>('extensiongallery:install:retry', {
extensionId: this.gallery.identifier.id,
succeeded: false
});
throw error;
}
} else {
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);
const { verificationStatus, location } = await this.download(metadata, token);
try {
this._verificationStatus = verificationStatus;
this.validateManifest(location.fsPath);
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) {
try {
await this.extensionsDownloader.delete(location);
} catch (error) {
} catch (e) {
/* Ignore */
this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(error));
this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e));
}
throw error;
}
}
private async download(metadata: Metadata, token: CancellationToken): Promise<{ readonly location: URI; readonly verificationStatus: ExtensionVerificationStatus }> {
try {
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 {
const result = await this.download(metadata, token);
this.telemetryService.publicLog2<RetryDownloadingVSIXEvent, RetryDownloadingVSIXClassification>('extensiongallery:download:retry', {
extensionId: this.gallery.identifier.id,
succeeded: true
});
return result;
} catch (error) {
this.telemetryService.publicLog2<RetryDownloadingVSIXEvent, RetryDownloadingVSIXClassification>('extensiongallery:download:retry', {
extensionId: this.gallery.identifier.id,
succeeded: false
});
throw error;
}
}
}
protected async validateManifest(zipPath: string): Promise<void> {
try {
await getManifest(zipPath);
} catch (error) {
throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Invalid);
throw toExtensionManagementError(error, ExtensionManagementErrorCode.Invalid);
}
}