Files
azuredatastudio/src/sql/platform/capabilities/common/capabilitiesService.ts
Anthony Dresser 87765e8673 Vscode merge (#4582)
* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
2019-03-19 17:44:35 -07:00

247 lines
8.4 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* 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 { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import * as Constants from 'sql/common/constants';
import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
import { toObject } from 'sql/base/common/map';
import * as azdata from 'sqlops';
import { Event, Emitter } from 'vs/base/common/event';
import { IAction } from 'vs/base/common/actions';
import { Memento } from 'vs/workbench/common/memento';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Disposable } from 'vs/base/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { entries } from 'sql/base/common/objects';
export const SERVICE_ID = 'capabilitiesService';
export const HOST_NAME = 'azdata';
export const HOST_VERSION = '1.0';
const connectionRegistry = Registry.as<IConnectionProviderRegistry>(ConnectionExtensions.ConnectionProviderContributions);
interface ConnectionCache {
[id: string]: ConnectionProviderProperties;
}
interface CapabilitiesMomento {
connectionProviderCache: ConnectionCache;
}
export const clientCapabilities = {
hostName: HOST_NAME,
hostVersion: HOST_VERSION
};
export interface ProviderFeatures {
connection: ConnectionProviderProperties;
}
export const ICapabilitiesService = createDecorator<ICapabilitiesService>(SERVICE_ID);
/**
* Interface for managing provider capabilities
*/
export interface ICapabilitiesService {
_serviceBrand: any;
/**
* Retrieve a list of registered capabilities providers
*/
getCapabilities(provider: string): ProviderFeatures;
/**
* get the old version of provider information
*/
getLegacyCapabilities(provider: string): azdata.DataProtocolServerCapabilities;
/**
* Register a capabilities provider
*/
registerProvider(provider: azdata.CapabilitiesProvider): void;
/**
* Returns true if the feature is available for given connection
*/
isFeatureAvailable(action: IAction, connectionManagementInfo: ConnectionManagementInfo): boolean;
/**
* When a new capabilities is registered, it emits the provider name, be to use to get the new capabilities
*/
readonly onCapabilitiesRegistered: Event<ProviderFeatures>;
/**
* Get an array of all known providers
*/
readonly providers: { [id: string]: ProviderFeatures };
}
/**
* Capabilities service implementation class. This class provides the ability
* to discover the DMP capabilties that a DMP provider offers.
*/
export class CapabilitiesService extends Disposable implements ICapabilitiesService {
_serviceBrand: any;
private _momento: Memento;
private _providers = new Map<string, ProviderFeatures>();
private _featureUpdateEvents = new Map<string, Emitter<ProviderFeatures>>();
private _legacyProviders = new Map<string, azdata.DataProtocolServerCapabilities>();
private _onCapabilitiesRegistered = this._register(new Emitter<ProviderFeatures>());
public readonly onCapabilitiesRegistered = this._onCapabilitiesRegistered.event;
constructor(
@IStorageService private _storageService: IStorageService,
@IExtensionService extensionService: IExtensionService,
@IExtensionManagementService extentionManagementService: IExtensionManagementService
) {
super();
this._momento = new Memento('capabilities', this._storageService);
if (!this.capabilities.connectionProviderCache) {
this.capabilities.connectionProviderCache = {};
}
// handle in case some extensions have already registered (unlikley)
entries(connectionRegistry.providers).map(v => {
this.handleConnectionProvider({ id: v[0], properties: v[1] });
});
// register for when new extensions are added
this._register(connectionRegistry.onNewProvider(this.handleConnectionProvider, this));
// handle adding already known capabilities (could have caching problems)
entries(this.capabilities.connectionProviderCache).map(v => {
this.handleConnectionProvider({ id: v[0], properties: v[1] }, false);
});
extensionService.whenInstalledExtensionsRegistered().then(() => {
this.cleanupProviders();
});
_storageService.onWillSaveState(() => this.shutdown());
this._register(extentionManagementService.onDidUninstallExtension(({ identifier }) => {
const connectionProvider = 'connectionProvider';
extensionService.getExtensions().then(i => {
let extension = i.find(c => c.identifier.value.toLowerCase() === identifier.id.toLowerCase());
if (extension && extension.contributes
&& extension.contributes[connectionProvider]
&& extension.contributes[connectionProvider].providerId) {
let id = extension.contributes[connectionProvider].providerId;
delete this.capabilities.connectionProviderCache[id];
}
});
}));
}
private cleanupProviders(): void {
let knownProviders = Object.keys(connectionRegistry.providers);
for (let key in this.capabilities.connectionProviderCache) {
if (!knownProviders.includes(key)) {
this._providers.delete(key);
delete this.capabilities.connectionProviderCache[key];
}
}
}
private handleConnectionProvider(e: { id: string, properties: ConnectionProviderProperties }, isNew = true): void {
let provider = this._providers.get(e.id);
if (provider) {
provider.connection = e.properties;
} else {
provider = {
connection: e.properties
};
this._providers.set(e.id, provider);
}
if (!this._featureUpdateEvents.has(e.id)) {
this._featureUpdateEvents.set(e.id, new Emitter<ProviderFeatures>());
}
if (isNew) {
this.capabilities.connectionProviderCache[e.id] = e.properties;
this._onCapabilitiesRegistered.fire(provider);
}
}
/**
* Retrieve a list of registered server capabilities
*/
public getCapabilities(provider: string): ProviderFeatures {
return this._providers.get(provider);
}
public getLegacyCapabilities(provider: string): azdata.DataProtocolServerCapabilities {
return this._legacyProviders.get(provider);
}
public get providers(): { [id: string]: ProviderFeatures } {
return toObject(this._providers);
}
private get capabilities(): CapabilitiesMomento {
return this._momento.getMemento(StorageScope.GLOBAL) as CapabilitiesMomento;
}
/**
* Register the capabilities provider and query the provider for its capabilities
* @param provider
*/
public registerProvider(provider: azdata.CapabilitiesProvider): void {
// request the capabilities from server
provider.getServerCapabilities(clientCapabilities).then(serverCapabilities => {
this._legacyProviders.set(serverCapabilities.providerName, serverCapabilities);
});
}
/**
* Returns true if the feature is available for given connection
* @param featureComponent a component which should have the feature name
* @param connectionManagementInfo connectionManagementInfo
*/
public isFeatureAvailable(action: IAction, connectionManagementInfo: ConnectionManagementInfo): boolean {
let isCloud = connectionManagementInfo && connectionManagementInfo.serverInfo && connectionManagementInfo.serverInfo.isCloud;
let isMssql = connectionManagementInfo.connectionProfile.providerName === 'MSSQL';
// TODO: The logic should from capabilities service.
if (action) {
let featureName: string = action.id;
switch (featureName) {
case Constants.BackupFeatureName:
if (isMssql) {
return connectionManagementInfo.connectionProfile.databaseName && !isCloud;
} else {
return !!connectionManagementInfo.connectionProfile.databaseName;
}
case Constants.RestoreFeatureName:
if (isMssql) {
return !isCloud;
} else {
return !!connectionManagementInfo.connectionProfile.databaseName;
}
default:
return true;
}
} else {
return true;
}
}
private shutdown(): void {
this._momento.saveMemento();
}
}