diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 65a9c4c8e735843646a8b4bfea794ab13529a6e7..bcd0239619775165fd84f7b401d5292478569c2e 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -119,14 +119,18 @@ export interface IGalleryExtensionProperties { engine?: string; } +export interface IGalleryExtensionAsset { + uri: string; + fallbackUri: string; +} + export interface IGalleryExtensionAssets { - manifest: string; - readme: string; - changelog: string; - download: string; - icon: string; - iconFallback: string; - license: string; + manifest: IGalleryExtensionAsset; + readme: IGalleryExtensionAsset; + changelog: IGalleryExtensionAsset; + download: IGalleryExtensionAsset; + icon: IGalleryExtensionAsset; + license: IGalleryExtensionAsset; } export interface IGalleryExtension { diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index bcc502cf75b33c69d91f3eb64fafbaae242775bd..b1e3d08ea3284bff5074c77a160bb4e7792653ba 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -11,7 +11,7 @@ import { distinct } from 'vs/base/common/arrays'; import { getErrorMessage } from 'vs/base/common/errors'; import { memoize } from 'vs/base/common/decorators'; import { ArraySet } from 'vs/base/common/set'; -import { IGalleryExtension, IExtensionGalleryService, IQueryOptions, SortBy, SortOrder, IExtensionManifest, EXTENSION_IDENTIFIER_REGEX } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, EXTENSION_IDENTIFIER_REGEX } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry'; import { assign, getOrDefault } from 'vs/base/common/objects'; import { IRequestService } from 'vs/platform/request/node/request'; @@ -22,7 +22,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import pkg from 'vs/platform/package'; import product from 'vs/platform/product'; import { isVersionValid } from 'vs/platform/extensions/node/extensionValidator'; -import * as url from 'url'; import { getCommonHTTPHeaders } from 'vs/platform/environment/node/http'; interface IRawGalleryExtensionFile { @@ -39,6 +38,7 @@ interface IRawGalleryExtensionVersion { version: string; lastUpdated: string; assetUri: string; + fallbackAssetUri: string; files: IRawGalleryExtensionFile[]; properties?: IRawGalleryExtensionProperty[]; } @@ -193,9 +193,29 @@ function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string return result ? result.value : 0; } -function getAssetSource(files: IRawGalleryExtensionFile[], type: string): string { - const result = files.filter(f => f.assetType === type)[0]; - return result && result.source; +function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset { + const result = version.files.filter(f => f.assetType === type)[0]; + + if (!result) { + if (type === AssetType.Icon) { + const uri = require.toUrl('./media/defaultIcon.png'); + return { uri, fallbackUri: uri }; + } + + return null; + } + + if (type === AssetType.VSIX) { + return { + uri: `${version.fallbackAssetUri}/${type}?redirect=true&install=true`, + fallbackUri: `${version.fallbackAssetUri}/${type}?install=true` + }; + } + + return { + uri: `${version.assetUri}/${type}`, + fallbackUri: `${version.fallbackAssetUri}/${type}` + }; } function getDependencies(version: IRawGalleryExtensionVersion): string[] { @@ -211,27 +231,13 @@ function getEngine(version: IRawGalleryExtensionVersion): string { function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string): IGalleryExtension { const [version] = galleryExtension.versions; - - let iconFallback = getAssetSource(version.files, AssetType.Icon); - let icon: string; - - if (iconFallback) { - const parsedUrl = url.parse(iconFallback, true); - parsedUrl.search = undefined; - parsedUrl.query['redirect'] = 'true'; - icon = url.format(parsedUrl); - } else { - iconFallback = icon = require.toUrl('./media/defaultIcon.png'); - } - const assets = { - manifest: getAssetSource(version.files, AssetType.Manifest), - readme: getAssetSource(version.files, AssetType.Details), - changelog: getAssetSource(version.files, AssetType.Changelog), - download: `${getAssetSource(version.files, AssetType.VSIX)}?install=true`, - icon, - iconFallback, - license: getAssetSource(version.files, AssetType.License) + manifest: getVersionAsset(version, AssetType.Manifest), + readme: getVersionAsset(version, AssetType.Details), + changelog: getVersionAsset(version, AssetType.Changelog), + download: getVersionAsset(version, AssetType.VSIX), + icon: getVersionAsset(version, AssetType.Icon), + license: getVersionAsset(version, AssetType.License) }; return { @@ -365,13 +371,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { download(extension: IGalleryExtension): TPromise { return this.loadCompatibleVersion(extension).then(extension => { - const url = extension.assets.download; const zipPath = path.join(tmpdir(), extension.id); const data = getGalleryExtensionTelemetryData(extension); const startTime = new Date().getTime(); const log = duration => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration })); - return this.getAsset({ url }) + return this.getAsset(extension.assets.download) .then(context => download(zipPath, context)) .then(() => log(new Date().getTime() - startTime)) .then(() => zipPath); @@ -379,20 +384,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } getReadme(extension: IGalleryExtension): TPromise { - const url = extension.assets.readme; - - if (!url) { - return TPromise.wrapError('not available'); - } - - return this.getAsset({ url }) + return this.getAsset(extension.assets.readme) .then(asText); } getManifest(extension: IGalleryExtension): TPromise { - const url = extension.assets.manifest; - - return this.getAsset({ url }) + return this.getAsset(extension.assets.manifest) .then(asText) .then(JSON.parse); } @@ -406,6 +403,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (extension.properties.engine && this.isEngineValid(extension.properties.engine)) { return TPromise.wrap(extension); } + const query = new Query() .withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties) .withPage(1, 1) @@ -416,14 +414,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return this.queryGallery(query).then(({ galleryExtensions }) => { const [rawExtension] = galleryExtensions; + if (!rawExtension) { return TPromise.wrapError(new Error(localize('notFound', "Extension not found"))); } + return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions) .then(rawVersion => { extension.properties.dependencies = getDependencies(rawVersion); extension.properties.engine = getEngine(rawVersion); - extension.assets.download = `${getAssetSource(rawVersion.files, AssetType.VSIX)}?install=true`; + extension.assets.download = getVersionAsset(rawVersion, AssetType.VSIX); extension.version = rawVersion.version; return extension; }); @@ -480,29 +480,23 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }); } - /** - * Always try with the `redirect=true` query string. - * If that does not return 200, try without it. - */ - private getAsset(options: IRequestOptions): TPromise { - const parsedUrl = url.parse(options.url, true); - parsedUrl.search = undefined; - parsedUrl.query['redirect'] = 'true'; + private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}): TPromise { + const baseOptions = { type: 'GET' }; return this.commonHTTPHeaders.then(headers => { headers = assign({}, headers, options.headers || {}); - options = assign({}, options, { headers }); + options = assign({}, options, baseOptions, { headers }); - const cdnUrl = url.format(parsedUrl); - const cdnOptions = assign({}, options, { url: cdnUrl }); + const firstOptions = assign({}, options, { url: asset.uri }); - return this.requestService.request(cdnOptions) + return this.requestService.request(firstOptions) .then(context => context.res.statusCode === 200 ? context : TPromise.wrapError('expected 200')) .then(null, err => { this.telemetryService.publicLog('galleryService:requestError', { cdn: true, message: getErrorMessage(err) }); - this.telemetryService.publicLog('galleryService:cdnFallback', { url: cdnUrl }); + this.telemetryService.publicLog('galleryService:cdnFallback', { url: asset.uri }); - return this.requestService.request(options).then(null, err => { + const fallbackOptions = assign({}, options, { url: asset.fallbackUri }); + return this.requestService.request(fallbackOptions).then(null, err => { this.telemetryService.publicLog('galleryService:requestError', { cdn: false, message: getErrorMessage(err) }); return TPromise.wrapError(err); }); @@ -537,10 +531,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } const version = versions[0]; - const url = getAssetSource(version.files, AssetType.Manifest); + const asset = getVersionAsset(version, AssetType.Manifest); const headers = { 'Accept-Encoding': 'gzip' }; - return this.getAsset({ url, headers }) + return this.getAsset(asset, { headers }) .then(context => asJson(context)) .then(manifest => { const engine = manifest.engines.vscode; diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index a9fbeaeeb724a2451c6e4dd52a165aa1cdc15fe5..8aa207b72c041b0cf5217c41bff443c6e90c886d 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -105,7 +105,7 @@ class Extension implements IExtension { return this.local.changelogUrl; } - return this.gallery && this.gallery.assets.changelog; + return this.gallery && this.gallery.assets.changelog && this.gallery.assets.changelog.uri; } get iconUrl(): string { @@ -122,11 +122,11 @@ class Extension implements IExtension { } private get galleryIconUrl(): string { - return this.gallery && this.gallery.assets.icon; + return this.gallery && this.gallery.assets.icon.uri; } private get galleryIconUrlFallback(): string { - return this.gallery && this.gallery.assets.iconFallback; + return this.gallery && this.gallery.assets.icon.fallbackUri; } private get defaultIconUrl(): string { @@ -134,7 +134,7 @@ class Extension implements IExtension { } get licenseUrl(): string { - return this.gallery && this.gallery.assets.license; + return this.gallery && this.gallery.assets.license && this.gallery.assets.license.uri; } get state(): ExtensionState {