extensionGalleryService.ts 8.8 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 18 19
	assetType: string;
}

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

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

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

J
Joao Moreno 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
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
}

interface ICriterium {
	filterType: FilterType;
	value?: string;
}

J
Joao Moreno 已提交
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
const DefaultPageSize = 10;

interface IQueryState {
	pageNumber: number;
	pageSize: number;
	sortBy: SortBy;
	sortOrder: SortOrder;
	flags: Flags;
	criteria: ICriterium[];
}

const DefaultQueryState: IQueryState = {
	pageNumber: 1,
	pageSize: DefaultPageSize,
	sortBy: SortBy.NoneOrRelevance,
	sortOrder: SortOrder.Default,
	flags: Flags.None,
	criteria: []
};

J
Joao Moreno 已提交
91 92
class Query {

J
Joao Moreno 已提交
93 94
	constructor(private state = DefaultQueryState) {}

J
Joao Moreno 已提交
95
	get pageNumber(): number { return this.state.pageNumber; }
J
Joao Moreno 已提交
96
	get pageSize(): number { return this.state.pageSize; }
J
Joao Moreno 已提交
97 98 99
	get sortBy(): number { return this.state.sortBy; }
	get sortOrder(): number { return this.state.sortOrder; }
	get flags(): number { return this.state.flags; }
J
Joao Moreno 已提交
100

J
paging!  
Joao Moreno 已提交
101
	withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query {
J
Joao Moreno 已提交
102
		return new Query(assign({}, this.state, { pageNumber, pageSize }));
J
Joao Moreno 已提交
103 104 105 106 107 108 109 110 111
	}

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

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

J
Joao Moreno 已提交
112 113 114
		const criteria = this.state.criteria.slice();
		criteria.push(criterium);
		return new Query(assign({}, this.state, { criteria }));
J
Joao Moreno 已提交
115 116
	}

J
Joao Moreno 已提交
117 118 119 120 121 122
	withSortBy(sortBy: SortBy): Query {
		return new Query(assign({}, this.state, { sortBy }));
	}

	withSortOrder(sortOrder): Query {
		return new Query(assign({}, this.state, { sortOrder }));
J
Joao Moreno 已提交
123 124 125
	}

	withFlags(...flags: Flags[]): Query {
J
Joao Moreno 已提交
126
		return new Query(assign({}, this.state, { flags: flags.reduce((r, f) => r | f, 0) }));
J
Joao Moreno 已提交
127 128 129 130 131
	}

	get raw(): any {
		return {
			filters: [{
J
Joao Moreno 已提交
132 133 134 135 136
				criteria: this.state.criteria,
				pageNumber: this.state.pageNumber,
				pageSize: this.state.pageSize,
				sortBy: this.state.sortBy,
				sortOrder: this.state.sortOrder
J
Joao Moreno 已提交
137
			}],
J
Joao Moreno 已提交
138
			flags: this.state.flags
J
Joao Moreno 已提交
139 140 141 142
		};
	}
}

J
Joao Moreno 已提交
143 144
function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number {
	const result = (statistics || []).filter(s => s.statisticName === name)[0];
J
Joao Moreno 已提交
145 146 147
	return result ? result.value : 0;
}

J
Joao Moreno 已提交
148
function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, downloadHeaders: any): IGalleryExtension {
J
Joao Moreno 已提交
149 150 151 152 153
	const versions = galleryExtension.versions.map<IGalleryVersion>(v => ({
		version: v.version,
		date: v.lastUpdated,
		downloadHeaders,
		downloadUrl: `${ v.assetUri }/Microsoft.VisualStudio.Services.VSIXPackage?install=true`,
J
Joao Moreno 已提交
154
		manifestUrl: `${ v.assetUri }/Microsoft.VisualStudio.Code.Manifest`,
J
Joao Moreno 已提交
155
		readmeUrl: `${ v.assetUri }/Microsoft.VisualStudio.Services.Content.Details`,
J
Joao Moreno 已提交
156
		iconUrl: `${ v.assetUri }/Microsoft.VisualStudio.Services.Icons.Default`
J
Joao Moreno 已提交
157 158
	}));

J
Joao Moreno 已提交
159 160
	return {
		id: galleryExtension.extensionId,
J
Joao Moreno 已提交
161
		name: galleryExtension.extensionName,
J
Joao Moreno 已提交
162 163
		displayName: galleryExtension.displayName,
		publisherId: galleryExtension.publisher.publisherId,
J
Joao Moreno 已提交
164
		publisher: galleryExtension.publisher.publisherName,
J
Joao Moreno 已提交
165
		publisherDisplayName: galleryExtension.publisher.displayName,
J
Joao Moreno 已提交
166
		description: galleryExtension.shortDescription || '',
J
Joao Moreno 已提交
167 168 169
		installCount: getStatistic(galleryExtension.statistics, 'install'),
		rating: getStatistic(galleryExtension.statistics, 'averagerating'),
		ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),
J
Joao Moreno 已提交
170
		versions
J
Joao Moreno 已提交
171 172 173
	};
}

J
Joao Moreno 已提交
174
export class ExtensionGalleryService implements IExtensionGalleryService {
E
Erich Gamma 已提交
175

176
	_serviceBrand: any;
E
Erich Gamma 已提交
177 178

	private extensionsGalleryUrl: string;
179
	private machineId: TPromise<string>;
E
Erich Gamma 已提交
180 181 182

	constructor(
		@IRequestService private requestService: IRequestService,
J
Joao Moreno 已提交
183
		@ITelemetryService private telemetryService: ITelemetryService
E
Erich Gamma 已提交
184
	) {
185
		const config = product.extensionsGallery;
J
Joao Moreno 已提交
186
		this.extensionsGalleryUrl = config && config.serviceUrl;
187
		this.machineId = telemetryService.getTelemetryInfo().then(({ machineId }) => machineId);
E
Erich Gamma 已提交
188 189 190 191 192 193
	}

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

J
Joao Moreno 已提交
194
	isEnabled(): boolean {
E
Erich Gamma 已提交
195 196 197
		return !!this.extensionsGalleryUrl;
	}

J
Joao Moreno 已提交
198
	query(options: IQueryOptions = {}): TPromise<IPager<IGalleryExtension>> {
J
Joao Moreno 已提交
199
		if (!this.isEnabled()) {
E
Erich Gamma 已提交
200 201 202
			return TPromise.wrapError(new Error('No extension gallery service configured.'));
		}

203
		const type = options.names ? 'ids' : (options.text ? 'text' : 'all');
J
Joao Moreno 已提交
204
		const text = options.text || '';
J
Joao Moreno 已提交
205
		const pageSize = getOrDefault(options, o => o.pageSize, 50);
J
Joao Moreno 已提交
206

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

J
Joao Moreno 已提交
209 210
		let query = new Query()
			.withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeAssetUri, Flags.IncludeStatistics)
J
Joao Moreno 已提交
211
			.withPage(1, pageSize)
212
			.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');
J
Joao Moreno 已提交
213 214

		if (text) {
J
Joao Moreno 已提交
215
			query = query.withFilter(FilterType.SearchText, text).withSortBy(SortBy.NoneOrRelevance);
216
		} else if (options.ids) {
217 218 219
			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);
220
		} else {
J
Joao Moreno 已提交
221 222 223 224 225 226 227 228 229
			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 已提交
230 231
		}

J
Joao Moreno 已提交
232 233 234 235
		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 已提交
236
				const getPage = pageIndex => this.queryGallery(query.withPage(pageIndex + 1))
J
Joao Moreno 已提交
237
					.then(({ galleryExtensions }) => galleryExtensions.map(e => toExtension(e, this.extensionsGalleryUrl, downloadHeaders)));
J
Joao Moreno 已提交
238

J
Joao Moreno 已提交
239
				return { firstPage: extensions, total, pageSize, getPage };
240
			});
J
Joao Moreno 已提交
241 242
		});
	}
243

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

J
Joao Moreno 已提交
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
		return this.getRequestHeaders()
			.then(headers => {
				headers = assign(headers, {
					'Content-Type': 'application/json',
					'Accept': 'application/json;api-version=3.0-preview.1',
					'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 已提交
272
	}
273 274 275 276

	private getRequestHeaders(): TPromise<any> {
		return this.machineId.then(machineId => {
			const result = {
277 278
				'X-Market-Client-Id': `VSCode ${ pkg.version }`,
				'User-Agent': `VSCode ${ pkg.version }`
279 280 281 282 283 284 285 286 287
			};

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

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