Skip to content

Commit

Permalink
contain gallery code inside GalleryService
Browse files Browse the repository at this point in the history
related to #10180
  • Loading branch information
joaomoreno committed Aug 16, 2016
1 parent 416fe53 commit c8caac3
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 192 deletions.
5 changes: 2 additions & 3 deletions src/vs/code/node/cliProcessMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ class Main {
console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id));
console.log(localize('installing', "Installing..."));

return this.extensionManagementService.install(extension).then(() => {
console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.versions[0].version));
});
return this.extensionManagementService.installFromGallery(extension)
.then(() => console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)));
});
});
});
Expand Down
7 changes: 5 additions & 2 deletions src/vs/code/node/sharedProcessMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
import { IEventService } from 'vs/platform/event/common/event';
import { EventService } from 'vs/platform/event/common/eventService';
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NodeConfigurationService } from 'vs/platform/configuration/node/nodeConfigurationService';
import { ITelemetryService, combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry';
Expand Down Expand Up @@ -55,7 +56,6 @@ function main(server: Server): void {

services.set(IEventService, new SyncDescriptor(EventService));
services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService));
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IConfigurationService, new SyncDescriptor(NodeConfigurationService));

const instantiationService = new InstantiationService(services);
Expand Down Expand Up @@ -93,6 +93,9 @@ function main(server: Server): void {
services.set(ITelemetryService, NullTelemetryService);
}

services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));

const instantiationService2 = instantiationService.createChild(services);

instantiationService2.invokeFunction(accessor => {
Expand Down
27 changes: 14 additions & 13 deletions src/vs/platform/extensionManagement/common/extensionManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,24 @@ export interface IExtensionManifest {
icon?: string;
}

export interface IGalleryVersion {
version: string;
date: string;
manifestUrl: string;
readmeUrl: string;
downloadUrl: string;
iconUrl: string;
licenseUrl: string;
downloadHeaders: { [key: string]: string; };
}

export interface IExtensionIdentity {
name: string;
publisher: string;
}

export interface IGalleryExtensionAssets {
manifest: string;
readme: string;
download: string;
icon: string;
license: string;
}

export interface IGalleryExtension {
id: string;
name: string;
version: string;
date: string;
displayName: string;
publisherId: string;
publisher: string;
Expand All @@ -49,7 +48,8 @@ export interface IGalleryExtension {
installCount: number;
rating: number;
ratingCount: number;
versions: IGalleryVersion[];
assets: IGalleryExtensionAssets;
downloadHeaders: { [key: string]: string; };
}

export interface IGalleryMetadata {
Expand Down Expand Up @@ -98,6 +98,7 @@ export interface IExtensionGalleryService {
_serviceBrand: any;
isEnabled(): boolean;
query(options?: IQueryOptions): TPromise<IPager<IGalleryExtension>>;
download(extension: IGalleryExtension): TPromise<string>;
}

export type InstallExtensionEvent = { id: string; gallery?: IGalleryExtension; };
Expand All @@ -111,8 +112,8 @@ export interface IExtensionManagementService {
onUninstallExtension: Event<string>;
onDidUninstallExtension: Event<string>;

install(extension: IGalleryExtension): TPromise<void>;
install(zipPath: string): TPromise<void>;
installFromGallery(extension: IGalleryExtension): TPromise<void>;
uninstall(extension: ILocalExtension): TPromise<void>;
getInstalled(): TPromise<ILocalExtension[]>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@

import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent } from './extensionManagement';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension } from './extensionManagement';
import Event from 'vs/base/common/event';

export interface IExtensionManagementChannel extends IChannel {
call(command: 'event:onInstallExtension'): TPromise<void>;
call(command: 'event:onDidInstallExtension'): TPromise<void>;
call(command: 'event:onUninstallExtension'): TPromise<void>;
call(command: 'event:onDidUninstallExtension'): TPromise<void>;
call(command: 'install', extensionOrPath: ILocalExtension | string): TPromise<ILocalExtension>;
call(command: 'install', path: string): TPromise<void>;
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<void>;
call(command: 'uninstall', extension: ILocalExtension): TPromise<void>;
call(command: 'getInstalled'): TPromise<ILocalExtension[]>;
call(command: string, arg: any): TPromise<any>;
Expand All @@ -32,6 +33,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
case 'event:onUninstallExtension': return eventToCall(this.service.onUninstallExtension);
case 'event:onDidUninstallExtension': return eventToCall(this.service.onDidUninstallExtension);
case 'install': return this.service.install(arg);
case 'installFromGallery': return this.service.installFromGallery(arg);
case 'uninstall': return this.service.uninstall(arg);
case 'getInstalled': return this.service.getInstalled();
}
Expand All @@ -56,10 +58,12 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
private _onDidUninstallExtension = eventFromCall<string>(this.channel, 'event:onDidUninstallExtension');
get onDidUninstallExtension(): Event<string> { return this._onDidUninstallExtension; }

install(extension: IGalleryExtension): TPromise<void>;
install(zipPath: string): TPromise<void>;
install(arg: any): TPromise<void> {
return this.channel.call('install', arg);
install(zipPath: string): TPromise<void> {
return this.channel.call('install', zipPath);
}

installFromGallery(extension: IGalleryExtension): TPromise<void> {
return this.channel.call('installFromGallery', extension);
}

uninstall(extension: ILocalExtension): TPromise<void> {
Expand Down
159 changes: 111 additions & 48 deletions src/vs/platform/extensionManagement/node/extensionGalleryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from 'vs/nls';
import { tmpdir } from 'os';
import * as path from 'path';
import { TPromise } from 'vs/base/common/winjs.base';
import { IGalleryExtension, IExtensionGalleryService, IGalleryVersion, IQueryOptions, SortBy, SortOrder } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IGalleryExtension, IExtensionGalleryService, IQueryOptions, SortBy, SortOrder, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { isUndefined } from 'vs/base/common/types';
import { assign, getOrDefault } from 'vs/base/common/objects';
import { IRequestService } from 'vs/platform/request/common/request';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPager } from 'vs/base/common/paging';
import { download, json, IRequestOptions } from 'vs/base/node/request';
import { getProxyAgent } from 'vs/base/node/proxy';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import pkg from 'vs/platform/package';
import product from 'vs/platform/product';
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';

interface IRawGalleryExtensionFile {
assetType: string;
Expand Down Expand Up @@ -158,21 +165,21 @@ function getAssetSource(files: IRawGalleryExtensionFile[], type: string): string
return result && result.source;
}

function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, downloadHeaders: any): IGalleryExtension {
const versions = galleryExtension.versions.map<IGalleryVersion>(v => ({
version: v.version,
date: v.lastUpdated,
downloadHeaders,
downloadUrl: `${ v.assetUri }/${ AssetType.VSIX }?install=true`,
manifestUrl: `${ v.assetUri }/${ AssetType.Manifest }`,
readmeUrl: `${ v.assetUri }/${ AssetType.Details }`,
iconUrl: getAssetSource(v.files, AssetType.Icon) || require.toUrl('./media/defaultIcon.png'),
licenseUrl: getAssetSource(v.files, AssetType.License)
}));
function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, downloadHeaders: { [key: string]: string; }): IGalleryExtension {
const [version] = galleryExtension.versions;
const assets = {
manifest: getAssetSource(version.files, AssetType.Manifest),
readme: getAssetSource(version.files, AssetType.Details),
download: `${ getAssetSource(version.files, AssetType.VSIX) }?install=true`,
icon: getAssetSource(version.files, AssetType.Icon) || require.toUrl('./media/defaultIcon.png'),
license: getAssetSource(version.files, AssetType.License)
};

return {
id: galleryExtension.extensionId,
name: galleryExtension.extensionName,
version: version.version,
date: version.lastUpdated,
displayName: galleryExtension.displayName,
publisherId: galleryExtension.publisher.publisherId,
publisher: galleryExtension.publisher.publisherName,
Expand All @@ -181,7 +188,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
installCount: getStatistic(galleryExtension.statistics, 'install'),
rating: getStatistic(galleryExtension.statistics, 'averagerating'),
ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),
versions
assets,
downloadHeaders
};
}

Expand All @@ -190,15 +198,29 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
_serviceBrand: any;

private extensionsGalleryUrl: string;
private machineId: TPromise<string>;

private getCommonHeaders(): TPromise<{ [key: string]: string; }> {
return this.telemetryService.getTelemetryInfo().then(({ machineId }) => {
const result: { [key: string]: string; } = {
'X-Market-Client-Id': `VSCode ${ pkg.version }`,
'User-Agent': `VSCode ${ pkg.version }`
};

if (machineId) {
result['X-Market-User-Id'] = machineId;
}

return result;
});
}

constructor(
@IRequestService private requestService: IRequestService,
@ITelemetryService private telemetryService: ITelemetryService
@ITelemetryService private telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService
) {
const config = product.extensionsGallery;
this.extensionsGalleryUrl = config && config.serviceUrl;
this.machineId = telemetryService.getTelemetryInfo().then(({ machineId }) => machineId);
}

private api(path = ''): string {
Expand All @@ -221,10 +243,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
this.telemetryService.publicLog('galleryService:query', { type, text });

let query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles)
.withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles)
.withPage(1, pageSize)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withAssetTypes(AssetType.Icon, AssetType.License);
.withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX);

if (text) {
query = query.withFilter(FilterType.SearchText, text).withSortBy(SortBy.NoneOrRelevance);
Expand All @@ -245,7 +267,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}

return this.queryGallery(query).then(({ galleryExtensions, total }) => {
return this.getRequestHeaders().then(downloadHeaders => {
return this.getCommonHeaders().then(downloadHeaders => {
const extensions = galleryExtensions.map(e => toExtension(e, this.extensionsGalleryUrl, downloadHeaders));
const pageSize = query.pageSize;
const getPage = pageIndex => this.queryGallery(query.withPage(pageIndex + 1))
Expand All @@ -258,27 +280,19 @@ export class ExtensionGalleryService implements IExtensionGalleryService {

private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
const data = JSON.stringify(query.raw);

return this.getRequestHeaders()
.then(headers => {
headers = assign(headers, {
'Content-Type': 'application/json',
'Accept': 'application/json;api-version=3.0-preview.1',
'Accept-Encoding': 'gzip',
'Content-Length': data.length
});

const request = {
type: 'POST',
url: this.api('/extensionquery'),
data,
headers
};

return this.requestService.makeRequest(request);
})
.then(r => JSON.parse(r.responseText).results[0])
.then(r => {
const request = this.request(this.api('/extensionquery'));

return this.getCommonHeaders()
.then(headers => assign(headers, {
'Content-Type': 'application/json',
'Accept': 'application/json;api-version=3.0-preview.1',
'Accept-Encoding': 'gzip',
'Content-Length': data.length
}))
.then(headers => assign(request, { type: 'POST', data, headers }))
.then(() => json<any>(request))
.then(result => {
const r = result.results[0];
const galleryExtensions = r.extensions;
const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];
const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;
Expand All @@ -287,18 +301,67 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}

private getRequestHeaders(): TPromise<any> {
return this.machineId.then(machineId => {
const result = {
'X-Market-Client-Id': `VSCode ${ pkg.version }`,
'User-Agent': `VSCode ${ pkg.version }`
download(extension: IGalleryExtension): TPromise<string> {
const query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withAssetTypes(AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionId, extension.id);

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 => {
const url = `${ getAssetSource(rawVersion.files, AssetType.VSIX) }?install=true`;
const zipPath = path.join(tmpdir(), extension.id);
const request = this.request(url);

return this.getCommonHeaders()
.then(headers => assign(request, { headers }))
.then(() => download(zipPath, request))
.then(() => zipPath);
});
});
}

private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
if (!versions.length) {
return TPromise.wrapError(new Error(localize('noCompatible', "Couldn't find a compatible version of {0} with this version of Code.", extension.displayName || extension.extensionName)));
}

const version = versions[0];
const url = getAssetSource(version.files, AssetType.Manifest);
let request = this.request(url);
request = assign(request, { headers: { 'accept-encoding': 'gzip' } });

return json<IExtensionManifest>(request).then(manifest => {
const desc = {
isBuiltin: false,
engines: { vscode: manifest.engines.vscode },
main: manifest.main
};

if (machineId) {
result['X-Market-User-Id'] = machineId;
if (!isValidExtensionVersion(pkg.version, desc, [])) {
return this.getLastValidExtensionVersion(extension, versions.slice(1));
}

return result;
return version;
});
}

// Helper for proxy business... shameful.
// This should be pushed down and not rely on the context service
private request(url: string): IRequestOptions {
const httpConfig = this.configurationService.getConfiguration<any>('http') || {};
const proxyUrl = httpConfig.proxy as string;
const strictSSL = httpConfig.proxyStrictSSL as boolean;
const agent = getProxyAgent(url, { proxyUrl, strictSSL });

return { url, agent, strictSSL };
}
}
Loading

0 comments on commit c8caac3

Please sign in to comment.