Skip to content

Commit

Permalink
Revert f8c7fec (#212203)
Browse files Browse the repository at this point in the history
This seems to break extension install/update
  • Loading branch information
mjbvz authored May 7, 2024
1 parent 8b86b2f commit 2a57bf6
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 235 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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, ExtensionGalleryErrorCode
IProductVersion
} 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';
Expand Down Expand Up @@ -290,10 +290,26 @@ 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 {
local = await task.run();
await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None)), ExtensionManagementErrorCode.PostInstall);
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 });
} catch (e) {
const error = toExtensionManagementError(e);
if (!URI.isUri(task.source)) {
Expand All @@ -303,23 +319,6 @@ 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) {
Expand Down Expand Up @@ -429,35 +428,36 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return true;
}

private async joinAllSettled<T>(promises: Promise<T>[], errorCode?: ExtensionManagementErrorCode): Promise<T[]> {
private async joinAllSettled<T>(promises: Promise<T>[]): Promise<T[]> {
const results: T[] = [];
const errors: ExtensionManagementError[] = [];
const errors: any[] = [];
const promiseResults = await Promise.allSettled(promises);
for (const r of promiseResults) {
if (r.status === 'fulfilled') {
results.push(r.value);
} else {
errors.push(toExtensionManagementError(r.reason, errorCode));
errors.push(r.reason);
}
}

if (!errors.length) {
return results;
}

// Throw if there are errors
if (errors.length === 1) {
throw errors[0];
}
if (errors.length) {
if (errors.length === 1) {
throw errors[0];
}

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
);
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 error;

return results;
}

private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined, productVersion: IProductVersion): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> {
Expand Down Expand Up @@ -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, code?: ExtensionManagementErrorCode): ExtensionManagementError {
export function toExtensionManagementError(error: Error): ExtensionManagementError {
if (error instanceof ExtensionManagementError) {
return error;
}
let extensionManagementError: ExtensionManagementError;
if (error instanceof ExtensionGalleryError) {
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, ExtensionManagementErrorCode.Gallery);
e.stack = error.stack;
return e;
}
extensionManagementError.stack = error.stack;
return extensionManagementError;
const e = new ExtensionManagementError(error.message, isCancellationError(error) ? ExtensionManagementErrorCode.Cancelled : ExtensionManagementErrorCode.Internal);
e.stack = error.stack;
return e;
}

function reportTelemetry(telemetryService: ITelemetryService, eventName: string, { extensionData, verificationStatus, duration, error, durationSinceUpdate }: { extensionData: any; verificationStatus?: ExtensionVerificationStatus; duration?: number; durationSinceUpdate?: number; error?: ExtensionManagementError | ExtensionGalleryError }): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1029,28 +1029,6 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);
const data = getGalleryExtensionTelemetryData(extension);
const startTime = new Date().getTime();

const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
const downloadAsset = operationParam ? {
uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`,
fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true`
} : extension.assets.download;

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

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",
Expand All @@ -1060,7 +1038,18 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
]
}
*/
this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration: new Date().getTime() - startTime });
const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration });

const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
const downloadAsset = operationParam ? {
uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`,
fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true`
} : extension.assets.download;

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);
}

async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void> {
Expand All @@ -1071,18 +1060,7 @@ 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);
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);
}

await this.fileService.writeFile(location, context.stream);
}

async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
Expand Down
36 changes: 14 additions & 22 deletions src/vs/platform/extensionManagement/common/extensionManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,21 +407,7 @@ export interface DidUninstallExtensionEvent {
readonly workspaceScoped?: boolean;
}

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 {
export enum ExtensionManagementErrorCode {
Unsupported = 'Unsupported',
Deprecated = 'Deprecated',
Malicious = 'Malicious',
Expand All @@ -431,18 +417,11 @@ export const 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',
Expand All @@ -460,6 +439,19 @@ 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;
Expand Down
32 changes: 15 additions & 17 deletions src/vs/platform/extensionManagement/node/extensionDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, toExtensionManagementError } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import { ExtensionVerificationStatus } 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';
Expand Down Expand Up @@ -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 toExtensionManagementError(error, ExtensionManagementErrorCode.Download);
throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.Download);
}

let verificationStatus: ExtensionVerificationStatus = false;
Expand All @@ -62,20 +62,23 @@ export class ExtensionsDownloader extends Disposable {
try {
verificationStatus = await this.extensionSignatureVerificationService.verify(extension.identifier.id, location.fsPath, signatureArchiveLocation.fsPath);
} catch (error) {
verificationStatus = (error as ExtensionSignatureVerificationError).code;
const sigError = error as ExtensionSignatureVerificationError;
verificationStatus = sigError.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 downloaded files
await Promise.allSettled([
this.delete(location),
this.delete(signatureArchiveLocation)
]);
// Delete signature archive always
await this.delete(signatureArchiveLocation);
} catch (error) {
// Ignore error
this.logService.warn(`Error while deleting downloaded files: ${getErrorMessage(error)}`);
this.logService.error(error);
}
}
}
Expand All @@ -100,7 +103,7 @@ export class ExtensionsDownloader extends Disposable {
try {
await this.downloadFile(extension, location, location => this.extensionGalleryService.downloadSignatureArchive(extension, location));
} catch (error) {
throw toExtensionManagementError(error, ExtensionManagementErrorCode.DownloadSignature);
throw new ExtensionManagementError(error.message, ExtensionManagementErrorCode.DownloadSignature);
}
return location;
}
Expand All @@ -119,13 +122,8 @@ export class ExtensionsDownloader extends Disposable {

// Download to temporary location first only if file does not exist
const tempLocation = joinPath(this.extensionsDownloadDir, `.${generateUuid()}`);
try {
if (!await this.fileService.exists(tempLocation)) {
await downloadFn(tempLocation);
} catch (error) {
try {
await this.fileService.del(tempLocation);
} catch (e) { /* ignore */ }
throw error;
}

try {
Expand Down
Loading

0 comments on commit 2a57bf6

Please sign in to comment.