mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
More extension install error codes (#212161)
This commit is contained in:
parent
a812bde8e5
commit
f8c7fec0c2
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue