Files
vscode/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts
2017-06-26 15:24:41 +02:00

849 lines
29 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import { readFile } from 'vs/base/node/pfs';
import * as semver from 'semver';
import * as path from 'path';
import Event, { Emitter, chain } from 'vs/base/common/event';
import { index } from 'vs/base/common/arrays';
import { assign } from 'vs/base/common/objects';
import { ThrottledDelayer } from 'vs/base/common/async';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionTipsService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IChoiceService, IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import URI from 'vs/base/common/uri';
import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IURLService } from 'vs/platform/url/common/url';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import product from 'vs/platform/node/product';
interface IExtensionStateProvider {
(extension: Extension): ExtensionState;
}
class Extension implements IExtension {
public disabledGlobally = false;
public disabledForWorkspace = false;
constructor(
private galleryService: IExtensionGalleryService,
private stateProvider: IExtensionStateProvider,
public local: ILocalExtension,
public gallery: IGalleryExtension = null
) { }
get type(): LocalExtensionType {
return this.local ? this.local.type : null;
}
get name(): string {
return this.gallery ? this.gallery.name : this.local.manifest.name;
}
get displayName(): string {
if (this.gallery) {
return this.gallery.displayName || this.gallery.name;
}
return this.local.manifest.displayName || this.local.manifest.name;
}
get id(): string {
if (this.gallery) {
return this.gallery.id;
}
return getGalleryExtensionIdFromLocal(this.local);
}
get publisher(): string {
return this.gallery ? this.gallery.publisher : this.local.manifest.publisher;
}
get publisherDisplayName(): string {
if (this.gallery) {
return this.gallery.publisherDisplayName || this.gallery.publisher;
}
if (this.local.metadata && this.local.metadata.publisherDisplayName) {
return this.local.metadata.publisherDisplayName;
}
return this.local.manifest.publisher;
}
get version(): string {
return this.local ? this.local.manifest.version : this.gallery.version;
}
get latestVersion(): string {
return this.gallery ? this.gallery.version : this.local.manifest.version;
}
get description(): string {
return this.gallery ? this.gallery.description : this.local.manifest.description;
}
get url(): string {
if (!product.extensionsGallery) {
return null;
}
return `${product.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`;
}
get iconUrl(): string {
return this.galleryIconUrl || this.localIconUrl || this.defaultIconUrl;
}
get iconUrlFallback(): string {
return this.galleryIconUrlFallback || this.localIconUrl || this.defaultIconUrl;
}
private get localIconUrl(): string {
return this.local && this.local.manifest.icon
&& URI.file(path.join(this.local.path, this.local.manifest.icon)).toString();
}
private get galleryIconUrl(): string {
return this.gallery && this.gallery.assets.icon.uri;
}
private get galleryIconUrlFallback(): string {
return this.gallery && this.gallery.assets.icon.fallbackUri;
}
private get defaultIconUrl(): string {
return require.toUrl('../browser/media/defaultIcon.png');
}
get licenseUrl(): string {
return this.gallery && this.gallery.assets.license && this.gallery.assets.license.uri;
}
get state(): ExtensionState {
return this.stateProvider(this);
}
get installCount(): number {
return this.gallery ? this.gallery.installCount : null;
}
get rating(): number {
return this.gallery ? this.gallery.rating : null;
}
get ratingCount(): number {
return this.gallery ? this.gallery.ratingCount : null;
}
get outdated(): boolean {
return !!this.gallery && this.type === LocalExtensionType.User && semver.gt(this.latestVersion, this.version);
}
get telemetryData(): any {
const { local, gallery } = this;
if (gallery) {
return getGalleryExtensionTelemetryData(gallery);
} else {
return getLocalExtensionTelemetryData(local);
}
}
getManifest(): TPromise<IExtensionManifest> {
if (this.gallery) {
return this.galleryService.getManifest(this.gallery);
}
return TPromise.as(this.local.manifest);
}
getReadme(): TPromise<string> {
if (this.gallery) {
return this.galleryService.getReadme(this.gallery);
}
if (this.local && this.local.readmeUrl) {
const uri = URI.parse(this.local.readmeUrl);
return readFile(uri.fsPath, 'utf8');
}
return TPromise.wrapError<string>(new Error('not available'));
}
getChangelog(): TPromise<string> {
if (this.gallery && this.gallery.assets.changelog) {
return this.galleryService.getChangelog(this.gallery);
}
const changelogUrl = this.local && this.local.changelogUrl;
if (!changelogUrl) {
return TPromise.wrapError<string>(new Error('not available'));
}
const uri = URI.parse(changelogUrl);
if (uri.scheme === 'file') {
return readFile(uri.fsPath, 'utf8');
}
return TPromise.wrapError<string>(new Error('not available'));
}
get dependencies(): string[] {
const { local, gallery } = this;
if (local && local.manifest.extensionDependencies) {
return local.manifest.extensionDependencies;
}
if (gallery) {
return gallery.properties.dependencies;
}
return [];
}
}
class ExtensionDependencies implements IExtensionDependencies {
private _hasDependencies: boolean = null;
constructor(private _extension: IExtension, private _identifier: string, private _map: Map<string, IExtension>, private _dependent: IExtensionDependencies = null) { }
get hasDependencies(): boolean {
if (this._hasDependencies === null) {
this._hasDependencies = this.computeHasDependencies();
}
return this._hasDependencies;
}
get extension(): IExtension {
return this._extension;
}
get identifier(): string {
return this._identifier;
}
get dependent(): IExtensionDependencies {
return this._dependent;
}
get dependencies(): IExtensionDependencies[] {
if (!this.hasDependencies) {
return [];
}
return this._extension.dependencies.map(d => new ExtensionDependencies(this._map.get(d), d, this._map, this));
}
private computeHasDependencies(): boolean {
if (this._extension && this._extension.dependencies.length > 0) {
let dependent = this._dependent;
while (dependent !== null) {
if (dependent.identifier === this.identifier) {
return false;
}
dependent = dependent.dependent;
}
return true;
}
return false;
}
}
enum Operation {
Installing,
Updating,
Uninstalling
}
interface IActiveExtension {
operation: Operation;
extension: Extension;
start: Date;
}
function toTelemetryEventName(operation: Operation) {
switch (operation) {
case Operation.Installing: return 'extensionGallery:install';
case Operation.Updating: return 'extensionGallery:update';
case Operation.Uninstalling: return 'extensionGallery:uninstall';
}
return '';
}
export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
private static SyncPeriod = 1000 * 60 * 60 * 12; // 12 hours
_serviceBrand: any;
private stateProvider: IExtensionStateProvider;
private installing: IActiveExtension[] = [];
private uninstalling: IActiveExtension[] = [];
private installed: Extension[] = [];
private syncDelayer: ThrottledDelayer<void>;
private autoUpdateDelayer: ThrottledDelayer<void>;
private disposables: IDisposable[] = [];
private _onChange: Emitter<void> = new Emitter<void>();
get onChange(): Event<void> { return this._onChange.event; }
private _isAutoUpdateEnabled: boolean;
private _extensionAllowedBadgeProviders: string[];
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IExtensionManagementService private extensionService: IExtensionManagementService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService,
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService,
@ITelemetryService private telemetryService: ITelemetryService,
@IMessageService private messageService: IMessageService,
@IChoiceService private choiceService: IChoiceService,
@IURLService urlService: IURLService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
@IExtensionTipsService private tipsService: IExtensionTipsService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
) {
this.stateProvider = ext => this.getExtensionState(ext);
extensionService.onInstallExtension(this.onInstallExtension, this, this.disposables);
extensionService.onDidInstallExtension(this.onDidInstallExtension, this, this.disposables);
extensionService.onUninstallExtension(this.onUninstallExtension, this, this.disposables);
extensionService.onDidUninstallExtension(this.onDidUninstallExtension, this, this.disposables);
extensionEnablementService.onEnablementChanged(this.onEnablementChanged, this, this.disposables);
this.syncDelayer = new ThrottledDelayer<void>(ExtensionsWorkbenchService.SyncPeriod);
this.autoUpdateDelayer = new ThrottledDelayer<void>(1000);
chain(urlService.onOpenURL)
.filter(uri => /^extension/.test(uri.path))
.on(this.onOpenExtensionUrl, this, this.disposables);
this._isAutoUpdateEnabled = this.configurationService.getConfiguration<IExtensionsConfiguration>(ConfigurationKey).autoUpdate;
this.configurationService.onDidUpdateConfiguration(() => {
const isAutoUpdateEnabled = this.configurationService.getConfiguration<IExtensionsConfiguration>(ConfigurationKey).autoUpdate;
if (this._isAutoUpdateEnabled !== isAutoUpdateEnabled) {
this._isAutoUpdateEnabled = isAutoUpdateEnabled;
if (this._isAutoUpdateEnabled) {
this.checkForUpdates();
}
}
}, this, this.disposables);
this.queryLocal().done(() => this.eventuallySyncWithGallery(true));
}
get local(): IExtension[] {
const installing = this.installing
.filter(e => !this.installed.some(installed => installed.id === e.extension.id))
.map(e => e.extension);
return [...this.installed, ...installing];
}
queryLocal(): TPromise<IExtension[]> {
return this.extensionService.getInstalled().then(result => {
const installedById = index(this.installed, e => e.local.id);
const globallyDisabledExtensions = this.extensionEnablementService.getGloballyDisabledExtensions();
const workspaceDisabledExtensions = this.extensionEnablementService.getWorkspaceDisabledExtensions();
this.installed = result.map(local => {
const extension = installedById[local.id] || new Extension(this.galleryService, this.stateProvider, local);
extension.local = local;
extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.id) !== -1;
extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.id) !== -1;
return extension;
});
this._onChange.fire();
return this.local;
});
}
queryGallery(options: IQueryOptions = {}): TPromise<IPager<IExtension>> {
return this.galleryService.query(options)
.then(result => mapPager(result, gallery => this.fromGallery(gallery)))
.then(null, err => {
if (/No extension gallery service configured/.test(err.message)) {
return TPromise.as(singlePagePager([]));
}
return TPromise.wrapError<IPager<IExtension>>(err);
});
}
loadDependencies(extension: IExtension): TPromise<IExtensionDependencies> {
if (!extension.dependencies.length) {
return TPromise.wrap<IExtensionDependencies>(null);
}
return this.galleryService.getAllDependencies((<Extension>extension).gallery)
.then(galleryExtensions => galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension)))
.then(extensions => [...this.local, ...extensions])
.then(extensions => {
const map = new Map<string, IExtension>();
for (const extension of extensions) {
map.set(extension.id, extension);
}
return new ExtensionDependencies(extension, extension.id, map);
});
}
open(extension: IExtension, sideByside: boolean = false): TPromise<any> {
return this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), null, sideByside);
}
private fromGallery(gallery: IGalleryExtension): Extension {
const installed = this.installed.filter(installed => installed.id === gallery.id)[0];
if (installed) {
// Loading the compatible version only there is an engine property
// Otherwise falling back to old way so that we will not make many roundtrips
if (gallery.properties.engine) {
this.galleryService.loadCompatibleVersion(gallery).then(compatible => this.syncLocalWithGalleryExtension(installed, compatible));
} else {
this.syncLocalWithGalleryExtension(installed, gallery);
}
return installed;
}
return new Extension(this.galleryService, this.stateProvider, null, gallery);
}
private syncLocalWithGalleryExtension(local: Extension, gallery: IGalleryExtension) {
local.gallery = gallery;
this._onChange.fire();
this.eventuallyAutoUpdateExtensions();
}
checkForUpdates(): TPromise<void> {
return this.syncDelayer.trigger(() => this.syncWithGallery(), 0);
}
get isAutoUpdateEnabled(): boolean {
return this._isAutoUpdateEnabled;
}
setAutoUpdate(autoUpdate: boolean): TPromise<void> {
if (this.isAutoUpdateEnabled === autoUpdate) {
return TPromise.as(null);
}
return this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: 'extensions.autoUpdate', value: autoUpdate });
}
private eventuallySyncWithGallery(immediate = false): void {
const loop = () => this.syncWithGallery().then(() => this.eventuallySyncWithGallery());
const delay = immediate ? 0 : ExtensionsWorkbenchService.SyncPeriod;
this.syncDelayer.trigger(loop, delay)
.done(null, err => null);
}
private syncWithGallery(): TPromise<void> {
const names = this.installed
.filter(e => e.type === LocalExtensionType.User)
.map(e => e.id);
if (names.length === 0) {
return TPromise.as(null);
}
return this.queryGallery({ names, pageSize: names.length }) as TPromise<any>;
}
private eventuallyAutoUpdateExtensions(): void {
this.autoUpdateDelayer.trigger(() => this.autoUpdateExtensions())
.done(null, err => null);
}
private autoUpdateExtensions(): TPromise<any> {
if (!this.isAutoUpdateEnabled) {
return TPromise.as(null);
}
const toUpdate = this.local.filter(e => e.outdated && (e.state !== ExtensionState.Installing));
return TPromise.join(toUpdate.map(e => this.install(e, false)));
}
canInstall(extension: IExtension): boolean {
if (!(extension instanceof Extension)) {
return false;
}
return !!(extension as Extension).gallery;
}
install(extension: string | IExtension, promptToInstallDependencies: boolean = true): TPromise<void> {
if (typeof extension === 'string') {
return this.extensionService.install(extension);
}
if (!(extension instanceof Extension)) {
return undefined;
}
const ext = extension as Extension;
const gallery = ext.gallery;
if (!gallery) {
return TPromise.wrapError<void>(new Error('Missing gallery'));
}
return this.extensionService.installFromGallery(gallery, promptToInstallDependencies);
}
setEnablement(extension: IExtension, enable: boolean, workspace: boolean = false): TPromise<void> {
if (extension.type === LocalExtensionType.System) {
return TPromise.wrap<void>(void 0);
}
return this.promptAndSetEnablement(extension, enable, workspace).then(reload => {
this.telemetryService.publicLog(enable ? 'extension:enable' : 'extension:disable', extension.telemetryData);
});
}
uninstall(extension: IExtension): TPromise<void> {
if (!(extension instanceof Extension)) {
return undefined;
}
const ext = extension as Extension;
const local = ext.local || this.installed.filter(e => e.id === extension.id)[0].local;
if (!local) {
return TPromise.wrapError<void>(new Error('Missing local'));
}
return this.extensionService.uninstall(local);
}
private promptAndSetEnablement(extension: IExtension, enable: boolean, workspace: boolean): TPromise<any> {
const allDependencies = this.getDependenciesRecursively(extension, this.local, enable, workspace, []);
if (allDependencies.length > 0) {
if (enable) {
return this.promptForDependenciesAndEnable(extension, allDependencies, workspace);
} else {
return this.promptForDependenciesAndDisable(extension, allDependencies, workspace);
}
}
return this.checkAndSetEnablement(extension, [], enable, workspace);
}
private promptForDependenciesAndEnable(extension: IExtension, dependencies: IExtension[], workspace: boolean): TPromise<any> {
const message = nls.localize('enableDependeciesConfirmation', "Enabling '{0}' also enable its dependencies. Would you like to continue?", extension.displayName);
const options = [
nls.localize('enable', "Yes"),
nls.localize('doNotEnable', "No")
];
return this.choiceService.choose(Severity.Info, message, options, 1, true)
.then<void>(value => {
if (value === 0) {
return this.checkAndSetEnablement(extension, dependencies, true, workspace);
}
return TPromise.as(null);
});
}
private promptForDependenciesAndDisable(extension: IExtension, dependencies: IExtension[], workspace: boolean): TPromise<void> {
const message = nls.localize('disableDependeciesConfirmation', "Would you like to disable '{0}' only or its dependencies also?", extension.displayName);
const options = [
nls.localize('disableOnly', "Only"),
nls.localize('disableAll', "All"),
nls.localize('cancel', "Cancel")
];
return this.choiceService.choose(Severity.Info, message, options, 2, true)
.then<void>(value => {
if (value === 0) {
return this.checkAndSetEnablement(extension, [], false, workspace);
}
if (value === 1) {
return this.checkAndSetEnablement(extension, dependencies, false, workspace);
}
return TPromise.as(null);
});
}
private checkAndSetEnablement(extension: IExtension, dependencies: IExtension[], enable: boolean, workspace: boolean): TPromise<any> {
if (!enable) {
let dependents = this.getDependentsAfterDisablement(extension, dependencies, this.local, workspace);
if (dependents.length) {
return TPromise.wrapError<void>(new Error(this.getDependentsErrorMessage(extension, dependents)));
}
}
return TPromise.join([extension, ...dependencies].map(e => this.doSetEnablement(e, enable, workspace)));
}
private getDependenciesRecursively(extension: IExtension, installed: IExtension[], enable: boolean, workspace: boolean, checked: IExtension[]): IExtension[] {
if (checked.indexOf(extension) !== -1) {
return [];
}
checked.push(extension);
if (!extension.dependencies || extension.dependencies.length === 0) {
return [];
}
const dependenciesToDisable = installed.filter(i => {
// Do not include extensions which are already disabled and request is to disable
if (!enable && (workspace ? i.disabledForWorkspace : i.disabledGlobally)) {
return false;
}
return i.type === LocalExtensionType.User && extension.dependencies.indexOf(i.id) !== -1;
});
const depsOfDeps = [];
for (const dep of dependenciesToDisable) {
depsOfDeps.push(...this.getDependenciesRecursively(dep, installed, enable, workspace, checked));
}
return [...dependenciesToDisable, ...depsOfDeps];
}
private getDependentsAfterDisablement(extension: IExtension, dependencies: IExtension[], installed: IExtension[], workspace: boolean): IExtension[] {
return installed.filter(i => {
if (i.dependencies.length === 0) {
return false;
}
if (i === extension) {
return false;
}
const disabled = workspace ? i.disabledForWorkspace : i.disabledGlobally;
if (disabled) {
return false;
}
if (dependencies.indexOf(i) !== -1) {
return false;
}
return i.dependencies.some(dep => {
if (extension.id === dep) {
return true;
}
return dependencies.some(d => d.id === dep);
});
});
}
private getDependentsErrorMessage(extension: IExtension, dependents: IExtension[]): string {
if (dependents.length === 1) {
return nls.localize('singleDependentError', "Cannot disable extension '{0}'. Extension '{1}' depends on this.", extension.displayName, dependents[0].displayName);
}
if (dependents.length === 2) {
return nls.localize('twoDependentsError', "Cannot disable extension '{0}'. Extensions '{1}' and '{2}' depend on this.",
extension.displayName, dependents[0].displayName, dependents[1].displayName);
}
return nls.localize('multipleDependentsError', "Cannot disable extension '{0}'. Extensions '{1}', '{2}' and others depend on this.",
extension.displayName, dependents[0].displayName, dependents[1].displayName);
}
private doSetEnablement(extension: IExtension, enable: boolean, workspace: boolean): TPromise<boolean> {
if (workspace) {
return this.extensionEnablementService.setEnablement(extension.id, enable, workspace);
}
const globalElablement = this.extensionEnablementService.setEnablement(extension.id, enable, false);
if (enable && this.workspaceContextService.hasWorkspace()) {
const workspaceEnablement = this.extensionEnablementService.setEnablement(extension.id, enable, true);
return TPromise.join([globalElablement, workspaceEnablement]).then(values => values[0] || values[1]);
}
return globalElablement;
}
get allowedBadgeProviders(): string[] {
if (!this._extensionAllowedBadgeProviders) {
this._extensionAllowedBadgeProviders = (product.extensionAllowedBadgeProviders || []).map(s => s.toLowerCase());
}
return this._extensionAllowedBadgeProviders;
}
private onInstallExtension(event: InstallExtensionEvent): void {
const { gallery } = event;
if (!gallery) {
return;
}
let extension = this.installed.filter(e => e.id === gallery.id)[0];
if (!extension) {
extension = new Extension(this.galleryService, this.stateProvider, null, gallery);
}
extension.gallery = gallery;
const start = new Date();
const operation = Operation.Installing;
this.installing.push({ operation, extension, start });
this._onChange.fire();
}
private onDidInstallExtension(event: DidInstallExtensionEvent): void {
const { local, zipPath, error, gallery } = event;
const installing = gallery ? this.installing.filter(e => e.extension.id === gallery.id)[0] : null;
const extension: Extension = installing ? installing.extension : zipPath ? new Extension(this.galleryService, this.stateProvider, null) : null;
if (extension) {
this.installing = installing ? this.installing.filter(e => e !== installing) : this.installing;
if (!error) {
extension.local = local;
const installed = this.installed.filter(e => e.id === extension.id)[0];
if (installed) {
if (installing) {
installing.operation = Operation.Updating;
}
installed.local = local;
} else {
this.installed.push(extension);
}
}
if (extension.gallery) {
// Report telemetry only for gallery extensions
this.reportTelemetry(installing, !error);
}
}
this._onChange.fire();
}
private onUninstallExtension(id: string): void {
const extension = this.installed.filter(e => e.local.id === id)[0];
const newLength = this.installed.filter(e => e.local.id !== id).length;
// TODO: Ask @Joao why is this?
if (newLength === this.installed.length) {
return;
}
const start = new Date();
const operation = Operation.Uninstalling;
const uninstalling = this.uninstalling.filter(e => e.extension.local.id === id)[0] || { id, operation, extension, start };
this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => e.extension.local.id !== id)];
this._onChange.fire();
}
private onDidUninstallExtension({ id, error }: DidUninstallExtensionEvent): void {
if (!error) {
this.installed = this.installed.filter(e => e.local.id !== id);
}
const uninstalling = this.uninstalling.filter(e => e.extension.local.id === id)[0];
this.uninstalling = this.uninstalling.filter(e => e.extension.local.id !== id);
if (!uninstalling) {
return;
}
if (!error) {
this.reportTelemetry(uninstalling, true);
}
this._onChange.fire();
}
private onEnablementChanged(extensionIdentifier: string) {
const [extension] = this.local.filter(e => e.id === extensionIdentifier);
if (extension) {
const globallyDisabledExtensions = this.extensionEnablementService.getGloballyDisabledExtensions();
const workspaceDisabledExtensions = this.extensionEnablementService.getWorkspaceDisabledExtensions();
extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.id) !== -1;
extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.id) !== -1;
this._onChange.fire();
}
}
private getExtensionState(extension: Extension): ExtensionState {
if (extension.gallery && this.installing.some(e => e.extension.gallery && e.extension.gallery.id === extension.gallery.id)) {
return ExtensionState.Installing;
}
if (this.uninstalling.some(e => e.extension.id === extension.id)) {
return ExtensionState.Uninstalling;
}
const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && e.gallery.id === extension.gallery.id))[0];
return local ? ExtensionState.Installed : ExtensionState.Uninstalled;
}
private reportTelemetry(active: IActiveExtension, success: boolean): void {
const data = active.extension.telemetryData;
const duration = new Date().getTime() - active.start.getTime();
const eventName = toTelemetryEventName(active.operation);
this.telemetryService.publicLog(eventName, assign(data, { success, duration }));
}
private onError(err: any): void {
if (isPromiseCanceledError(err)) {
return;
}
const message = err && err.message || '';
if (/getaddrinfo ENOTFOUND|getaddrinfo ENOENT|connect EACCES|connect ECONNREFUSED/.test(message)) {
return;
}
this.messageService.show(Severity.Error, err);
}
private onOpenExtensionUrl(uri: URI): void {
const match = /^extension\/([^/]+)$/.exec(uri.path);
if (!match) {
return;
}
const extensionId = match[1];
this.queryGallery({ names: [extensionId] })
.then(result => {
if (result.total < 1) {
return TPromise.as(null);
}
const extension = result.firstPage[0];
return this.open(extension).then(() => {
const message = nls.localize('installConfirmation', "Would you like to install the '{0}' extension?", extension.displayName, extension.publisher);
const options = [
nls.localize('install', "Install"),
nls.localize('cancel', "Cancel")
];
return this.choiceService.choose(Severity.Info, message, options, 2, false)
.then<void>(value => {
if (value === 0) {
const promises: TPromise<any>[] = [];
if (this.local.every(local => local.id !== extension.id)) {
promises.push(this.install(extension));
}
return TPromise.join(promises);
}
return TPromise.as(null);
});
});
})
.done(undefined, error => this.onError(error));
}
dispose(): void {
this.syncDelayer.cancel();
this.disposables = dispose(this.disposables);
}
}