Revert "Connection Store Refactor (#4632)" (#4671)

This reverts commit 756f77063a.
This commit is contained in:
Anthony Dresser
2019-03-22 11:30:20 -07:00
committed by GitHub
parent 8d5f676039
commit f5c9174c2f
19 changed files with 1008 additions and 897 deletions

View File

@@ -7,16 +7,14 @@
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants';
import * as Constants from 'sql/platform/connection/common/constants';
import { IConnectionConfig } from 'sql/platform/connection/common/iconnectionConfig';
import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connection/common/interfaces';
import * as Utils from 'sql/platform/connection/common/utils';
import { generateUuid } from 'vs/base/common/uuid';
import * as nls from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
const GROUPS_CONFIG_KEY = 'datasource.connectionGroups';
const CONNECTIONS_CONFIG_KEY = 'datasource.connections';
export interface ISaveGroupResult {
groups: IConnectionProfileGroup[];
newGroupId: string;
@@ -25,7 +23,7 @@ export interface ISaveGroupResult {
/**
* Implements connection profile file storage.
*/
export class ConnectionConfig {
export class ConnectionConfig implements IConnectionConfig {
public constructor(
private configurationService: IConfigurationService,
@@ -38,7 +36,7 @@ export class ConnectionConfig {
public getAllGroups(): IConnectionProfileGroup[] {
let allGroups: IConnectionProfileGroup[] = [];
let { user, workspace } = this.configurationService.inspect<IConnectionProfileGroup[]>(GROUPS_CONFIG_KEY);
let { user, workspace } = this.configurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName);
if (user) {
if (workspace) {
@@ -47,12 +45,13 @@ export class ConnectionConfig {
}
allGroups = allGroups.concat(user);
}
return allGroups.map(g => {
allGroups = allGroups.map(g => {
if (g.parentId === '' || !g.parentId) {
g.parentId = undefined;
}
return g;
});
return allGroups;
}
/**
@@ -61,7 +60,7 @@ export class ConnectionConfig {
public addConnection(profile: IConnectionProfile): Promise<IConnectionProfile> {
if (profile.saveProfile) {
return this.addGroupFromProfile(profile).then(groupId => {
let profiles = this.configurationService.inspect<IConnectionProfileStore[]>(CONNECTIONS_CONFIG_KEY).user;
let profiles = this.configurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
if (!profiles) {
profiles = [];
}
@@ -70,7 +69,7 @@ export class ConnectionConfig {
let newProfile = ConnectionProfile.convertToProfileStore(this._capabilitiesService, connectionProfile);
// Remove the profile if already set
let sameProfileInList = profiles.find(value => {
var sameProfileInList = profiles.find(value => {
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
return providerConnectionProfile.matches(connectionProfile);
});
@@ -83,7 +82,7 @@ export class ConnectionConfig {
profiles.push(newProfile);
}
return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER).then(() => connectionProfile);
return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER).then(() => connectionProfile);
});
} else {
return Promise.resolve(profile);
@@ -107,11 +106,11 @@ export class ConnectionConfig {
if (profile.groupId && profile.groupId !== Utils.defaultGroupId) {
return Promise.resolve(profile.groupId);
} else {
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(GROUPS_CONFIG_KEY).user;
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
let result = this.saveGroup(groups, profile.groupFullName, undefined, undefined);
groups = result.groups;
return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId);
return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER).then(() => result.newGroupId);
}
}
@@ -123,7 +122,7 @@ export class ConnectionConfig {
if (profileGroup.id) {
return Promise.resolve(profileGroup.id);
} else {
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(GROUPS_CONFIG_KEY).user;
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
let sameNameGroup = groups ? groups.find(group => group.name === profileGroup.name) : undefined;
if (sameNameGroup) {
let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists.");
@@ -132,13 +131,13 @@ export class ConnectionConfig {
let result = this.saveGroup(groups, profileGroup.name, profileGroup.color, profileGroup.description);
groups = result.groups;
return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER).then(() => result.newGroupId);
return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER).then(() => result.newGroupId);
}
}
}
private getConnectionProfilesForTarget(configTarget: ConfigurationTarget): IConnectionProfileStore[] {
let configs = this.configurationService.inspect<IConnectionProfileStore[]>(CONNECTIONS_CONFIG_KEY);
let configs = this.configurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName);
let profiles: IConnectionProfileStore[];
if (configs) {
if (configTarget === ConfigurationTarget.USER) {
@@ -148,7 +147,7 @@ export class ConnectionConfig {
}
if (profiles) {
if (this.fixConnectionIds(profiles)) {
this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, configTarget);
this.configurationService.updateValue(Constants.connectionsArrayName, profiles, configTarget);
}
} else {
profiles = [];
@@ -165,7 +164,8 @@ export class ConnectionConfig {
private fixConnectionIds(profiles: IConnectionProfileStore[]): boolean {
let idsCache: { [label: string]: boolean } = {};
let changed: boolean = false;
for (let profile of profiles) {
for (var index = 0; index < profiles.length; index++) {
var profile = profiles[index];
if (!profile.id) {
profile.id = generateUuid();
changed = true;
@@ -215,7 +215,7 @@ export class ConnectionConfig {
*/
public deleteConnection(profile: ConnectionProfile): Promise<void> {
// Get all connections in the settings
let profiles = this.configurationService.inspect<IConnectionProfileStore[]>(CONNECTIONS_CONFIG_KEY).user;
let profiles = this.configurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
// Remove the profile from the connections
profiles = profiles.filter(value => {
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
@@ -223,7 +223,7 @@ export class ConnectionConfig {
});
// Write connections back to settings
return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER);
return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER);
}
/**
@@ -236,7 +236,7 @@ export class ConnectionConfig {
// Add selected group to subgroups list
subgroups.push(group);
// Get all connections in the settings
let profiles = this.configurationService.inspect<IConnectionProfileStore[]>(CONNECTIONS_CONFIG_KEY).user;
let profiles = this.configurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
// Remove the profiles from the connections
profiles = profiles.filter(value => {
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
@@ -244,14 +244,14 @@ export class ConnectionConfig {
});
// Get all groups in the settings
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(GROUPS_CONFIG_KEY).user;
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
// Remove subgroups in the settings
groups = groups.filter((grp) => {
return !subgroups.some((item) => item.id === grp.id);
});
return Promise.all([
this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, ConfigurationTarget.USER),
this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER)
this.configurationService.updateValue(Constants.connectionsArrayName, profiles, ConfigurationTarget.USER),
this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER)
]).then(() => Promise.resolve());
}
@@ -259,14 +259,14 @@ export class ConnectionConfig {
* Moves the source group under the target group.
*/
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(GROUPS_CONFIG_KEY).user;
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
groups = groups.map(g => {
if (g.id === source.id) {
g.parentId = target.id;
}
return g;
});
return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER);
return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER);
}
/**
@@ -283,10 +283,10 @@ export class ConnectionConfig {
* Moves the connection under the target group with the new ID.
*/
private changeGroupIdForConnectionInSettings(profile: ConnectionProfile, newGroupID: string, target: ConfigurationTarget = ConfigurationTarget.USER): Promise<void> {
let profiles = target === ConfigurationTarget.USER ? this.configurationService.inspect<IConnectionProfileStore[]>(CONNECTIONS_CONFIG_KEY).user :
this.configurationService.inspect<IConnectionProfileStore[]>(CONNECTIONS_CONFIG_KEY).workspace;
let profiles = target === ConfigurationTarget.USER ? this.configurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user :
this.configurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).workspace;
if (profiles) {
if (profile.parent && profile.parent.id === UNSAVED_GROUP_ID) {
if (profile.parent && profile.parent.id === Constants.unsavedGroupId) {
profile.groupId = newGroupID;
profiles.push(ConnectionProfile.convertToProfileStore(this._capabilitiesService, profile));
} else {
@@ -298,7 +298,7 @@ export class ConnectionConfig {
});
}
return this.configurationService.updateValue(CONNECTIONS_CONFIG_KEY, profiles, target);
return this.configurationService.updateValue(Constants.connectionsArrayName, profiles, target);
} else {
return Promise.resolve();
}
@@ -320,12 +320,14 @@ export class ConnectionConfig {
}
public saveGroup(groups: IConnectionProfileGroup[], groupFullName: string, color: string, description: string): ISaveGroupResult {
let result: ISaveGroupResult;
let groupNames = ConnectionProfileGroup.getGroupFullNameParts(groupFullName);
return this.saveGroupInTree(groups, undefined, groupNames, color, description, 0);
result = this.saveGroupInTree(groups, undefined, groupNames, color, description, 0);
return result;
}
public editGroup(source: ConnectionProfileGroup): Promise<void> {
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(GROUPS_CONFIG_KEY).user;
let groups = this.configurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
let sameNameGroup = groups ? groups.find(group => group.name === source.name && group.id !== source.id) : undefined;
if (sameNameGroup) {
let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists.");
@@ -340,7 +342,7 @@ export class ConnectionConfig {
}
return g;
});
return this.configurationService.updateValue(GROUPS_CONFIG_KEY, groups, ConfigurationTarget.USER);
return this.configurationService.updateValue(Constants.connectionGroupsArrayName, groups, ConfigurationTarget.USER);
}
private isSameGroupName(group1: IConnectionProfileGroup, group2: IConnectionProfileGroup): boolean {

View File

@@ -17,6 +17,7 @@ import { ConnectionManagementInfo } from 'sql/platform/connection/common/connect
import * as Utils from 'sql/platform/connection/common/utils';
import * as Constants from 'sql/platform/connection/common/constants';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService';
import * as ConnectionContracts from 'sql/parts/connection/common/connection';
import { ConnectionStatusManager } from 'sql/platform/connection/common/connectionStatusManager';
import { DashboardInput } from 'sql/parts/dashboard/dashboardInput';
@@ -43,7 +44,9 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import * as platform from 'vs/platform/registry/common/platform';
import { Memento } from 'vs/workbench/common/memento';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { Event, Emitter } from 'vs/base/common/event';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
@@ -74,13 +77,16 @@ export class ConnectionManagementService extends Disposable implements IConnecti
private _connectionGlobalStatus = new ConnectionGlobalStatus(this._statusBarService);
constructor(
private _connectionMemento: Memento,
private _connectionStore: ConnectionStore,
@IStorageService _storageService: IStorageService,
@IConnectionDialogService private _connectionDialogService: IConnectionDialogService,
@IServerGroupController private _serverGroupController: IServerGroupController,
@IInstantiationService private _instantiationService: IInstantiationService,
@IEditorService private _editorService: IEditorService,
@ITelemetryService private _telemetryService: ITelemetryService,
@IConfigurationService private _configurationService: IConfigurationService,
@ICredentialsService private _credentialsService: ICredentialsService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IQuickInputService private _quickInputService: IQuickInputService,
@IEditorGroupsService private _editorGroupService: IEditorGroupsService,
@@ -91,8 +97,13 @@ export class ConnectionManagementService extends Disposable implements IConnecti
) {
super();
// _connectionMemento and _connectionStore are in constructor to enable this class to be more testable
if (!this._connectionMemento) {
this._connectionMemento = new Memento('ConnectionManagement', _storageService);
}
if (!this._connectionStore) {
this._connectionStore = _instantiationService.createInstance(ConnectionStore);
this._connectionStore = new ConnectionStore(this._connectionMemento,
this._configurationService, this._credentialsService, this._capabilitiesService);
}
// Register Statusbar item
@@ -124,6 +135,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
this.onConnectionChanged(() => this.refreshEditorTitles());
this.onConnect(() => this.refreshEditorTitles());
this.onDisconnect(() => this.refreshEditorTitles());
_storageService.onWillSaveState(() => this.shutdown());
}
public providerRegistered(providerId: string): boolean {
@@ -612,11 +624,11 @@ export class ConnectionManagementService extends Disposable implements IConnecti
}
public clearRecentConnection(connectionProfile: IConnectionProfile): void {
this._connectionStore.removeRecentConnection(connectionProfile);
this._connectionStore.removeConnectionToMemento(connectionProfile, Constants.recentConnections);
}
public getActiveConnections(providers?: string[]): ConnectionProfile[] {
return this._connectionStatusManager.getActiveConnectionProfiles(providers);
return this._connectionStatusManager.getActiveConnectionProfiles();
}
public getConnectionUriFromId(connectionId: string): string {
@@ -849,8 +861,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti
* Add a connection to the active connections list.
*/
private tryAddActiveConnection(connectionManagementInfo: ConnectionManagementInfo, newConnection: IConnectionProfile, addToMru: boolean): void {
if (newConnection && addToMru) {
this._connectionStore.addRecentConnection(newConnection)
if (newConnection) {
this._connectionStore.addActiveConnection(newConnection, addToMru)
.then(() => {
connectionManagementInfo.connectHandler(true);
}, err => {
@@ -918,6 +930,11 @@ export class ConnectionManagementService extends Disposable implements IConnecti
public onIntelliSenseCacheComplete(handle: number, connectionUri: string): void {
}
private shutdown(): void {
this._connectionStore.clearActiveConnections();
this._connectionMemento.saveMemento();
}
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.MoveServerConnection);
return this._connectionStore.changeGroupIdForConnectionGroup(source, target);
@@ -1079,6 +1096,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
this.doDisconnect(uri, profile).then(result => {
if (result) {
this.addTelemetryForConnectionDisconnected(input);
this._connectionStore.removeActiveConnection(input);
this._connectionStatusManager.removeConnection(uri);
resolve();
} else {

View File

@@ -5,26 +5,21 @@
'use strict';
import { ReverseLookUpMap } from 'sql/base/common/map';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionConfig } from 'sql/platform/connection/common/connectionConfig';
import { fixupConnectionCredentials } from 'sql/platform/connection/common/connectionInfo';
import * as Constants from 'sql/platform/connection/common/constants';
import * as ConnInfo from 'sql/platform/connection/common/connectionInfo';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService';
import { IConnectionConfig } from 'sql/platform/connection/common/iconnectionConfig';
import { ConnectionConfig } from 'sql/platform/connection/common/connectionConfig';
import { Memento } from 'vs/workbench/common/memento';
import { StorageScope } from 'vs/platform/storage/common/storage';
import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStateService } from 'vs/platform/state/common/state';
const MAX_CONNECTIONS_DEFAULT = 25;
const RECENT_CONNECTIONS_STATE_KEY = 'recentConnections';
const CRED_PREFIX = 'Microsoft.SqlTools';
const CRED_SEPARATOR = '|';
const CRED_ID_PREFIX = 'id:';
const CRED_ITEMTYPE_PREFIX = 'itemtype:';
const CRED_PROFILE_USER = 'Profile';
/**
* Manages the connections list including saved profiles and the most recently used connections
*
@@ -32,15 +27,39 @@ const CRED_PROFILE_USER = 'Profile';
* @class ConnectionStore
*/
export class ConnectionStore {
private groupIdMap = new ReverseLookUpMap<string, string>();
private connectionConfig = new ConnectionConfig(this.configurationService, this.capabilitiesService);
private _memento: any;
private _groupIdToFullNameMap: { [groupId: string]: string };
private _groupFullNameToIdMap: { [groupId: string]: string };
constructor(
@IStateService private stateService: IStateService,
@IConfigurationService private configurationService: IConfigurationService,
@ICredentialsService private credentialService: ICredentialsService,
@ICapabilitiesService private capabilitiesService: ICapabilitiesService
private _context: Memento,
private _configurationService: IConfigurationService,
private _credentialService: ICredentialsService,
private _capabilitiesService: ICapabilitiesService,
private _connectionConfig?: IConnectionConfig
) {
if (_context) {
this._memento = this._context.getMemento(StorageScope.GLOBAL);
}
this._groupIdToFullNameMap = {};
this._groupFullNameToIdMap = {};
if (!this._connectionConfig) {
this._connectionConfig = new ConnectionConfig(this._configurationService, this._capabilitiesService);
}
}
public static get CRED_PREFIX(): string { return 'Microsoft.SqlTools'; }
public static get CRED_SEPARATOR(): string { return '|'; }
public static get CRED_ID_PREFIX(): string { return 'id:'; }
public static get CRED_ITEMTYPE_PREFIX(): string { return 'itemtype:'; }
public static get CRED_PROFILE_USER(): string { return 'Profile'; }
public formatCredentialIdForCred(connectionProfile: IConnectionProfile): string {
if (!connectionProfile) {
throw new Error('Missing Connection which is required');
}
let itemTypeString: string = ConnectionStore.CRED_PROFILE_USER;
return this.formatCredentialId(connectionProfile, itemTypeString);
}
/**
@@ -51,17 +70,26 @@ export class ConnectionStore {
* @param {string} itemType type of the item (MRU or Profile) - optional
* @returns {string} formatted string with server, DB and username
*/
private formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string {
public formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string {
let connectionProfileInstance: ConnectionProfile = ConnectionProfile.fromIConnectionProfile(
this.capabilitiesService, connectionProfile);
let cred: string[] = [CRED_PREFIX];
this._capabilitiesService, connectionProfile);
if (!connectionProfileInstance.getConnectionInfoId()) {
throw new Error('Missing Id, which is required');
}
let cred: string[] = [ConnectionStore.CRED_PREFIX];
if (!itemType) {
itemType = CRED_PROFILE_USER;
itemType = ConnectionStore.CRED_PROFILE_USER;
}
cred.push(CRED_ITEMTYPE_PREFIX.concat(itemType));
cred.push(CRED_ID_PREFIX.concat(connectionProfileInstance.getConnectionInfoId()));
return cred.join(CRED_SEPARATOR);
ConnectionStore.pushIfNonEmpty(itemType, ConnectionStore.CRED_ITEMTYPE_PREFIX, cred);
ConnectionStore.pushIfNonEmpty(connectionProfileInstance.getConnectionInfoId(), ConnectionStore.CRED_ID_PREFIX, cred);
return cred.join(ConnectionStore.CRED_SEPARATOR);
}
private static pushIfNonEmpty(value: string, prefix: string, arr: string[]): void {
if (value) {
arr.push(prefix.concat(value));
}
}
/**
@@ -70,7 +98,7 @@ export class ConnectionStore {
*/
public isPasswordRequired(connection: IConnectionProfile): boolean {
if (connection) {
let connectionProfile = ConnectionProfile.fromIConnectionProfile(this.capabilitiesService, connection);
let connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, connection);
return connectionProfile.isPasswordRequired();
} else {
return false;
@@ -78,20 +106,28 @@ export class ConnectionStore {
}
public addSavedPassword(credentialsItem: IConnectionProfile): Promise<{ profile: IConnectionProfile, savedCred: boolean }> {
if (credentialsItem.savePassword && this.isPasswordRequired(credentialsItem) && !credentialsItem.password) {
let credentialId = this.formatCredentialId(credentialsItem, CRED_PROFILE_USER);
return this.credentialService.readCredential(credentialId)
.then(savedCred => {
if (savedCred) {
credentialsItem.password = savedCred.password;
credentialsItem.options['password'] = savedCred.password;
}
return { profile: credentialsItem, savedCred: !!savedCred };
});
} else {
// No need to look up the password
return Promise.resolve({ profile: credentialsItem, savedCred: credentialsItem.savePassword });
}
let self = this;
return new Promise<{ profile: IConnectionProfile, savedCred: boolean }>((resolve, reject) => {
if (credentialsItem.savePassword && this.isPasswordRequired(credentialsItem)
&& !credentialsItem.password) {
let credentialId = this.formatCredentialIdForCred(credentialsItem);
self._credentialService.readCredential(credentialId)
.then(savedCred => {
if (savedCred) {
credentialsItem.password = savedCred.password;
credentialsItem.options['password'] = savedCred.password;
}
resolve({ profile: credentialsItem, savedCred: !!savedCred });
},
reason => {
reject(reason);
});
} else {
// No need to look up the password
resolve({ profile: credentialsItem, savedCred: credentialsItem.savePassword });
}
});
}
/**
@@ -103,20 +139,34 @@ export class ConnectionStore {
* @returns {Promise<IConnectionProfile>} a Promise that returns the original profile, for help in chaining calls
*/
public saveProfile(profile: IConnectionProfile, forceWritePlaintextPassword?: boolean): Promise<IConnectionProfile> {
// Add the profile to the saved list, taking care to clear out the password field if necessary
let savedProfile = forceWritePlaintextPassword ? profile : this.getProfileWithoutPassword(profile);
return this.saveProfileToConfig(savedProfile)
.then(savedConnectionProfile => {
profile.groupId = savedConnectionProfile.groupId;
profile.id = savedConnectionProfile.id;
// Only save if we successfully added the profile
return this.saveProfilePasswordIfNeeded(profile);
}).then(() => {
// Add necessary default properties before returning
// this is needed to support immediate connections
fixupConnectionCredentials(profile);
return profile;
});
const self = this;
return new Promise<IConnectionProfile>((resolve, reject) => {
// Add the profile to the saved list, taking care to clear out the password field if necessary
let savedProfile: IConnectionProfile;
if (forceWritePlaintextPassword) {
savedProfile = profile;
} else {
savedProfile = this.getProfileWithoutPassword(profile);
}
self.saveProfileToConfig(savedProfile)
.then(savedConnectionProfile => {
profile.groupId = savedConnectionProfile.groupId;
profile.id = savedConnectionProfile.id;
// Only save if we successfully added the profile
return self.saveProfilePasswordIfNeeded(profile);
// And resolve / reject at the end of the process
}, err => {
reject(err);
}).then(resolved => {
// Add necessary default properties before returning
// this is needed to support immediate connections
ConnInfo.fixupConnectionCredentials(profile);
resolve(profile);
}, err => {
reject(err);
});
});
}
/**
@@ -126,15 +176,29 @@ export class ConnectionStore {
* @returns {Promise<string>} a Promise that returns the id of connection group
*/
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
return this.connectionConfig.addGroup(profile);
const self = this;
return new Promise<string>((resolve, reject) => {
self._connectionConfig.addGroup(profile).then(groupId => {
resolve(groupId);
}).catch(error => {
reject(error);
});
});
}
private saveProfileToConfig(profile: IConnectionProfile): Promise<IConnectionProfile> {
if (profile.saveProfile) {
return this.connectionConfig.addConnection(profile);
} else {
return Promise.resolve(profile);
}
const self = this;
return new Promise<IConnectionProfile>((resolve, reject) => {
if (profile.saveProfile) {
self._connectionConfig.addConnection(profile).then(savedProfile => {
resolve(savedProfile);
}).catch(error => {
reject(error);
});
} else {
resolve(profile);
}
});
}
/**
@@ -144,8 +208,12 @@ export class ConnectionStore {
* @returns {azdata.ConnectionInfo} the array of connections, empty if none are found
*/
public getRecentlyUsedConnections(providers?: string[]): ConnectionProfile[] {
let configValues = this.stateService.getItem<IConnectionProfile[]>(RECENT_CONNECTIONS_STATE_KEY, []).filter(c => !!c);
let configValues: IConnectionProfile[] = this._memento[Constants.recentConnections];
if (!configValues) {
configValues = [];
}
configValues = configValues.filter(c => !!(c));
if (providers && providers.length > 0) {
configValues = configValues.filter(c => providers.includes(c.providerName));
}
@@ -155,7 +223,7 @@ export class ConnectionStore {
private convertConfigValuesToConnectionProfiles(configValues: IConnectionProfile[]): ConnectionProfile[] {
return configValues.map(c => {
if (c) {
let connectionProfile = new ConnectionProfile(this.capabilitiesService, c);
let connectionProfile = new ConnectionProfile(this._capabilitiesService, c);
if (connectionProfile.saveProfile) {
if (!connectionProfile.groupFullName && connectionProfile.groupId) {
connectionProfile.groupFullName = this.getGroupFullName(connectionProfile.groupId);
@@ -173,9 +241,24 @@ export class ConnectionStore {
});
}
/**
* Gets the list of active connections. These will not include the password - a separate call to
* {addSavedPassword} is needed to fill that before connecting
*
* @returns {azdata.ConnectionInfo} the array of connections, empty if none are found
*/
public getActiveConnections(): ConnectionProfile[] {
let configValues: IConnectionProfile[] = this._memento[Constants.activeConnections];
if (!configValues) {
configValues = [];
}
return this.convertConfigValuesToConnectionProfiles(configValues);
}
public getProfileWithoutPassword(conn: IConnectionProfile): ConnectionProfile {
if (conn) {
let savedConn: ConnectionProfile = ConnectionProfile.fromIConnectionProfile(this.capabilitiesService, conn);
let savedConn: ConnectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, conn);
savedConn = savedConn.withoutPassword();
return savedConn;
@@ -193,35 +276,71 @@ export class ConnectionStore {
* @param {boolean} addToMru Whether to add this connection to the MRU
* @returns {Promise<void>} a Promise that returns when the connection was saved
*/
public addRecentConnection(conn: IConnectionProfile): Promise<void> {
let maxConnections = this.getMaxRecentConnectionsCount();
return this.addConnectionToState(conn, RECENT_CONNECTIONS_STATE_KEY, maxConnections, conn.savePassword);
}
private addConnectionToState(conn: IConnectionProfile, key: string, maxConnections?: number, savePassword?: boolean): Promise<void> {
// Get all profiles
let configValues = this.getConnectionsFromState(key);
let configToSave = this.addToConnectionList(conn, configValues);
if (maxConnections) {
// Remove last element if needed
if (configToSave.length > maxConnections) {
configToSave = configToSave.slice(0, maxConnections);
}
public async addActiveConnection(conn: IConnectionProfile, addToMru: boolean): Promise<void> {
if (addToMru) {
await this.addConnectionToMru(conn);
}
// Only add connections we don't already know about
if (!this.getActiveConnections().some(existingConn => existingConn.id === conn.id)) {
await this.addConnectionToMemento(conn, Constants.activeConnections, undefined, conn.savePassword);
}
this.stateService.setItem(key, configToSave);
return savePassword ? this.doSavePassword(conn).then() : Promise.resolve();
}
private removeConnectionFromState(conn: IConnectionProfile, key: string): void {
// Get all profiles
let configValues = this.getConnectionsFromState(key);
let configToSave = this.removeFromConnectionList(conn, configValues);
this.stateService.setItem(key, configToSave);
/**
* Adds the specified connection to the MRU list
* @param conn The connection to add
*/
private async addConnectionToMru(conn: IConnectionProfile): Promise<void> {
let maxConnections = this.getMaxRecentConnectionsCount();
if (ConnectionProfile.isConnectionToDefaultDb(conn)) {
conn.databaseName = '';
}
await this.addConnectionToMemento(conn, Constants.recentConnections, maxConnections);
}
private getConnectionsFromState(mementoKey: string): ConnectionProfile[] {
return this.convertConfigValuesToConnectionProfiles(this.stateService.getItem<IConnectionProfile[]>(mementoKey, []));
public addConnectionToMemento(conn: IConnectionProfile, mementoKey: string, maxConnections?: number, savePassword?: boolean): Promise<void> {
const self = this;
return new Promise<void>((resolve, reject) => {
// Get all profiles
let configValues = self.getConnectionsFromMemento(mementoKey);
let configToSave = this.addToConnectionList(conn, configValues);
if (maxConnections) {
// Remove last element if needed
if (configToSave.length > maxConnections) {
configToSave = configToSave.slice(0, maxConnections);
}
}
self._memento[mementoKey] = configToSave;
if (savePassword) {
self.doSavePassword(conn).then(result => {
resolve(undefined);
});
} else {
resolve(undefined);
}
});
}
public removeConnectionToMemento(conn: IConnectionProfile, mementoKey: string): Promise<void> {
const self = this;
return new Promise<void>((resolve, reject) => {
// Get all profiles
let configValues = self.getConnectionsFromMemento(mementoKey);
let configToSave = this.removeFromConnectionList(conn, configValues);
self._memento[mementoKey] = configToSave;
resolve(undefined);
});
}
public getConnectionsFromMemento(mementoKey: string): ConnectionProfile[] {
let configValues: IConnectionProfile[] = this._memento[mementoKey];
if (!configValues) {
configValues = [];
}
return this.convertConfigValuesToConnectionProfiles(configValues);
}
private addToConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] {
@@ -239,7 +358,11 @@ export class ConnectionStore {
list.unshift(savedProfile);
return list.filter(n => n !== undefined).map(c => c.toIConnectionProfile());
let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile;
});
return newList.filter(n => n !== undefined);
}
private removeFromConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] {
@@ -255,18 +378,37 @@ export class ConnectionStore {
return !equal;
});
return list.filter(n => n !== undefined).map(c => c.toIConnectionProfile());
let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile;
});
return newList.filter(n => n !== undefined);
}
/**
* Clear all recently used connections from the MRU list.
*/
public clearRecentlyUsed(): void {
this.stateService.setItem(RECENT_CONNECTIONS_STATE_KEY, []);
this._memento[Constants.recentConnections] = [];
}
public removeRecentConnection(conn: IConnectionProfile): void {
this.removeConnectionFromState(conn, RECENT_CONNECTIONS_STATE_KEY);
public clearFromMemento(name: string): void {
this._memento[name] = [];
}
/**
* Clear all active connections from the MRU list.
*/
public clearActiveConnections(): void {
this._memento[Constants.activeConnections] = [];
}
/**
* Remove a connection profile from the active connections list.
*/
public removeActiveConnection(conn: IConnectionProfile): Promise<void> {
return this.removeConnectionToMemento(conn, Constants.activeConnections);
}
private saveProfilePasswordIfNeeded(profile: IConnectionProfile): Promise<boolean> {
@@ -277,23 +419,32 @@ export class ConnectionStore {
}
private doSavePassword(conn: IConnectionProfile): Promise<boolean> {
if (conn.password) {
let credentialId = this.formatCredentialId(conn);
return this.credentialService.saveCredential(credentialId, conn.password);
} else {
return Promise.resolve(true);
}
let self = this;
return new Promise<boolean>((resolve, reject) => {
if (conn.password) {
let credentialId = this.formatCredentialId(conn);
self._credentialService.saveCredential(credentialId, conn.password)
.then((result) => {
resolve(result);
}, reason => {
// Bubble up error if there was a problem executing the set command
reject(reason);
});
} else {
resolve(true);
}
});
}
public getConnectionProfileGroups(withoutConnections?: boolean, providers?: string[]): ConnectionProfileGroup[] {
let profilesInConfiguration: ConnectionProfile[];
if (!withoutConnections) {
profilesInConfiguration = this.connectionConfig.getConnections(true);
profilesInConfiguration = this._connectionConfig.getConnections(true);
if (providers && providers.length > 0) {
profilesInConfiguration = profilesInConfiguration.filter(x => providers.includes(x.providerName));
}
}
let groups = this.connectionConfig.getAllGroups();
let groups = this._connectionConfig.getAllGroups();
let connectionProfileGroups = this.convertToConnectionGroup(groups, profilesInConfiguration, undefined);
return connectionProfileGroups;
@@ -328,53 +479,74 @@ export class ConnectionStore {
}
public getGroupFromId(groupId: string): IConnectionProfileGroup {
let groups = this.connectionConfig.getAllGroups();
let groups = this._connectionConfig.getAllGroups();
return groups.find(group => group.id === groupId);
}
private getMaxRecentConnectionsCount(): number {
return this.configurationService.getValue('sql.maxRecentConnections') || MAX_CONNECTIONS_DEFAULT;
let config = this._configurationService.getValue(Constants.sqlConfigSectionName);
let maxConnections: number = config[Constants.configMaxRecentConnections];
if (typeof (maxConnections) !== 'number' || maxConnections <= 0) {
maxConnections = MAX_CONNECTIONS_DEFAULT;
}
return maxConnections;
}
public editGroup(group: ConnectionProfileGroup): Promise<void> {
return this.connectionConfig.editGroup(group).then();
public editGroup(group: ConnectionProfileGroup): Promise<any> {
const self = this;
return new Promise<string>((resolve, reject) => {
self._connectionConfig.editGroup(group).then(() => {
resolve(null);
}).catch(error => {
reject(error);
});
});
}
public deleteConnectionFromConfiguration(connection: ConnectionProfile): Promise<void> {
return this.connectionConfig.deleteConnection(connection);
return this._connectionConfig.deleteConnection(connection);
}
public deleteGroupFromConfiguration(group: ConnectionProfileGroup): Promise<void> {
return this.connectionConfig.deleteGroup(group);
return this._connectionConfig.deleteGroup(group);
}
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
return this.connectionConfig.changeGroupIdForConnectionGroup(source, target);
return this._connectionConfig.changeGroupIdForConnectionGroup(source, target);
}
public canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean {
return this.connectionConfig.canChangeConnectionConfig(profile, newGroupID);
return this._connectionConfig.canChangeConnectionConfig(profile, newGroupID);
}
public changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void> {
return this.connectionConfig.changeGroupIdForConnection(source, targetGroupId).then();
return new Promise<void>((resolve, reject) => {
this._connectionConfig.changeGroupIdForConnection(source, targetGroupId).then(() => {
resolve();
}, (error => {
reject(error);
}));
});
}
private addGroupFullNameToMap(groupId: string, groupFullName: string): void {
if (groupId) {
this.groupIdMap.set(groupId, groupFullName);
this._groupIdToFullNameMap[groupId] = groupFullName;
}
if (groupFullName !== undefined) {
this.groupIdMap.set(groupFullName.toUpperCase(), groupId);
this._groupFullNameToIdMap[groupFullName.toUpperCase()] = groupId;
}
}
private getGroupFullName(groupId: string): string {
if (!this.groupIdMap.has(groupId)) {
if (groupId in this._groupIdToFullNameMap) {
return this._groupIdToFullNameMap[groupId];
} else {
// Load the cache
this.getConnectionProfileGroups(true);
}
return this.groupIdMap.get(groupId);
return this._groupIdToFullNameMap[groupId];
}
private getGroupId(groupFullName: string): string {
@@ -382,10 +554,14 @@ export class ConnectionStore {
groupFullName = '';
}
let key = groupFullName.toUpperCase();
if (!this.groupIdMap.reverseHas(key)) {
let result: string = '';
if (key in this._groupFullNameToIdMap) {
result = this._groupFullNameToIdMap[key];
} else {
// Load the cache
this.getConnectionProfileGroups(true);
result = this._groupFullNameToIdMap[key];
}
return this.groupIdMap.reverseGet(key);
return result;
}
}

View File

@@ -7,9 +7,19 @@
export const sqlConfigSectionName = 'sql';
export const outputChannelName = 'MSSQL';
export const connectionsArrayName = 'datasource.connections';
export const connectionGroupsArrayName = 'datasource.connectionGroups';
/**Unsaved connections Id */
export const unsavedGroupId = 'unsaved';
/* Memento constants */
export const activeConnections = 'ACTIVE_CONNECTIONS';
export const recentConnections = 'RECENT_CONNECTIONS';
export const capabilitiesOptions = 'OPTIONS_METADATA';
export const configMaxRecentConnections = 'maxRecentConnections';
export const mssqlProviderName = 'MSSQL';
export const anyProviderName = '*';
export const connectionProviderContextKey = 'connectionProvider';
@@ -24,5 +34,3 @@ export const passwordChars = '***************';
export const sqlLogin = 'SqlLogin';
export const integrated = 'Integrated';
export const azureMFA = 'AzureMFA';
export const UNSAVED_GROUP_ID = 'unsaved';

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* 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 { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
/**
* Interface for a configuration file that stores connection profiles.
*
* @export
* @interface IConnectionConfig
*/
export interface IConnectionConfig {
addConnection(profile: IConnectionProfile): Promise<IConnectionProfile>;
addGroup(profileGroup: IConnectionProfileGroup): Promise<string>;
getConnections(getWorkspaceConnections: boolean): ConnectionProfile[];
getAllGroups(): IConnectionProfileGroup[];
changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void>;
changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void>;
editGroup(group: ConnectionProfileGroup): Promise<void>;
deleteConnection(profile: ConnectionProfile): Promise<void>;
deleteGroup(group: ConnectionProfileGroup): Promise<void>;
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
}