extensionGalleryService.ts 9.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { TPromise } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
7
import { IGalleryExtension, IExtensionGalleryService, IGalleryVersion, IQueryOptions, SortBy, SortOrder } from 'vs/platform/extensionManagement/common/extensionManagement';
J
Joao Moreno 已提交
8
import { isUndefined } from 'vs/base/common/types';
J
Joao Moreno 已提交
9
import { assign, getOrDefault } from 'vs/base/common/objects';
E
Erich Gamma 已提交
10
import { IRequestService } from 'vs/platform/request/common/request';
J
Joao Moreno 已提交
11
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
J
Joao Moreno 已提交
12
import { IPager } from 'vs/base/common/paging';
13
import pkg from 'vs/platform/package';
J
Joao Moreno 已提交
14
import product from 'vs/platform/product';
E
Erich Gamma 已提交
15

J
Joao Moreno 已提交
16
interface IRawGalleryExtensionFile {
E
Erich Gamma 已提交
17
	assetType: string;
J
Joao Moreno 已提交
18
	source: string;
E
Erich Gamma 已提交
19 20
}

J
Joao Moreno 已提交
21
interface IRawGalleryExtensionVersion {
E
Erich Gamma 已提交
22 23
	version: string;
	lastUpdated: string;
J
Joao Moreno 已提交
24
	assetUri: string;
J
Joao Moreno 已提交
25
	files: IRawGalleryExtensionFile[];
E
Erich Gamma 已提交
26 27
}

J
Joao Moreno 已提交
28
interface IRawGalleryExtension {
E
Erich Gamma 已提交
29 30 31 32 33
	extensionId: string;
	extensionName: string;
	displayName: string;
	shortDescription: string;
	publisher: { displayName: string, publisherId: string, publisherName: string; };
J
Joao Moreno 已提交
34 35
	versions: IRawGalleryExtensionVersion[];
	statistics: IRawGalleryExtensionStatistics[];
36 37
}

J
Joao Moreno 已提交
38
interface IRawGalleryExtensionStatistics {
39 40
	statisticName: string;
	value: number;
E
Erich Gamma 已提交
41 42
}

J
Joao Moreno 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
enum Flags {
	None = 0x0,
	IncludeVersions = 0x1,
	IncludeFiles = 0x2,
	IncludeCategoryAndTags = 0x4,
	IncludeSharedAccounts = 0x8,
	IncludeVersionProperties = 0x10,
	ExcludeNonValidated = 0x20,
	IncludeInstallationTargets = 0x40,
	IncludeAssetUri = 0x80,
	IncludeStatistics = 0x100,
	IncludeLatestVersionOnly = 0x200
}

enum FilterType {
	Tag = 1,
	ExtensionId = 4,
	Category = 5,
	ExtensionName = 7,
	Target = 8,
	Featured = 9,
	SearchText = 10
}

67 68 69 70
const AssetType = {
	Icon: 'Microsoft.VisualStudio.Services.Icons.Default',
	Details: 'Microsoft.VisualStudio.Services.Content.Details',
	Manifest: 'Microsoft.VisualStudio.Code.Manifest',
71 72
	VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage',
	License: 'Microsoft.VisualStudio.Services.Content.License'
73 74
};

J
Joao Moreno 已提交
75 76 77 78 79
interface ICriterium {
	filterType: FilterType;
	value?: string;
}

J
Joao Moreno 已提交
80 81 82 83 84 85 86 87 88
const DefaultPageSize = 10;

interface IQueryState {
	pageNumber: number;
	pageSize: number;
	sortBy: SortBy;
	sortOrder: SortOrder;
	flags: Flags;
	criteria: ICriterium[];
89
	assetTypes: string[];
J
Joao Moreno 已提交
90 91 92 93 94 95 96 97
}

const DefaultQueryState: IQueryState = {
	pageNumber: 1,
	pageSize: DefaultPageSize,
	sortBy: SortBy.NoneOrRelevance,
	sortOrder: SortOrder.Default,
	flags: Flags.None,
98 99
	criteria: [],
	assetTypes: []
J
Joao Moreno 已提交
100 101
};

J
Joao Moreno 已提交
102 103
class Query {

J
Joao Moreno 已提交
104 105
	constructor(private state = DefaultQueryState) {}

J
Joao Moreno 已提交
106
	get pageNumber(): number { return this.state.pageNumber; }
J
Joao Moreno 已提交
107
	get pageSize(): number { return this.state.pageSize; }
J
Joao Moreno 已提交
108 109 110
	get sortBy(): number { return this.state.sortBy; }
	get sortOrder(): number { return this.state.sortOrder; }
	get flags(): number { return this.state.flags; }
J
Joao Moreno 已提交
111

J
paging!  
Joao Moreno 已提交
112
	withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query {
J
Joao Moreno 已提交
113
		return new Query(assign({}, this.state, { pageNumber, pageSize }));
J
Joao Moreno 已提交
114 115 116 117 118 119 120 121 122
	}

	withFilter(filterType: FilterType, value?: string): Query {
		const criterium: ICriterium = { filterType };

		if (!isUndefined(value)) {
			criterium.value = value;
		}

J
Joao Moreno 已提交
123 124 125
		const criteria = this.state.criteria.slice();
		criteria.push(criterium);
		return new Query(assign({}, this.state, { criteria }));
J
Joao Moreno 已提交
126 127
	}

J
Joao Moreno 已提交
128 129 130 131
	withSortBy(sortBy: SortBy): Query {
		return new Query(assign({}, this.state, { sortBy }));
	}

132
	withSortOrder(sortOrder: SortOrder): Query {
J
Joao Moreno 已提交
133
		return new Query(assign({}, this.state, { sortOrder }));
J
Joao Moreno 已提交
134 135 136
	}

	withFlags(...flags: Flags[]): Query {
J
Joao Moreno 已提交
137
		return new Query(assign({}, this.state, { flags: flags.reduce((r, f) => r | f, 0) }));
J
Joao Moreno 已提交
138 139
	}

140 141 142 143
	withAssetTypes(...assetTypes: string[]): Query {
		return new Query(assign({}, this.state, { assetTypes }));
	}

J
Joao Moreno 已提交
144
	get raw(): any {
145 146 147
		const { criteria, pageNumber, pageSize, sortBy, sortOrder, flags, assetTypes } = this.state;
		const filters = [{ criteria, pageNumber, pageSize, sortBy, sortOrder }];
		return { filters, assetTypes, flags };
J
Joao Moreno 已提交
148 149 150
	}
}

J
Joao Moreno 已提交
151 152
function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number {
	const result = (statistics || []).filter(s => s.statisticName === name)[0];
J
Joao Moreno 已提交
153 154 155
	return result ? result.value : 0;
}

156 157 158 159 160
function getAssetSource(files: IRawGalleryExtensionFile[], type: string): string {
	const result = files.filter(f => f.assetType === type)[0];
	return result && result.source;
}

J
Joao Moreno 已提交
161
function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, downloadHeaders: any): IGalleryExtension {
162 163 164 165 166 167 168 169 170 171
	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)
	}));
J
Joao Moreno 已提交
172

J
Joao Moreno 已提交
173 174
	return {
		id: galleryExtension.extensionId,
J
Joao Moreno 已提交
175
		name: galleryExtension.extensionName,
J
Joao Moreno 已提交
176 177
		displayName: galleryExtension.displayName,
		publisherId: galleryExtension.publisher.publisherId,
J
Joao Moreno 已提交
178
		publisher: galleryExtension.publisher.publisherName,
J
Joao Moreno 已提交
179
		publisherDisplayName: galleryExtension.publisher.displayName,
J
Joao Moreno 已提交
180
		description: galleryExtension.shortDescription || '',
J
Joao Moreno 已提交
181 182 183
		installCount: getStatistic(galleryExtension.statistics, 'install'),
		rating: getStatistic(galleryExtension.statistics, 'averagerating'),
		ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),
J
Joao Moreno 已提交
184
		versions
J
Joao Moreno 已提交
185 186 187
	};
}

J
Joao Moreno 已提交
188
export class ExtensionGalleryService implements IExtensionGalleryService {
E
Erich Gamma 已提交
189

190
	_serviceBrand: any;
E
Erich Gamma 已提交
191 192

	private extensionsGalleryUrl: string;
193
	private machineId: TPromise<string>;
E
Erich Gamma 已提交
194 195 196

	constructor(
		@IRequestService private requestService: IRequestService,
J
Joao Moreno 已提交
197
		@ITelemetryService private telemetryService: ITelemetryService
E
Erich Gamma 已提交
198
	) {
199
		const config = product.extensionsGallery;
J
Joao Moreno 已提交
200
		this.extensionsGalleryUrl = config && config.serviceUrl;
201
		this.machineId = telemetryService.getTelemetryInfo().then(({ machineId }) => machineId);
E
Erich Gamma 已提交
202 203 204 205 206 207
	}

	private api(path = ''): string {
		return `${ this.extensionsGalleryUrl }${ path }`;
	}

J
Joao Moreno 已提交
208
	isEnabled(): boolean {
E
Erich Gamma 已提交
209 210 211
		return !!this.extensionsGalleryUrl;
	}

J
Joao Moreno 已提交
212
	query(options: IQueryOptions = {}): TPromise<IPager<IGalleryExtension>> {
J
Joao Moreno 已提交
213
		if (!this.isEnabled()) {
E
Erich Gamma 已提交
214 215 216
			return TPromise.wrapError(new Error('No extension gallery service configured.'));
		}

217
		const type = options.names ? 'ids' : (options.text ? 'text' : 'all');
J
Joao Moreno 已提交
218
		const text = options.text || '';
J
Joao Moreno 已提交
219
		const pageSize = getOrDefault(options, o => o.pageSize, 50);
J
Joao Moreno 已提交
220

J
Joao Moreno 已提交
221
		this.telemetryService.publicLog('galleryService:query', { type, text });
J
Joao Moreno 已提交
222

J
Joao Moreno 已提交
223
		let query = new Query()
J
Joao Moreno 已提交
224
			.withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles)
J
Joao Moreno 已提交
225
			.withPage(1, pageSize)
J
Joao Moreno 已提交
226
			.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
227
			.withAssetTypes(AssetType.Icon, AssetType.License);
J
Joao Moreno 已提交
228 229

		if (text) {
J
Joao Moreno 已提交
230
			query = query.withFilter(FilterType.SearchText, text).withSortBy(SortBy.NoneOrRelevance);
231
		} else if (options.ids) {
232 233 234
			query = options.ids.reduce((query, id) => query.withFilter(FilterType.ExtensionId, id), query);
		} else if (options.names) {
			query = options.names.reduce((query, name) => query.withFilter(FilterType.ExtensionName, name), query);
235
		} else {
J
Joao Moreno 已提交
236 237 238 239 240 241 242 243 244
			query = query.withSortBy(SortBy.InstallCount);
		}

		if (typeof options.sortBy === 'number') {
			query = query.withSortBy(options.sortBy);
		}

		if (typeof options.sortOrder === 'number') {
			query = query.withSortOrder(options.sortOrder);
J
Joao Moreno 已提交
245 246
		}

J
Joao Moreno 已提交
247 248 249 250
		return this.queryGallery(query).then(({ galleryExtensions, total }) => {
			return this.getRequestHeaders().then(downloadHeaders => {
				const extensions = galleryExtensions.map(e => toExtension(e, this.extensionsGalleryUrl, downloadHeaders));
				const pageSize = query.pageSize;
J
paging!  
Joao Moreno 已提交
251
				const getPage = pageIndex => this.queryGallery(query.withPage(pageIndex + 1))
J
Joao Moreno 已提交
252
					.then(({ galleryExtensions }) => galleryExtensions.map(e => toExtension(e, this.extensionsGalleryUrl, downloadHeaders)));
J
Joao Moreno 已提交
253

J
Joao Moreno 已提交
254
				return { firstPage: extensions, total, pageSize, getPage };
255
			});
J
Joao Moreno 已提交
256 257
		});
	}
258

J
Joao Moreno 已提交
259
	private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
J
Joao Moreno 已提交
260
		const data = JSON.stringify(query.raw);
J
Joao Moreno 已提交
261

J
Joao Moreno 已提交
262 263 264 265 266
		return this.getRequestHeaders()
			.then(headers => {
				headers = assign(headers, {
					'Content-Type': 'application/json',
					'Accept': 'application/json;api-version=3.0-preview.1',
J
Joao Moreno 已提交
267
					'Accept-Encoding': 'gzip',
J
Joao Moreno 已提交
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
					'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 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;

				return { galleryExtensions, total };
			});
J
Joao Moreno 已提交
288
	}
289 290 291 292

	private getRequestHeaders(): TPromise<any> {
		return this.machineId.then(machineId => {
			const result = {
293 294
				'X-Market-Client-Id': `VSCode ${ pkg.version }`,
				'User-Agent': `VSCode ${ pkg.version }`
295 296 297 298 299 300 301 302 303
			};

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

			return result;
		});
	}
E
Erich Gamma 已提交
304
}