Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -3,13 +3,11 @@
* Licensed under the Source EULA. 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 { distinct } from 'vs/base/common/arrays';
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { assign, getOrDefault } from 'vs/base/common/objects';
import { IRequestService } from 'vs/platform/request/node/request';
@@ -27,6 +25,9 @@ import { values } from 'vs/base/common/map';
// {{SQL CARBON EDIT}}
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/parts/extensions/common/extensions';
// {{SQL CARBON EDIT}} - End
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
interface IRawGalleryExtensionFile {
assetType: string;
@@ -121,7 +122,8 @@ const AssetType = {
const PropertyType = {
Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies',
ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack',
Engine: 'Microsoft.VisualStudio.Code.Engine'
Engine: 'Microsoft.VisualStudio.Code.Engine',
LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages'
};
interface ICriterium {
@@ -296,18 +298,23 @@ function getEngine(version: IRawGalleryExtensionVersion): string {
return (values.length > 0 && values[0].value) || '';
}
function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] {
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : [];
const value = (values.length > 0 && values[0].value) || '';
return value ? value.split(',') : [];
}
function getIsPreview(flags: string): boolean {
return flags.indexOf('preview') !== -1;
}
function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, index: number, query: Query, querySource?: string): IGalleryExtension {
const [version] = galleryExtension.versions;
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension {
const assets = {
manifest: getVersionAsset(version, AssetType.Manifest),
readme: getVersionAsset(version, AssetType.Details),
changelog: getVersionAsset(version, AssetType.Changelog),
download: getVersionAsset(version, AssetType.VSIX),
// {{SQL CARBON EDIT}}
// {{SQL CARBON EDIT}} - Add downloadPage
downloadPage: getVersionAsset(version, AssetType.DownloadPage),
icon: getVersionAsset(version, AssetType.Icon),
license: getVersionAsset(version, AssetType.License),
@@ -335,7 +342,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
properties: {
dependencies: getExtensions(version, PropertyType.Dependency),
extensionPack: getExtensions(version, PropertyType.ExtensionPack),
engine: getEngine(version)
engine: getEngine(version),
localizedLanguages: getLocalizedLanguages(version)
},
/* __GDPR__FRAGMENT__
"GalleryExtensionTelemetryData2" : {
@@ -365,10 +373,11 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
private extensionsGalleryUrl: string;
private extensionsControlUrl: string;
private readonly commonHeadersPromise: TPromise<{ [key: string]: string; }>;
private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>;
constructor(
@IRequestService private requestService: IRequestService,
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
// {{SQL CARBON EDIT}}
@@ -389,9 +398,34 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return !!this.extensionsGalleryUrl;
}
query(options: IQueryOptions = {}): TPromise<IPager<IGalleryExtension>> {
getExtension({ id, uuid }: IExtensionIdentifier, version?: string): Promise<IGalleryExtension> {
let query = new Query()
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished));
if (uuid) {
query = query.withFilter(FilterType.ExtensionId, uuid);
} else {
query = query.withFilter(FilterType.ExtensionName, id);
}
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => {
if (galleryExtensions.length) {
const galleryExtension = galleryExtensions[0];
const versionAsset = version ? galleryExtension.versions.filter(v => v.version === version)[0] : galleryExtension.versions[0];
if (versionAsset) {
return toExtension(galleryExtension, versionAsset, 0, query);
}
}
return null;
});
}
query(options: IQueryOptions = {}): Promise<IPager<IGalleryExtension>> {
if (!this.isEnabled()) {
return TPromise.wrapError<IPager<IGalleryExtension>>(new Error('No extension gallery service configured.'));
return Promise.reject(new Error('No extension gallery service configured.'));
}
const type = options.names ? 'ids' : (options.text ? 'text' : 'all');
@@ -449,18 +483,21 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
query = query.withSortOrder(options.sortOrder);
}
return this.queryGallery(query).then(({ galleryExtensions, total }) => {
const extensions = galleryExtensions.map((e, index) => toExtension(e, this.extensionsGalleryUrl, index, query, options.source));
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions, total }) => {
const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source));
// {{SQL CARBON EDIT}}
const pageSize = extensions.length;
const getPage = (pageIndex: number) => {
const getPage = (pageIndex: number, ct: CancellationToken) => {
if (ct.isCancellationRequested) {
return Promise.reject(canceled());
}
const nextPageQuery = query.withPage(pageIndex + 1);
return this.queryGallery(nextPageQuery)
.then(({ galleryExtensions }) => galleryExtensions.map((e, index) => toExtension(e, this.extensionsGalleryUrl, index, nextPageQuery, options.source)));
return this.queryGallery(nextPageQuery, ct)
.then(({ galleryExtensions }) => galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, nextPageQuery, options.source)));
};
return { firstPage: extensions, total, pageSize, getPage };
return { firstPage: extensions, total, pageSize, getPage } as IPager<IGalleryExtension>;
});
}
@@ -544,7 +581,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return a[fieldName] < b[fieldName] ? -1 : 1;
}
private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
private queryGallery(query: Query, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
return this.commonHeadersPromise.then(commonHeaders => {
const data = JSON.stringify(query.raw);
const headers = assign({}, commonHeaders, {
@@ -560,7 +597,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
url: this.api('/extensionquery'),
data,
headers
}).then(context => {
}, token).then(context => {
// {{SQL CARBON EDIT}}
let extensionPolicy: string = this.configurationService.getValue<string>(ExtensionsPolicyKey);
@@ -583,9 +620,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): TPromise<void> {
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {
if (!this.isEnabled()) {
return TPromise.as(null);
return Promise.resolve(null);
}
return this.commonHeadersPromise.then(commonHeaders => {
@@ -595,79 +632,101 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
type: 'POST',
url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`),
headers
}).then(null, () => null);
}, CancellationToken.None).then(null, () => null);
});
}
download(extension: IGalleryExtension, operation: InstallOperation): TPromise<string> {
return this.loadCompatibleVersion(extension)
.then(extension => {
if (!extension) {
return TPromise.wrapError(new Error(localize('notCompatibleDownload', "Unable to download because the extension compatible with current version '{0}' of VS Code is not found.", pkg.version)));
}
const zipPath = path.join(tmpdir(), generateUuid());
const data = getGalleryExtensionTelemetryData(extension);
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration }));
download(extension: IGalleryExtension, operation: InstallOperation): Promise<string> {
this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);
const zipPath = path.join(tmpdir(), generateUuid());
const data = getGalleryExtensionTelemetryData(extension);
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration }));
// {{SQL Carbon Edit}} - Don't append install or update on to the URL
// const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
const operationParam = undefined;
const downloadAsset = operationParam ? {
uri: `${extension.assets.download.uri}&${operationParam}=true`,
fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true`
} : extension.assets.download;
// {{SQL Carbon Edit}} - Don't append install or update on to the URL
// const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
const operationParam = undefined;
const downloadAsset = operationParam ? {
uri: `${extension.assets.download.uri}&${operationParam}=true`,
fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true`
} : extension.assets.download;
return this.getAsset(downloadAsset)
.then(context => download(zipPath, context))
.then(() => log(new Date().getTime() - startTime))
.then(() => zipPath);
});
return this.getAsset(downloadAsset)
.then(context => download(zipPath, context))
.then(() => log(new Date().getTime() - startTime))
.then(() => zipPath);
}
getReadme(extension: IGalleryExtension): TPromise<string> {
return this.getAsset(extension.assets.readme)
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
return this.getAsset(extension.assets.readme, {}, token)
.then(asText);
}
getManifest(extension: IGalleryExtension): TPromise<IExtensionManifest> {
return this.getAsset(extension.assets.manifest)
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest> {
return this.getAsset(extension.assets.manifest, {}, token)
.then(asText)
.then(JSON.parse);
}
getCoreTranslation(extension: IGalleryExtension, languageId: string): TPromise<ITranslation> {
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation> {
const asset = extension.assets.coreTranslations[languageId.toUpperCase()];
if (asset) {
return this.getAsset(asset)
.then(asText)
.then(JSON.parse);
}
return TPromise.as(null);
return Promise.resolve(null);
}
getChangelog(extension: IGalleryExtension): TPromise<string> {
return this.getAsset(extension.assets.changelog)
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
return this.getAsset(extension.assets.changelog, {}, token)
.then(asText);
}
loadAllDependencies(extensions: IExtensionIdentifier[]): TPromise<IGalleryExtension[]> {
return this.getDependenciesReccursively(extensions.map(e => e.id), []);
loadAllDependencies(extensions: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]> {
return this.getDependenciesReccursively(extensions.map(e => e.id), [], token);
}
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension> {
if (extension.properties.engine && isEngineValid(extension.properties.engine)) {
return TPromise.wrap(extension);
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]> {
let query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished));
if (extension.identifier.uuid) {
query = query.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
} else {
query = query.withFilter(FilterType.ExtensionName, extension.identifier.id);
}
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => {
if (galleryExtensions.length) {
if (compatible) {
return Promise.all(galleryExtensions[0].versions.map(v => this.getEngine(v).then(engine => isEngineValid(engine) ? v : null)))
.then(versions => versions
.filter(v => !!v)
.map(v => ({ version: v.version, date: v.lastUpdated })));
} else {
return galleryExtensions[0].versions.map(v => ({ version: v.version, date: v.lastUpdated }));
}
}
return [];
});
}
loadCompatibleVersion(extension: IGalleryExtension, fromVersion: string = extension.version): Promise<IGalleryExtension> {
if (extension.version === fromVersion && extension.properties.engine && isEngineValid(extension.properties.engine)) {
return Promise.resolve(extension);
}
const query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
@@ -676,7 +735,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.withAssetTypes(AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
return this.queryGallery(query)
return this.queryGallery(query, CancellationToken.None)
.then(({ galleryExtensions }) => {
const [rawExtension] = galleryExtensions;
@@ -684,25 +743,43 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return null;
}
return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions)
const versions: IRawGalleryExtensionVersion[] = this.getVersionsFrom(rawExtension.versions, fromVersion);
if (!versions.length) {
return null;
}
return this.getLastValidExtensionVersion(rawExtension, versions)
.then(rawVersion => {
if (rawVersion) {
extension.properties.dependencies = getExtensions(rawVersion, PropertyType.Dependency);
extension.properties.engine = getEngine(rawVersion);
// {{SQL CARBON EDIT}}
extension.assets.download = getVersionAsset(rawVersion, AssetType.VSIX);
extension.assets.downloadPage = getVersionAsset(rawVersion, AssetType.DownloadPage);
extension.version = rawVersion.version;
return extension;
return toExtension(rawExtension, rawVersion, 0, query);
}
return null;
});
});
}
private loadDependencies(extensionNames: string[]): TPromise<IGalleryExtension[]> {
private getVersionsFrom(versions: IRawGalleryExtensionVersion[], version: string): IRawGalleryExtensionVersion[] {
if (versions[0].version === version) {
return versions;
}
const result: IRawGalleryExtensionVersion[] = [];
let currentVersion: IRawGalleryExtensionVersion = null;
for (const v of versions) {
if (!currentVersion) {
if (v.version === version) {
currentVersion = v;
}
}
if (currentVersion) {
result.push(v);
}
}
return result;
}
private loadDependencies(extensionNames: string[], token: CancellationToken): Promise<IGalleryExtension[]> {
if (!extensionNames || extensionNames.length === 0) {
return TPromise.as([]);
return Promise.resolve([]);
}
let query = new Query()
@@ -713,14 +790,14 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionName, ...extensionNames);
return this.queryGallery(query).then(result => {
const dependencies = [];
const ids = [];
return this.queryGallery(query, token).then(result => {
const dependencies: IGalleryExtension[] = [];
const ids: string[] = [];
for (let index = 0; index < result.galleryExtensions.length; index++) {
const rawExtension = result.galleryExtensions[index];
if (ids.indexOf(rawExtension.extensionId) === -1) {
dependencies.push(toExtension(rawExtension, this.extensionsGalleryUrl, index, query, 'dependencies'));
dependencies.push(toExtension(rawExtension, rawExtension.versions[0], index, query, 'dependencies'));
ids.push(rawExtension.extensionId);
}
}
@@ -728,16 +805,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private getDependenciesReccursively(toGet: string[], result: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
private getDependenciesReccursively(toGet: string[], result: IGalleryExtension[], token: CancellationToken): Promise<IGalleryExtension[]> {
if (!toGet || !toGet.length) {
return TPromise.wrap(result);
return Promise.resolve(result);
}
toGet = result.length ? toGet.filter(e => !ExtensionGalleryService.hasExtensionByName(result, e)) : toGet;
if (!toGet.length) {
return TPromise.wrap(result);
return Promise.resolve(result);
}
return this.loadDependencies(toGet)
return this.loadDependencies(toGet, token)
.then(loadedDependencies => {
const dependenciesSet = new Set<string>();
for (const dep of loadedDependencies) {
@@ -748,11 +825,11 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
result = distinct(result.concat(loadedDependencies), d => d.identifier.uuid);
const dependencies: string[] = [];
dependenciesSet.forEach(d => !ExtensionGalleryService.hasExtensionByName(result, d) && dependencies.push(d));
return this.getDependenciesReccursively(dependencies, result);
return this.getDependenciesReccursively(dependencies, result, token);
});
}
private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}): TPromise<IRequestContext> {
private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {
return this.commonHeadersPromise.then(commonHeaders => {
const baseOptions = { type: 'GET' };
const headers = assign({}, commonHeaders, options.headers || {});
@@ -762,18 +839,18 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const fallbackUrl = asset.fallbackUri;
const firstOptions = assign({}, options, { url });
return this.requestService.request(firstOptions)
return this.requestService.request(firstOptions, token)
.then(context => {
if (context.res.statusCode === 200) {
return TPromise.as(context);
return Promise.resolve(context);
}
return asText(context)
.then(message => TPromise.wrapError<IRequestContext>(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)));
.then(message => Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)));
})
.then(null, err => {
if (isPromiseCanceledError(err)) {
return TPromise.wrapError<IRequestContext>(err);
return Promise.reject(err);
}
const message = getErrorMessage(err);
@@ -794,9 +871,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
this.telemetryService.publicLog('galleryService:cdnFallback', { url, message });
const fallbackOptions = assign({}, options, { url: fallbackUrl });
return this.requestService.request(fallbackOptions).then(null, err => {
return this.requestService.request(fallbackOptions, token).then(null, err => {
if (isPromiseCanceledError(err)) {
return TPromise.wrapError<IRequestContext>(err);
return Promise.reject(err);
}
const message = getErrorMessage(err);
@@ -808,13 +885,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
*/
this.telemetryService.publicLog('galleryService:requestError', { url: fallbackUrl, cdn: false, message });
return TPromise.wrapError<IRequestContext>(err);
return Promise.reject(err);
});
});
});
}
private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
const version = this.getLastValidExtensionVersionFromProperties(extension, versions);
if (version) {
return version;
@@ -822,39 +899,47 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return this.getLastValidExtensionVersionReccursively(extension, versions);
}
private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
for (const version of versions) {
const engine = getEngine(version);
if (!engine) {
return null;
}
if (isEngineValid(engine)) {
return TPromise.wrap(version);
return Promise.resolve(version);
}
}
return null;
}
private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
if (!versions.length) {
return null;
private getEngine(version: IRawGalleryExtensionVersion): Promise<string> {
const engine = getEngine(version);
if (engine) {
return Promise.resolve(engine);
}
const version = versions[0];
const asset = getVersionAsset(version, AssetType.Manifest);
const headers = { 'Accept-Encoding': 'gzip' };
return this.getAsset(asset, { headers })
.then(context => asJson<IExtensionManifest>(context))
.then(manifest => {
const engine = manifest.engines.vscode;
.then(manifest => manifest.engines.vscode);
}
private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
if (!versions.length) {
return null;
}
const version = versions[0];
return this.getEngine(version)
.then(engine => {
if (!isEngineValid(engine)) {
return this.getLastValidExtensionVersionReccursively(extension, versions.slice(1));
}
version.properties = version.properties || [];
version.properties.push({ key: PropertyType.Engine, value: manifest.engines.vscode });
version.properties.push({ key: PropertyType.Engine, value: engine });
return version;
});
}
@@ -868,18 +953,18 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return false;
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
getExtensionsReport(): Promise<IReportedExtension[]> {
if (!this.isEnabled()) {
return TPromise.wrapError(new Error('No extension gallery service configured.'));
return Promise.reject(new Error('No extension gallery service configured.'));
}
if (!this.extensionsControlUrl) {
return TPromise.as([]);
return Promise.resolve([]);
}
return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }).then(context => {
return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None).then(context => {
if (context.res.statusCode !== 200) {
return TPromise.wrapError(new Error('Could not get extensions report.'));
return Promise.reject(new Error('Could not get extensions report.'));
}
return asJson<IRawExtensionsReport>(context).then(result => {
@@ -891,13 +976,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
map.set(id, ext);
}
return TPromise.as(values(map));
return Promise.resolve(values(map));
});
});
}
}
export function resolveMarketplaceHeaders(environmentService: IEnvironmentService): TPromise<{ [key: string]: string; }> {
export function resolveMarketplaceHeaders(environmentService: IEnvironmentService): Promise<{ [key: string]: string; }> {
const marketplaceMachineIdFile = path.join(environmentService.userDataPath, 'machineid');
return readFile(marketplaceMachineIdFile, 'utf8').then(contents => {
@@ -905,9 +990,9 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic
return contents;
}
return TPromise.wrap(null); // invalid marketplace UUID
return Promise.resolve(null); // invalid marketplace UUID
}, error => {
return TPromise.wrap(null); // error reading ID file
return Promise.resolve(null); // error reading ID file
}).then(uuid => {
if (!uuid) {
uuid = generateUuid();
@@ -925,4 +1010,4 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic
'X-Market-User-Id': uuid
};
});
}
}

View File

@@ -2,11 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { TPromise } from 'vs/base/common/winjs.base';
import { ILogService } from 'vs/platform/log/common/log';
import { fork, ChildProcess } from 'child_process';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -14,97 +12,106 @@ import { posix } from 'path';
import { Limiter } from 'vs/base/common/async';
import { fromNodeEventEmitter, anyEvent, mapEvent, debounceEvent } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { rimraf } from 'vs/base/node/pfs';
export class ExtensionsLifecycle extends Disposable {
private processesLimiter: Limiter<void> = new Limiter(5); // Run max 5 processes in parallel
constructor(
@ILogService private logService: ILogService
private environmentService: IEnvironmentService,
private logService: ILogService
) {
super();
}
uninstall(extension: ILocalExtension): TPromise<void> {
const uninstallScript = this.parseUninstallScript(extension);
if (uninstallScript) {
this.logService.info(extension.identifier.id, 'Running Uninstall hook');
return this.processesLimiter.queue(() =>
this.runUninstallHook(uninstallScript.uninstallHook, uninstallScript.args, extension)
.then(() => this.logService.info(extension.identifier.id, 'Finished running uninstall hook'), err => this.logService.error(extension.identifier.id, `Failed to run uninstall hook: ${err}`)));
async postUninstall(extension: ILocalExtension): Promise<void> {
const script = this.parseScript(extension, 'uninstall');
if (script) {
this.logService.info(extension.identifier.id, `Running post uninstall script`);
await this.processesLimiter.queue(() =>
this.runLifecycleHook(script.script, 'uninstall', script.args, true, extension)
.then(() => this.logService.info(extension.identifier.id, `Finished running post uninstall script`), err => this.logService.error(extension.identifier.id, `Failed to run post uninstall script: ${err}`)));
}
return TPromise.as(null);
return rimraf(this.getExtensionStoragePath(extension)).then(null, e => this.logService.error('Error while removing extension storage path', e));
}
private parseUninstallScript(extension: ILocalExtension): { uninstallHook: string, args: string[] } {
if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts']['vscode:uninstall'] === 'string') {
const uninstallScript = (<string>extension.manifest['scripts']['vscode:uninstall']).split(' ');
if (uninstallScript.length < 2 || uninstallScript[0] !== 'node' || !uninstallScript[1]) {
this.logService.warn(extension.identifier.id, 'Uninstall script should be a node script');
private parseScript(extension: ILocalExtension, type: string): { script: string, args: string[] } | null {
const scriptKey = `vscode:${type}`;
if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts'][scriptKey] === 'string') {
const script = (<string>extension.manifest['scripts'][scriptKey]).split(' ');
if (script.length < 2 || script[0] !== 'node' || !script[1]) {
this.logService.warn(extension.identifier.id, `${scriptKey} should be a node script`);
return null;
}
return { uninstallHook: posix.join(extension.location.fsPath, uninstallScript[1]), args: uninstallScript.slice(2) || [] };
return { script: posix.join(extension.location.fsPath, script[1]), args: script.slice(2) || [] };
}
return null;
}
private runUninstallHook(lifecycleHook: string, args: string[], extension: ILocalExtension): TPromise<void> {
return new TPromise((c, e) => {
private runLifecycleHook(lifecycleHook: string, lifecycleType: string, args: string[], timeout: boolean, extension: ILocalExtension): Thenable<void> {
return new Promise<void>((c, e) => {
const extensionLifecycleProcess = this.start(lifecycleHook, args, extension);
const extensionLifecycleProcess = this.start(lifecycleHook, lifecycleType, args, extension);
let timeoutHandler;
const onexit = (error?: string) => {
clearTimeout(timeoutHandler);
timeoutHandler = null;
if (timeoutHandler) {
clearTimeout(timeoutHandler);
timeoutHandler = null;
}
if (error) {
e(error);
} else {
c(null);
c(void 0);
}
};
// on error
extensionLifecycleProcess.on('error', (err) => {
if (timeoutHandler) {
onexit(toErrorMessage(err) || 'Unknown');
}
onexit(toErrorMessage(err) || 'Unknown');
});
// on exit
extensionLifecycleProcess.on('exit', (code: number, signal: string) => {
if (timeoutHandler) {
onexit(code ? `Process exited with code ${code}` : void 0);
}
onexit(code ? `post-${lifecycleType} process exited with code ${code}` : void 0);
});
// timeout: kill process after waiting for 5s
timeoutHandler = setTimeout(() => {
timeoutHandler = null;
extensionLifecycleProcess.kill();
e('timed out');
}, 5000);
if (timeout) {
// timeout: kill process after waiting for 5s
timeoutHandler = setTimeout(() => {
timeoutHandler = null;
extensionLifecycleProcess.kill();
e('timed out');
}, 5000);
}
});
}
private start(uninstallHook: string, args: string[], extension: ILocalExtension): ChildProcess {
private start(uninstallHook: string, lifecycleType: string, args: string[], extension: ILocalExtension): ChildProcess {
const opts = {
silent: true,
execArgv: <string[]>undefined
execArgv: undefined
};
const extensionUninstallProcess = fork(uninstallHook, ['--type=extensionUninstall', ...args], opts);
const extensionUninstallProcess = fork(uninstallHook, [`--type=extension-post-${lifecycleType}`, ...args], opts);
// Catch all output coming from the process
type Output = { data: string, format: string[] };
extensionUninstallProcess.stdout.setEncoding('utf8');
extensionUninstallProcess.stderr.setEncoding('utf8');
const onStdout = fromNodeEventEmitter<string>(extensionUninstallProcess.stdout, 'data');
const onStderr = fromNodeEventEmitter<string>(extensionUninstallProcess.stderr, 'data');
// Log output
onStdout(data => this.logService.info(extension.identifier.id, `post-${lifecycleType}`, data));
onStderr(data => this.logService.error(extension.identifier.id, `post-${lifecycleType}`, data));
const onOutput = anyEvent(
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
);
// Debounce all output, so we can render it in the Chrome console as a group
const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
return r
@@ -112,7 +119,7 @@ export class ExtensionsLifecycle extends Disposable {
: { data: o.data, format: o.format };
}, 100);
// Print out extension host output
// Print out output
onDebouncedOutput(data => {
console.group(extension.identifier.id);
console.log(data.data, ...data.format);
@@ -121,4 +128,8 @@ export class ExtensionsLifecycle extends Disposable {
return extensionUninstallProcess;
}
private getExtensionStoragePath(extension: ILocalExtension): string {
return posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLocaleLowerCase());
}
}

View File

@@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from '../common/extensionManagement';
import { Event, buffer, mapEvent } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
export class ExtensionManagementChannel implements IServerChannel {
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: any) => IURITransformer) {
this.onInstallExtension = buffer(service.onInstallExtension, true);
this.onDidInstallExtension = buffer(service.onDidInstallExtension, true);
this.onUninstallExtension = buffer(service.onUninstallExtension, true);
this.onDidUninstallExtension = buffer(service.onDidUninstallExtension, true);
}
listen(context, event: string): Event<any> {
const uriTransformer = this.getUriTransformer(context);
switch (event) {
case 'onInstallExtension': return this.onInstallExtension;
case 'onDidInstallExtension': return mapEvent(this.onDidInstallExtension, i => ({ ...i, local: this._transformOutgoing(i.local, uriTransformer) }));
case 'onUninstallExtension': return this.onUninstallExtension;
case 'onDidUninstallExtension': return this.onDidUninstallExtension;
}
throw new Error('Invalid listen');
}
call(context, command: string, args?: any): Thenable<any> {
const uriTransformer = this.getUriTransformer(context);
switch (command) {
case 'zip': return this.service.zip(this._transformIncoming(args[0], uriTransformer)).then(uri => uriTransformer.transformOutgoing(uri));
case 'unzip': return this.service.unzip(URI.revive(uriTransformer.transformIncoming(args[0])), args[1]);
case 'install': return this.service.install(URI.revive(uriTransformer.transformIncoming(args[0])));
case 'installFromGallery': return this.service.installFromGallery(args[0]);
case 'uninstall': return this.service.uninstall(this._transformIncoming(args[0], uriTransformer), args[1]);
case 'reinstallFromGallery': return this.service.reinstallFromGallery(this._transformIncoming(args[0], uriTransformer));
case 'getInstalled': return this.service.getInstalled(args[0]).then(extensions => extensions.map(e => this._transformOutgoing(e, uriTransformer)));
case 'updateMetadata': return this.service.updateMetadata(this._transformIncoming(args[0], uriTransformer), args[1]).then(e => this._transformOutgoing(e, uriTransformer));
case 'getExtensionsReport': return this.service.getExtensionsReport();
}
throw new Error('Invalid call');
}
private _transformIncoming(extension: ILocalExtension, uriTransformer: IURITransformer): ILocalExtension {
return extension ? { ...extension, ...{ location: URI.revive(uriTransformer.transformIncoming(extension.location)) } } : extension;
}
private _transformOutgoing(extension: ILocalExtension, uriTransformer: IURITransformer): ILocalExtension;
private _transformOutgoing(extension: ILocalExtension | undefined, uriTransformer: IURITransformer): ILocalExtension | undefined;
private _transformOutgoing(extension: ILocalExtension | undefined, uriTransformer: IURITransformer): ILocalExtension | undefined {
return extension ? { ...extension, ...{ location: uriTransformer.transformOutgoing(extension.location) } } : extension;
}
}
export class ExtensionManagementChannelClient implements IExtensionManagementService {
_serviceBrand: any;
constructor(private channel: IChannel) { }
get onInstallExtension(): Event<InstallExtensionEvent> { return this.channel.listen('onInstallExtension'); }
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return mapEvent(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension'), i => ({ ...i, local: this._transformIncoming(i.local) })); }
get onUninstallExtension(): Event<IExtensionIdentifier> { return this.channel.listen('onUninstallExtension'); }
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this.channel.listen('onDidUninstallExtension'); }
zip(extension: ILocalExtension): Promise<URI> {
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result)));
}
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
return Promise.resolve(this.channel.call('unzip', [zipLocation, type]));
}
install(vsix: URI): Promise<IExtensionIdentifier> {
return Promise.resolve(this.channel.call('install', [vsix]));
}
installFromGallery(extension: IGalleryExtension): Promise<void> {
return Promise.resolve(this.channel.call('installFromGallery', [extension]));
}
uninstall(extension: ILocalExtension, force = false): Promise<void> {
return Promise.resolve(this.channel.call('uninstall', [extension!, force]));
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
return Promise.resolve(this.channel.call('reinstallFromGallery', [extension]));
}
getInstalled(type: LocalExtensionType | null = null): Promise<ILocalExtension[]> {
return Promise.resolve(this.channel.call<ILocalExtension[]>('getInstalled', [type]))
.then(extensions => extensions.map(extension => this._transformIncoming(extension)));
}
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
return Promise.resolve(this.channel.call<ILocalExtension>('updateMetadata', [local, metadata]))
.then(extension => this._transformIncoming(extension));
}
getExtensionsReport(): Promise<IReportedExtension[]> {
return Promise.resolve(this.channel.call('getExtensionsReport'));
}
private _transformIncoming(extension: ILocalExtension): ILocalExtension;
private _transformIncoming(extension: ILocalExtension | undefined): ILocalExtension | undefined;
private _transformIncoming(extension: ILocalExtension | undefined): ILocalExtension | undefined {
return extension ? { ...extension, ...{ location: URI.revive(extension.location) } } : extension;
}
}

View File

@@ -3,12 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as semver from 'semver';
import { adoptToGalleryExtensionId, LOCAL_EXTENSION_ID_REGEX } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { buffer } from 'vs/platform/node/zip';
import { localize } from 'vs/nls';
export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): { id: string, version: string } {
export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): { id: string, version: string | null } {
const matches = LOCAL_EXTENSION_ID_REGEX.exec(localExtensionId);
if (matches && matches[1] && matches[2]) {
const version = semver.valid(matches[2]);
@@ -20,4 +21,15 @@ export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): {
id: adoptToGalleryExtensionId(localExtensionId),
version: null
};
}
export function getManifest(vsix: string): Promise<IExtensionManifest> {
return buffer(vsix, 'extension/package.json')
.then(buffer => {
try {
return JSON.parse(buffer.toString('utf8'));
} catch (err) {
throw new Error(localize('invalidManifest', "VSIX invalid: package.json is not a JSON file."));
}
});
}

View File

@@ -2,7 +2,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable } from 'vs/base/common/lifecycle';
import { join } from 'path';
@@ -37,6 +36,6 @@ export class ExtensionsManifestCache extends Disposable {
}
invalidate(): void {
pfs.del(this.extensionsManifestCache).done(() => { }, () => { });
pfs.del(this.extensionsManifestCache).then(() => { }, () => { });
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
IExtensionManagementService, ILocalExtension, IGalleryExtension, LocalExtensionType, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata,
IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { flatten } from 'vs/base/common/arrays';
import { isUIExtension } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IRemoteAuthorityResolverService, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
export class MulitExtensionManagementService extends Disposable implements IExtensionManagementService {
_serviceBrand: any;
readonly onInstallExtension: Event<InstallExtensionEvent>;
readonly onDidInstallExtension: Event<DidInstallExtensionEvent>;
readonly onUninstallExtension: Event<IExtensionIdentifier>;
readonly onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
private readonly servers: IExtensionManagementServer[];
constructor(
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService,
@IRemoteAuthorityResolverService private remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer];
this.onInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<InstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onInstallExtension); return emitter; }, new EventMultiplexer<InstallExtensionEvent>())).event;
this.onDidInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<DidInstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidInstallExtension); return emitter; }, new EventMultiplexer<DidInstallExtensionEvent>())).event;
this.onUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<IExtensionIdentifier>, server) => { emitter.add(server.extensionManagementService.onUninstallExtension); return emitter; }, new EventMultiplexer<IExtensionIdentifier>())).event;
this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<DidUninstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidUninstallExtension); return emitter; }, new EventMultiplexer<DidUninstallExtensionEvent>())).event;
}
getInstalled(type?: LocalExtensionType): Promise<ILocalExtension[]> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type)))
.then(result => flatten(result));
}
uninstall(extension: ILocalExtension, force?: boolean): Promise<void> {
return this.getServer(extension).extensionManagementService.uninstall(extension, force);
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
return this.getServer(extension).extensionManagementService.reinstallFromGallery(extension);
}
updateMetadata(extension: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
return this.getServer(extension).extensionManagementService.updateMetadata(extension, metadata);
}
zip(extension: ILocalExtension): Promise<URI> {
throw new Error('Not Supported');
}
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation, type))).then(() => null);
}
install(vsix: URI): Promise<IExtensionIdentifier> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
}
return Promise.all([getManifest(vsix.fsPath), this.hasToSyncExtensions()])
.then(([manifest, syncExtensions]) => {
const servers = isUIExtension(manifest, this.configurationService) ? [this.extensionManagementServerService.localExtensionManagementServer] : syncExtensions ? this.servers : [this.extensionManagementServerService.remoteExtensionManagementServer];
return Promise.all(servers.map(server => server.extensionManagementService.install(vsix)))
.then(() => null);
});
}
installFromGallery(gallery: IGalleryExtension): Promise<void> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
return Promise.all([this.extensionGalleryService.getManifest(gallery, CancellationToken.None), this.hasToSyncExtensions()])
.then(([manifest, syncExtensions]) => {
const servers = isUIExtension(manifest, this.configurationService) ? [this.extensionManagementServerService.localExtensionManagementServer] : syncExtensions ? this.servers : [this.extensionManagementServerService.remoteExtensionManagementServer];
return Promise.all(servers.map(server => server.extensionManagementService.installFromGallery(gallery)))
.then(() => null);
});
}
getExtensionsReport(): Promise<IReportedExtension[]> {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsReport();
}
private getServer(extension: ILocalExtension): IExtensionManagementServer {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
private _remoteAuthorityResolverPromise: Thenable<ResolvedAuthority>;
private hasToSyncExtensions(): Thenable<boolean> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return Promise.resolve(false);
}
if (!this._remoteAuthorityResolverPromise) {
this._remoteAuthorityResolverPromise = this.remoteAuthorityResolverService.resolveAuthority(this.extensionManagementServerService.remoteExtensionManagementServer.authority);
}
return this._remoteAuthorityResolverPromise.then(({ syncExtensions }) => !!syncExtensions);
}
}