diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 05b56dbb65da6d5004586591760633d50126335a..60b490654e860623240c7d76c7600c049abce63a 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -277,6 +277,10 @@ { "name": "vs/workbench/services/notification", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/userData", + "project": "vscode-workbench" } ] } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 89d9ed41980aa44a2ebbd305b15e7765065acd6d..52de41f3a47353c303e28700db6d9dabdd4a6391 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1219,7 +1219,9 @@ declare module 'vscode' { export interface UserDataProvider { - dataProvider: FileSystemProvider; + read(key: string): Promise<{ version: number, content: string } | null>; + + write(key: string, version: number, content: string): Promise; } diff --git a/src/vs/workbench/api/browser/mainThreadUserData.ts b/src/vs/workbench/api/browser/mainThreadUserData.ts index af63864ffb069dad846588f8c83c577813327eec..dffe4363e7cafb032c56bc6c1caa23fcb0019eb9 100644 --- a/src/vs/workbench/api/browser/mainThreadUserData.ts +++ b/src/vs/workbench/api/browser/mainThreadUserData.ts @@ -6,74 +6,36 @@ import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { MainContext, ExtHostContext, IExtHostContext, MainThreadUserDataShape, ExtHostUserDataShape } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { IUserDataProviderService, IUserIdentityService, IUserDataProvider, IUserLoginProvider } from 'vs/workbench/services/userData/common/userData'; -import { Emitter, Event } from 'vs/base/common/event'; +import { IRemoteUserDataService, IUserData } from 'vs/workbench/services/userData/common/userData'; @extHostNamedCustomer(MainContext.MainThreadUserData) export class MainThreadUserData extends Disposable implements MainThreadUserDataShape { private readonly proxy: ExtHostUserDataShape; - private readonly loginProviders: Map = new Map(); constructor( extHostContext: IExtHostContext, - @IUserIdentityService private readonly userIdentityService: IUserIdentityService, - @IUserDataProviderService private readonly userDataProviderService: IUserDataProviderService, + @IRemoteUserDataService private readonly remoteUserDataService: IRemoteUserDataService ) { super(); this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostUserData); - this._register(toDisposable(() => { - this.userDataProviderService.deregisterAll(); - this.loginProviders.forEach((loginProvider, identity) => this.userIdentityService.deregisterUserLoginProvider(identity)); - this.loginProviders.clear(); - })); + this._register(toDisposable(() => this.remoteUserDataService.deregisterRemoteUserDataProvider())); } - $registerUserLoginProvider(identity: string, loggedIn: boolean): void { - const userLoginProvider = new UserLoginProvider(identity, loggedIn, this.proxy); - this.loginProviders.set(identity, userLoginProvider); - this.userIdentityService.registerUserLoginProvider(identity, userLoginProvider); + $registerUserDataProvider(name: string): void { + const proxy = this.proxy; + this.remoteUserDataService.registerRemoteUserDataProvider(name, { + read(key: string): Promise { + return proxy.$read(key); + }, + write(key: string, version: number, content: string): Promise { + return proxy.$write(key, version, content); + } + }); } - $registerUserDataProvider(identity: string, userDataProvider: IUserDataProvider): void { - this.userDataProviderService.registerUserDataProvider(identity, userDataProvider); - } - - $updateLoggedIn(identity: string, loggedIn: boolean): void { - const loginProvider = this.loginProviders.get(identity); - if (loginProvider) { - loginProvider.loggedIn = loggedIn; - } - } - -} - - -class UserLoginProvider extends Disposable implements IUserLoginProvider { - - private _onDidChange: Emitter = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; - - private _loggedIn: boolean; - get loggedIn(): boolean { return this._loggedIn; } - set loggedIn(loggedIn: boolean) { - if (this._loggedIn !== loggedIn) { - this._loggedIn = loggedIn; - this._onDidChange.fire(); - } - } - - constructor(private readonly identity: string, loggedIn: boolean, private readonly proxy: ExtHostUserDataShape) { - super(); - this._loggedIn = loggedIn; - } - - login(): Promise { - return this.proxy.$logIn(this.identity); - } - - logout(): Promise { - return this.proxy.$logOut(this.identity); + $deregisterUserDataProvider(): void { + this.remoteUserDataService.deregisterRemoteUserDataProvider(); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 33f114445a106d7f32a44f8ec4ab251f857717a3..140a4c35d51d1710910dd70b6cc128d23c3bd798 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -47,7 +47,7 @@ import { ExtensionActivationError } from 'vs/workbench/services/extensions/commo import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; +import { IUserData } from 'vs/workbench/services/userData/common/userData'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -142,9 +142,8 @@ export interface MainThreadConfigurationShape extends IDisposable { } export interface MainThreadUserDataShape extends IDisposable { - $registerUserLoginProvider(identitiy: string, loggedIn: boolean): void; - $updateLoggedIn(identitiy: string, loggedIn: boolean): void; - $registerUserDataProvider(identitiy: string, userDataProvider: IUserDataProvider): void; + $registerUserDataProvider(name: string): void; + $deregisterUserDataProvider(): void; } export interface MainThreadDiagnosticsShape extends IDisposable { @@ -753,8 +752,8 @@ export interface ExtHostConfigurationShape { } export interface ExtHostUserDataShape { - $logIn(identity: string): Promise; - $logOut(identity: string): Promise; + $read(key: string): Promise; + $write(key: string, version: number, content: string): Promise; } export interface ExtHostDiagnosticsShape { diff --git a/src/vs/workbench/api/common/extHostUserData.ts b/src/vs/workbench/api/common/extHostUserData.ts index 5472a0542b5255a0e6637eddc2db38988c30ca94..5c671f795b534eb908a9d300d29d6e06925b8b1d 100644 --- a/src/vs/workbench/api/common/extHostUserData.ts +++ b/src/vs/workbench/api/common/extHostUserData.ts @@ -4,50 +4,45 @@ *--------------------------------------------------------------------------------------------*/ import { ExtHostUserDataShape, MainThreadUserDataShape } from './extHost.protocol'; -import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; import * as vscode from 'vscode'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserData } from 'vs/workbench/services/userData/common/userData'; export class ExtHostUserData implements ExtHostUserDataShape { - private readonly loginProviders: Map = new Map(); + private name: string | null = null; + private userDataProvider: vscode.UserDataProvider | null = null; constructor( private readonly proxy: MainThreadUserDataShape, - private readonly extHostFileSystem: ExtHostFileSystem + private readonly logService: ILogService, ) { } - registerUserDataProvider(identity: string, userDataProvider: vscode.UserDataProvider): vscode.Disposable { - const userDataScheme = `vscode-userdata-${identity}`; - const disposable = this.extHostFileSystem.registerFileSystemProvider(userDataScheme, userDataProvider.dataProvider); - this.proxy.$registerUserDataProvider(identity, { userDataScheme }); - return disposable; - } - - registerUserLoginProvider(identity: string, loginProvider: vscode.UserLoginProvider): vscode.Disposable { - this.loginProviders.set(identity, loginProvider); - this.proxy.$registerUserLoginProvider(identity, loginProvider.isLoggedin()); - const disposable = new DisposableStore(); - disposable.add(loginProvider.onDidChange(() => this.proxy.$updateLoggedIn(identity, loginProvider.isLoggedin()))); - disposable.add(toDisposable(() => this.loginProviders.delete(identity))); - return disposable; + registerUserDataProvider(name: string, userDataProvider: vscode.UserDataProvider): vscode.Disposable { + if (this.userDataProvider) { + this.logService.warn(`A user data provider '${this.name}' already exists hence ignoring the remote user data provider '${name}'.`); + return Disposable.None; + } + this.userDataProvider = userDataProvider; + this.name = name; + this.proxy.$registerUserDataProvider(name); + return toDisposable(() => this.proxy.$deregisterUserDataProvider()); } - async $logIn(identity: string): Promise { - const loginProvider = this.loginProviders.get(identity); - if (!loginProvider) { - return Promise.reject(new Error(`No login provider found for ${identity}`)); + $read(key: string): Promise { + if (!this.userDataProvider) { + throw new Error('No remote user data provider exists.'); } - await loginProvider.login(); + return this.userDataProvider.read(key); } - $logOut(identity: string): Promise { - const loginProvider = this.loginProviders.get(identity); - if (!loginProvider) { - return Promise.reject(new Error(`No login provider found for ${identity}`)); + $write(key: string, version: number, content: string): Promise { + if (!this.userDataProvider) { + throw new Error('No remote user data provider exists.'); } - return Promise.resolve(); + return this.userDataProvider.write(key, version, content); } } diff --git a/src/vs/workbench/api/common/userDataExtensionPoint.ts b/src/vs/workbench/api/common/userDataExtensionPoint.ts deleted file mode 100644 index 267c1ab5ba56613d4530d71f8f64c24ad30721a4..0000000000000000000000000000000000000000 --- a/src/vs/workbench/api/common/userDataExtensionPoint.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IUserIdentityService, IUserIdentity } from 'vs/workbench/services/userData/common/userData'; - -export interface IUserFriendlyUserIdentityDescriptor { - id: string; - title: string; - iconText: string; -} - -export const userIdentityContribution: IJSONSchema = { - description: localize('vscode.extension.contributes.userIdentity', 'Contributes user identity to the editor'), - type: 'object', - properties: { - id: { - description: localize({ key: 'vscode.extension.contributes.user.identity.id', comment: ['Contribution refers to those that an extension contributes to VS Code through an extension/contribution point. '] }, "Unique id to identify the user user identity"), - type: 'string', - pattern: '^[a-zA-Z0-9_-]+$' - }, - title: { - description: localize('vscode.extension.contributes.views.containers.title', 'Human readable string used to render the user identity'), - type: 'string' - }, - iconText: { - description: localize('vscode.extension.contributes.views.containers.icon', "Path to the user identity icon."), - type: 'string' - } - } -}; - - -const viewsContainersExtensionPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'userData', - jsonSchema: userIdentityContribution -}); - -class UserIdentityExtensionHandler implements IWorkbenchContribution { - - constructor( - @IUserIdentityService private readonly userIdentityService: IUserIdentityService - ) { - this.handleAndRegisterUserIdentities(); - } - - private handleAndRegisterUserIdentities() { - viewsContainersExtensionPoint.setHandler((extensions, { added, removed }) => { - if (removed.length) { - this.removeUserIdentities(removed); - } - if (added.length) { - this.addUserIdentities(added); - } - }); - } - - private addUserIdentities(extensionPoints: readonly IExtensionPointUser[]) { - const userIdentities: IUserIdentity[] = []; - for (let { value, collector } of extensionPoints) { - if (!this.isValidUserIdentity(value, collector)) { - return; - } - userIdentities.push({ - identity: value.id, - title: value.title, - iconText: `$(${value.iconText})` - }); - } - if (userIdentities.length) { - this.userIdentityService.registerUserIdentities(userIdentities); - } - } - - private removeUserIdentities(extensionPoints: readonly IExtensionPointUser[]) { - const identities = extensionPoints.map(({ value }) => value.id); - if (identities.length) { - this.userIdentityService.deregisterUserIdentities(identities); - } - } - - - private isValidUserIdentity(userIdentityDescriptor: IUserFriendlyUserIdentityDescriptor, collector: ExtensionMessageCollector): boolean { - if (typeof userIdentityDescriptor.id !== 'string') { - collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string`. Only alphanumeric characters, '_', and '-' are allowed.", 'id')); - return false; - } - if (!(/^[a-z0-9_-]+$/i.test(userIdentityDescriptor.id))) { - collector.error(localize('requireidstring', "property `{0}` is mandatory and must be of type `string`. Only alphanumeric characters, '_', and '-' are allowed.", 'id')); - return false; - } - if (typeof userIdentityDescriptor.title !== 'string') { - collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'title')); - return false; - } - if (typeof userIdentityDescriptor.iconText !== 'string') { - collector.error(localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'icon')); - return false; - } - return true; - } - -} - -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(UserIdentityExtensionHandler, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/userData/common/userData.contribution.ts b/src/vs/workbench/contrib/userData/common/userData.contribution.ts index d7b27434f22d2f989a9ae9c4c6f4dde04ddff02e..9e5ad6a79a97e4210336294ca271825d8601541f 100644 --- a/src/vs/workbench/contrib/userData/common/userData.contribution.ts +++ b/src/vs/workbench/contrib/userData/common/userData.contribution.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserIdentityService, IUserDataProviderService, IUserIdentity, IUserDataSyncService, SyncStatus } from 'vs/workbench/services/userData/common/userData'; -import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { IUserDataSyncService, IRemoteUserDataService } from 'vs/workbench/services/userData/common/userData'; import { localize } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { Action } from 'vs/base/common/actions'; @@ -14,12 +13,12 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; + +const CONTEXT_SYNC_ENABLED = new RawContextKey('syncEnabled', false); Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ @@ -41,149 +40,57 @@ class AutoSyncUserDataContribution extends Disposable implements IWorkbenchContr constructor( @IConfigurationService private readonly configurationService: IConfigurationService, - @IUserIdentityService private readonly userIdentityService: IUserIdentityService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IExtensionService private readonly extensionService: IExtensionService, ) { super(); this.autoSync(); - this._register(Event.any(this.userIdentityService.onDidDeregisterUserIdentities, this.configurationService.onDidChangeConfiguration)(() => this.autoSync())); } private async autoSync(): Promise { if (this.configurationService.getValue('userData.autoSync')) { - const userIdentity = this.userIdentityService.getUserIndetities()[0]; - if (userIdentity) { - await this.extensionService.activateByEvent(`onUserData:${userIdentity.identity}`); - this.userDataSyncService.synchronise(); - } + this.userDataSyncService.synchronise(); } } } -class UserDataSyncStatusContribution extends Disposable implements IWorkbenchContribution { +class UserDataSyncContextUpdateContribution extends Disposable implements IWorkbenchContribution { - private readonly userDataSyncStatusAccessor: IStatusbarEntryAccessor; + private syncEnablementContext: IContextKey; constructor( - @IUserIdentityService private userIdentityService: IUserIdentityService, - @IStatusbarService private statusbarService: IStatusbarService, - @IUserDataSyncService private userDataSyncService: IUserDataSyncService, + @IRemoteUserDataService remoteUserDataService: IRemoteUserDataService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); - this.userDataSyncStatusAccessor = this.statusbarService.addEntry({ - text: '', - command: ShowUserDataSyncActions.ID - }, 'userDataSyncStatusEntry', localize('user data sync', "Sync User Data"), StatusbarAlignment.LEFT, 10); - this.updateUserDataSyncStatusAccessor(); - this._register(Event.any( - this.userIdentityService.onDidRegisterUserIdentities, this.userIdentityService.onDidDeregisterUserIdentities, - this.userIdentityService.onDidRegisterUserLoginProvider, this.userIdentityService.onDidDeregisterUserLoginProvider, - this.userDataSyncService.onDidChangeSyncStatus) - (() => this.updateUserDataSyncStatusAccessor())); - this._register(this.userIdentityService.onDidRegisterUserLoginProvider((identity => this.onDidRegisterUserLoginProvider(identity)))); + this.syncEnablementContext = CONTEXT_SYNC_ENABLED.bindTo(contextKeyService); + this.syncEnablementContext.set(remoteUserDataService.isEnabled()); + this._register(remoteUserDataService.onDidChangeEnablement(enabled => this.syncEnablementContext.set(enabled))); } - private onDidRegisterUserLoginProvider(identity: string): void { - const userLoginProvider = this.userIdentityService.getUserLoginProvider(identity); - if (userLoginProvider) { - this._register(userLoginProvider.onDidChange(() => this.updateUserDataSyncStatusAccessor())); - } - } - - private updateUserDataSyncStatusAccessor(): void { - const userIdentity = this.userIdentityService.getUserIndetities()[0]; - if (userIdentity) { - const loginProvider = this.userIdentityService.getUserLoginProvider(userIdentity.identity); - const neededSignIn = loginProvider && !loginProvider.loggedIn; - const text = this.getText(userIdentity, !!neededSignIn); - this.userDataSyncStatusAccessor.update({ text, command: ShowUserDataSyncActions.ID }); - this.statusbarService.updateEntryVisibility('userDataSyncStatusEntry', true); - } else { - this.statusbarService.updateEntryVisibility('userDataSyncStatusEntry', false); - } - } - - private getText(userIdentity: IUserIdentity, neededSignIn: boolean): string { - if (neededSignIn) { - const signinText = localize('sign in', "{0}: Sign in to sync", userIdentity.title); - return userIdentity.iconText ? `${userIdentity.iconText} ${signinText}` : signinText; - } - if (userIdentity.iconText) { - const syncText = this.userDataSyncService.syncStatus === SyncStatus.Syncing ? localize('syncing', "{0}: Synchronosing...", userIdentity.title) : localize('sync user data', "{0}: Sync", userIdentity.title); - return `${userIdentity.iconText} ${syncText}`; - } - return userIdentity.title; - } } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(UserDataSyncStatusContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncContextUpdateContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(AutoSyncUserDataContribution, LifecyclePhase.Ready); -export class ShowUserDataSyncActions extends Action { +class ShowUserDataSyncActions extends Action { public static ID: string = 'workbench.userData.actions.showUserDataSyncActions'; public static LABEL: string = localize('workbench.userData.actions.showUserDataSyncActions.label', "Show User Data Sync Actions"); constructor( @IQuickInputService private readonly quickInputService: IQuickInputService, - @IUserDataProviderService private readonly userDataProviderService: IUserDataProviderService, - @IUserIdentityService private userIdentityService: IUserIdentityService, @ICommandService private commandService: ICommandService, - @IExtensionService private extensionService: IExtensionService, - @IStorageService private storageService: IStorageService, - @IDialogService private dialogService: IDialogService, @IConfigurationService private configurationService: IConfigurationService, ) { super(ShowUserDataSyncActions.ID, ShowUserDataSyncActions.LABEL); } async run(): Promise { - const userIdentity = this.userIdentityService.getUserIndetities()[0]; - if (!userIdentity) { - return; - } - - await this.extensionService.activateByEvent(`onUserData:${userIdentity.identity}`); - const userDataProvider = this.userDataProviderService.getUserDataProvider(userIdentity.identity); - if (!userDataProvider) { - return; - } - - const loginProvider = this.userIdentityService.getUserLoginProvider(userIdentity.identity); - if (loginProvider && !loginProvider.loggedIn) { - if (!await this.promptToSigin(userIdentity)) { - return; - } - await loginProvider.login(); - if (!loginProvider.loggedIn) { - return; - } - } return this.showSyncActions(); } - private async promptToSigin(userIdentity: IUserIdentity): Promise { - if (!this.storageService.getBoolean(`workbench.userData.${userIdentity.identity}.donotAskSignIn`, StorageScope.GLOBAL, false)) { - const result = await this.dialogService.confirm({ - message: localize('sign in to start sync', "Sign In to Start Sync"), - detail: localize('sign in deatils', "Sign in to {0} to get your settings, keybindings, snippets and extensions.", userIdentity.title), - primaryButton: localize('sign in primary', "Sign In"), - secondaryButton: localize('no thanks', "No Thanks"), - checkbox: { - label: localize('doNotAskAgain', "Do not ask me again") - }, - }); - if (result.confirmed && result.checkboxChecked) { - this.storageService.store(`workbench.userData.${userIdentity.identity}.donotAskSignIn`, true, StorageScope.GLOBAL); - } - return result.confirmed; - } - return true; - } - private async showSyncActions(): Promise { const autoSync = this.configurationService.getValue('userData.autoSync'); const picks = []; @@ -215,3 +122,13 @@ CommandsRegistry.registerCommand('workbench.userData.actions.startAutoSync', (se const configurationService = serviceAccessor.get(IConfigurationService); return configurationService.updateValue('userData.autoSync', true); }); + +MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: { + id: ShowUserDataSyncActions.ID, + title: localize('synchronise user data', "Sync...") + }, + when: CONTEXT_SYNC_ENABLED, + order: 1 +}); diff --git a/src/vs/workbench/services/userData/common/remoteUserDataService.ts b/src/vs/workbench/services/userData/common/remoteUserDataService.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e71e26f455fdb113313849710ee39393a0e1399 --- /dev/null +++ b/src/vs/workbench/services/userData/common/remoteUserDataService.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, } from 'vs/base/common/lifecycle'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IRemoteUserDataService, IRemoteUserDataProvider, IUserData } from 'vs/workbench/services/userData/common/userData'; +import { ILogService } from 'vs/platform/log/common/log'; + +export class RemoteUserDataService extends Disposable implements IRemoteUserDataService { + + _serviceBrand: any; + + private remoteUserDataProvider: IRemoteUserDataProvider | null = null; + private name: string | null = null; + + private readonly _onDidChangeEnablement: Emitter = this._register(new Emitter()); + readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; + + constructor( + @ILogService private logService: ILogService + ) { + super(); + } + + registerRemoteUserDataProvider(name: string, remoteUserDataProvider: IRemoteUserDataProvider): void { + if (this.remoteUserDataProvider) { + this.logService.warn(`A remote user data provider '${this.name}' already exists hence ignoring the remote user data provider '${name}'.`); + return; + } + this.remoteUserDataProvider = remoteUserDataProvider; + this.name = name; + this._onDidChangeEnablement.fire(true); + } + + deregisterRemoteUserDataProvider(): void { + this.remoteUserDataProvider = null; + this.name = null; + this._onDidChangeEnablement.fire(false); + } + + getName(): string | null { + return this.name; + } + + isEnabled(): boolean { + return !!this.remoteUserDataProvider; + } + + read(key: string): Promise { + if (!this.remoteUserDataProvider) { + throw new Error('No remote user data provider exists.'); + } + return this.remoteUserDataProvider.read(key); + } + + write(key: string, version: number, content: string): Promise { + if (!this.remoteUserDataProvider) { + throw new Error('No remote user data provider exists.'); + } + return this.remoteUserDataProvider.write(key, version, content); + } + +} + +registerSingleton(IRemoteUserDataService, RemoteUserDataService); diff --git a/src/vs/workbench/services/userData/common/settingsSync.ts b/src/vs/workbench/services/userData/common/settingsSync.ts new file mode 100644 index 0000000000000000000000000000000000000000..0cff65b504fab13fef27eeb2ac093fd4b801fd83 --- /dev/null +++ b/src/vs/workbench/services/userData/common/settingsSync.ts @@ -0,0 +1,404 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IRemoteUserDataService, IUserData, RemoteUserDataError, RemoteUserDataErrorCode } from 'vs/workbench/services/userData/common/userData'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { parse, JSONPath } from 'vs/base/common/json'; +import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { isEqual } from 'vs/base/common/resources'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { localize } from 'vs/nls'; +import { Edit } from 'vs/base/common/jsonFormatter'; +import { repeat } from 'vs/base/common/strings'; +import { setProperty } from 'vs/base/common/jsonEdit'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; + +export interface ISettingsSyncService { + sync(): Promise; +} + +interface ISyncPreviewResult { + readonly fileContent: IFileContent | null; + readonly remoteUserData: IUserData | null; + readonly localSettingsPreviewModel: ITextModel; + readonly remoteSettingsPreviewModel: ITextModel; + readonly hasChanges: boolean; +} + +export class SettingsSyncService extends Disposable implements ISettingsSyncService, ITextModelContentProvider { + + private static LAST_SYNC_SETTINGS_STORAGE_KEY: string = 'LAST_SYNC_SETTINGS_CONTENTS'; + private static EXTERNAL_USER_DATA_SETTINGS_KEY: string = 'settings'; + + private readonly remoteSettingsPreviewResource: URI; + private readonly localSettingsPreviewResource: URI; + + private syncPreviewResultPromise: Promise | null = null; + + constructor( + @IFileService private readonly fileService: IFileService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IStorageService private readonly storageService: IStorageService, + @IRemoteUserDataService private readonly remoteUserDataService: IRemoteUserDataService, + @ITextModelService private readonly textModelResolverService: ITextModelService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + @IEditorService private readonly editorService: IEditorService + ) { + super(); + + this.remoteSettingsPreviewResource = URI.file('remote').with({ scheme: 'vscode-settings-sync' }); + this.localSettingsPreviewResource = this.workbenchEnvironmentService.settingsResource.with({ scheme: 'vscode-settings-sync' }); + + this.textModelResolverService.registerTextModelContentProvider('vscode-settings-sync', this); + } + + provideTextContent(uri: URI): Promise | null { + if (isEqual(this.remoteSettingsPreviewResource, uri, false)) { + return this.getPreview().then(({ remoteSettingsPreviewModel }) => remoteSettingsPreviewModel); + } + if (isEqual(this.localSettingsPreviewResource, uri, false)) { + return this.getPreview().then(({ localSettingsPreviewModel }) => localSettingsPreviewModel); + } + return null; + } + + async sync(): Promise { + + const result = await this.getPreview(); + + if (result.localSettingsPreviewModel.getValue() === result.remoteSettingsPreviewModel.getValue()) { + // Ask to show preview? + await this.applySyncPreview(result); + this.syncPreviewResultPromise = null; + return; + } + + await this.editorService.openEditor({ + leftResource: this.remoteSettingsPreviewResource, + rightResource: this.localSettingsPreviewResource, + label: localize('fileReplaceChanges', "Remote Settings ↔ Local Settings (Settings Preview)"), + options: { + preserveFocus: false, + pinned: true, + revealIfVisible: true + } + }); + + } + + private async applySyncPreview({ fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel }: ISyncPreviewResult): Promise { + const syncedRemoteUserData = remoteUserData && remoteUserData.content === remoteSettingsPreviewModel.getValue() ? remoteUserData : { content: remoteSettingsPreviewModel.getValue(), version: remoteUserData ? remoteUserData.version + 1 : 1 }; + if (!(remoteUserData && remoteUserData.version === syncedRemoteUserData.version)) { + await this.writeToRemote(syncedRemoteUserData); + } + await this.writeToLocal(localSettingsPreviewModel.getValue(), fileContent, syncedRemoteUserData); + } + + private getPreview(): Promise { + if (!this.syncPreviewResultPromise) { + this.syncPreviewResultPromise = this.generatePreview(); + } + return this.syncPreviewResultPromise; + } + + private async generatePreview(): Promise { + const fileContent = await this.getLocalFileContent(); + const remoteUserData = await this.remoteUserDataService.read(SettingsSyncService.EXTERNAL_USER_DATA_SETTINGS_KEY); + + const remoteSettingsPreviewModel = this.modelService.getModel(this.remoteSettingsPreviewResource) || this.modelService.createModel('', this.modeService.create('jsonc'), this.remoteSettingsPreviewResource); + const localSettingsPreviewModel = this.modelService.getModel(this.localSettingsPreviewResource) || this.modelService.createModel('', this.modeService.create('jsonc'), this.localSettingsPreviewResource); + + if (fileContent && !remoteUserData) { + // Remote does not exist, so sync with local. + const localContent = fileContent.value.toString(); + localSettingsPreviewModel.setValue(localContent); + remoteSettingsPreviewModel.setValue(localContent); + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: true }; + } + + if (remoteUserData && !fileContent) { + // Settings file does not exist, so sync with remote contents. + const remoteContent = remoteUserData.content; + localSettingsPreviewModel.setValue(remoteContent); + remoteSettingsPreviewModel.setValue(remoteContent); + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: true }; + } + + if (fileContent && remoteUserData) { + + const localContent: string = fileContent.value.toString(); + const remoteContent: string = remoteUserData.content; + const lastSyncData = this.getLastSyncUserData(); + + // Already in Sync. + if (localContent === remoteUserData.content) { + localSettingsPreviewModel.setValue(localContent); + remoteSettingsPreviewModel.setValue(remoteContent); + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: false }; + } + + // Not synced till now + if (!lastSyncData) { + localSettingsPreviewModel.setValue(localContent); + remoteSettingsPreviewModel.setValue(remoteContent); + await this.mergeContents(localSettingsPreviewModel, remoteSettingsPreviewModel, null); + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: true }; + } + + // Remote data is newer than last synced data + if (remoteUserData.version > lastSyncData.version) { + + // Local content is same as last synced. So, sync with remote content. + if (lastSyncData.content === localContent) { + localSettingsPreviewModel.setValue(remoteContent); + remoteSettingsPreviewModel.setValue(remoteContent); + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: true }; + } + + // Local content is diverged from last synced. Required merge and sync. + localSettingsPreviewModel.setValue(localContent); + remoteSettingsPreviewModel.setValue(remoteContent); + await this.mergeContents(localSettingsPreviewModel, remoteSettingsPreviewModel, null); + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: true }; + } + + // Remote data is same as last synced data + if (lastSyncData.version === remoteUserData.version) { + + // Local contents are same as last synced data. No op. + if (lastSyncData.content === localContent) { + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: false }; + } + + // New local contents. Sync with Local. + localSettingsPreviewModel.setValue(localContent); + remoteSettingsPreviewModel.setValue(localContent); + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: true }; + } + + } + + return { fileContent, remoteUserData, localSettingsPreviewModel, remoteSettingsPreviewModel, hasChanges: false }; + + } + + private getLastSyncUserData(): IUserData | null { + const lastSyncStorageContents = this.storageService.get(SettingsSyncService.LAST_SYNC_SETTINGS_STORAGE_KEY, StorageScope.GLOBAL, undefined); + if (lastSyncStorageContents) { + return JSON.parse(lastSyncStorageContents); + } + return null; + } + + private async getLocalFileContent(): Promise { + try { + return await this.fileService.readFile(this.workbenchEnvironmentService.settingsResource); + } catch (error) { + if (error instanceof FileSystemProviderError && error.code !== FileSystemProviderErrorCode.FileNotFound) { + return null; + } + throw error; + } + } + + private async mergeContents(localSettingsPreviewModel: ITextModel, remoteSettingsPreviewModel: ITextModel, lastSyncedContent: string | null): Promise { + const local = parse(localSettingsPreviewModel.getValue()); + const remote = parse(remoteSettingsPreviewModel.getValue()); + const base = lastSyncedContent ? parse(lastSyncedContent) : null; + + const baseToLocal = base ? this.compare(base, local) : { added: new Set(), removed: new Set(), updated: new Set() }; + const baseToRemote = base ? this.compare(base, remote) : { added: new Set(), removed: new Set(), updated: new Set() }; + const localToRemote = this.compare(local, remote); + + const conflicts: Set = new Set(); + + // Removed settings in Local + for (const key of baseToLocal.removed.keys()) { + // Got updated in remote + if (baseToRemote.updated.has(key)) { + conflicts.add(key); + } else { + this.removeSetting(remoteSettingsPreviewModel, key); + } + } + + // Removed settings in Remote + for (const key of baseToRemote.removed.keys()) { + // Got updated in local + if (baseToLocal.updated.has(key)) { + conflicts.add(key); + } else { + this.removeSetting(localSettingsPreviewModel, key); + } + } + + // Added settings in Local + for (const key of baseToLocal.added.keys()) { + // Got added in remote + if (baseToRemote.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + const edit = this.getEdit(remoteSettingsPreviewModel, [key], local[key]); + if (edit) { + this.applyEditsToBuffer(edit, remoteSettingsPreviewModel); + } + } + } + + // Added settings in remote + for (const key of baseToRemote.added.keys()) { + // Got added in local + if (baseToLocal.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + const edit = this.getEdit(localSettingsPreviewModel, [key], remote[key]); + if (edit) { + this.applyEditsToBuffer(edit, localSettingsPreviewModel); + } + } + } + + // Updated settings in Local + for (const key of baseToLocal.updated.keys()) { + // Got updated in remote + if (baseToRemote.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + const edit = this.getEdit(remoteSettingsPreviewModel, [key], local[key]); + if (edit) { + this.applyEditsToBuffer(edit, remoteSettingsPreviewModel); + } + } + } + + // Updated settings in Remote + for (const key of baseToRemote.updated.keys()) { + // Got updated in local + if (baseToLocal.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + const edit = this.getEdit(localSettingsPreviewModel, [key], remote[key]); + if (edit) { + this.applyEditsToBuffer(edit, localSettingsPreviewModel); + } + } + } + } + + private compare(from: { [key: string]: any }, to: { [key: string]: any }): { added: Set, removed: Set, updated: Set } { + const fromKeys = Object.keys(from); + const toKeys = Object.keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + const value1 = from[key]; + const value2 = to[key]; + if (!objects.equals(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; + } + + private removeSetting(model: ITextModel, key: string): void { + const edit = this.getEdit(model, [key], undefined); + if (edit) { + this.applyEditsToBuffer(edit, model); + } + } + + private applyEditsToBuffer(edit: Edit, model: ITextModel): void { + const startPosition = model.getPositionAt(edit.offset); + const endPosition = model.getPositionAt(edit.offset + edit.length); + const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column); + let currentText = model.getValueInRange(range); + if (edit.content !== currentText) { + const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content); + model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []); + } + } + + private getEdit(model: ITextModel, jsonPath: JSONPath, value: any): Edit | undefined { + const { tabSize, insertSpaces } = model.getOptions(); + const eol = model.getEOL(); + + // Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify + if (!jsonPath.length) { + const content = JSON.stringify(value, null, insertSpaces ? repeat(' ', tabSize) : '\t'); + return { + content, + length: model.getValue().length, + offset: 0 + }; + } + + return setProperty(model.getValue(), jsonPath, value, { tabSize, insertSpaces, eol })[0]; + } + + private async writeToRemote(userData: IUserData): Promise { + try { + await this.remoteUserDataService.write(SettingsSyncService.EXTERNAL_USER_DATA_SETTINGS_KEY, userData.version, userData.content); + } catch (e) { + if (e instanceof RemoteUserDataError && e.code === RemoteUserDataErrorCode.InvalidVersion) { + // Rejected as there is a new version. Sync again + return this.sync(); + } + // An unknown error + throw e; + } + } + + private async writeToLocal(newContent: string, oldContent: IFileContent | null, remoteUserData: IUserData): Promise { + if (oldContent) { + try { + // file exists before + await this.fileService.writeFile(this.workbenchEnvironmentService.settingsResource, VSBuffer.fromString(newContent), oldContent); + } catch (error) { + // Error to check for dirty to sync again + throw error; + } + } else { + try { + // file does not exist before + await this.fileService.createFile(this.workbenchEnvironmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false }); + } catch (error) { + if (error instanceof FileSystemProviderError && error.code === FileSystemProviderErrorCode.FileExists) { + return this.sync(); + } + throw error; + } + } + + this.storageService.store(SettingsSyncService.LAST_SYNC_SETTINGS_STORAGE_KEY, JSON.stringify(remoteUserData), StorageScope.GLOBAL); + } + +} diff --git a/src/vs/workbench/services/userData/common/userData.ts b/src/vs/workbench/services/userData/common/userData.ts index 9facb61c049b0535878c25928762bd49f856c15a..10ac85fdf93795d3d3510dd3b6f7578b5aaf3e32 100644 --- a/src/vs/workbench/services/userData/common/userData.ts +++ b/src/vs/workbench/services/userData/common/userData.ts @@ -4,89 +4,53 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; -import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { Schemas } from 'vs/base/common/network'; - -export interface IUserLoginProvider { - - readonly loggedIn: boolean; - - readonly onDidChange: Event; - - login(): Promise; - - logout(): Promise; +export interface IUserData { + version: number; + content: string; } -export interface IUserIdentity { - identity: string; - title: string; - iconText?: string; +export enum RemoteUserDataErrorCode { + InvalidVersion = 'InvalidVersion' } -export const IUserIdentityService = createDecorator('IUserIdentityService'); - -export interface IUserIdentityService { - - _serviceBrand: any; - - readonly onDidRegisterUserIdentities: Event; - - readonly onDidDeregisterUserIdentities: Event; - - readonly onDidRegisterUserLoginProvider: Event; - - readonly onDidDeregisterUserLoginProvider: Event; +export class RemoteUserDataError extends Error { - registerUserIdentities(userIdentities: IUserIdentity[]): void; + constructor(message: string, public readonly code: RemoteUserDataErrorCode) { + super(message); + } - deregisterUserIdentities(identities: string[]): void; - - registerUserLoginProvider(identity: string, userLoginProvider: IUserLoginProvider): void; - - deregisterUserLoginProvider(identity: string): void; - - getUserIndetities(): ReadonlyArray; - - getUserIdentity(identity: string): IUserIdentity | null; - - getUserLoginProvider(identity: string): IUserLoginProvider | null; } -export interface IUserDataProvider { +export interface IRemoteUserDataProvider { + + read(key: string): Promise; - userDataScheme: string; + write(key: string, version: number, content: string): Promise; } -export const IUserDataProviderService = createDecorator('IUserDataProviderService'); +export const IRemoteUserDataService = createDecorator('IRemoteUserDataService'); -export interface IUserDataProviderService { +export interface IRemoteUserDataService { - _serviceBrand: any; + _serviceBrand: undefined; - registerUserDataProvider(identity: string, userDataProvider: IUserDataProvider): void; + readonly onDidChangeEnablement: Event; - deregisterAll(): void; + isEnabled(): boolean; - getUserDataProvider(identity: string): IUserDataProvider | null; + registerRemoteUserDataProvider(name: string, remoteUserDataProvider: IRemoteUserDataProvider): void; -} + deregisterRemoteUserDataProvider(): void; -export const IUserDataSyncService = createDecorator('IUserDataSyncService'); + getName(): string | null; + read(key: string): Promise; -export const USER_DATA_SETTINGS_RESOURCE = URI.file('settings.json').with({ scheme: Schemas.userData }); -export const USER_DATA_KEYBINDINGS_RESOURCE = URI.file('keybindings.json').with({ scheme: Schemas.userData }); -export const USER_DATA_SNIPPETS_RESOURCE = URI.file('snippets').with({ scheme: Schemas.userData }); -export const USER_DATA_EXTENSIONS_RESOURCE = URI.file('extensions.json').with({ scheme: Schemas.userData }); + write(key: string, version: number, content: string): Promise; -export interface IUserDataExtension { - identifier: IExtensionIdentifier; - version?: string; } export enum SyncStatus { @@ -94,6 +58,8 @@ export enum SyncStatus { SyncDone } +export const IUserDataSyncService = createDecorator('IUserDataSyncService'); + export interface IUserDataSyncService { _serviceBrand: any; @@ -104,6 +70,4 @@ export interface IUserDataSyncService { synchronise(): Promise; - getExtensions(): Promise; - } diff --git a/src/vs/workbench/services/userData/common/userDataProviderService.ts b/src/vs/workbench/services/userData/common/userDataProviderService.ts deleted file mode 100644 index 330705482b8e0800f853ff69b5f788683c3af56e..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/userData/common/userDataProviderService.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IUserDataProviderService, IUserDataProvider } from 'vs/workbench/services/userData/common/userData'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Emitter, Event } from 'vs/base/common/event'; - -export class UserDataProviderService extends Disposable implements IUserDataProviderService { - - _serviceBrand: any; - - private readonly userDataProviders: Map = new Map(); - private activeUserDataProvider: IUserDataProvider | null = null; - - private readonly _ondDidChangeActiveUserDataProvider: Emitter = this._register(new Emitter()); - readonly ondDidChangeActiveUserDataProvider: Event = this._ondDidChangeActiveUserDataProvider.event; - - constructor() { - super(); - this._register(toDisposable(() => this.userDataProviders.clear())); - } - - registerUserDataProvider(identity: string, userDataProvider: IUserDataProvider): void { - this.userDataProviders.set(identity, userDataProvider); - this.activeUserDataProvider = userDataProvider; - this._ondDidChangeActiveUserDataProvider.fire(); - } - - deregisterAll(): void { - this.userDataProviders.clear(); - this.activeUserDataProvider = null; - this._ondDidChangeActiveUserDataProvider.fire(); - } - - getUserDataProvider(identity: string): IUserDataProvider | null { - return this.userDataProviders.get(identity) || null; - } - - getActiveUserDataProvider(): IUserDataProvider | null { - return this.activeUserDataProvider; - } - -} - -registerSingleton(IUserDataProviderService, UserDataProviderService); diff --git a/src/vs/workbench/services/userData/common/userIdentityService.ts b/src/vs/workbench/services/userData/common/userIdentityService.ts deleted file mode 100644 index 8de526bb91d4aede6eedc4b470067d812d0227a8..0000000000000000000000000000000000000000 --- a/src/vs/workbench/services/userData/common/userIdentityService.ts +++ /dev/null @@ -1,90 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IUserIdentityService, IUserIdentity, IUserLoginProvider } from 'vs/workbench/services/userData/common/userData'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { values } from 'vs/base/common/map'; -import { Emitter, Event } from 'vs/base/common/event'; - -export class UserIdentityService extends Disposable implements IUserIdentityService { - - _serviceBrand: any; - - private readonly userIdentities: Map = new Map(); - private readonly userLoginProviders: Map = new Map(); - - private readonly _onDidRegisterUserIdentities: Emitter = this._register(new Emitter()); - readonly onDidRegisterUserIdentities: Event = this._onDidRegisterUserIdentities.event; - - private readonly _onDidDeregisterUserIdentities: Emitter = this._register(new Emitter()); - readonly onDidDeregisterUserIdentities: Event = this._onDidDeregisterUserIdentities.event; - - private readonly _onDidRegisterUserLoginProvider: Emitter = this._register(new Emitter()); - readonly onDidRegisterUserLoginProvider: Event = this._onDidRegisterUserLoginProvider.event; - - private readonly _onDidDeregisterUserLoginProvider: Emitter = this._register(new Emitter()); - readonly onDidDeregisterUserLoginProvider: Event = this._onDidDeregisterUserLoginProvider.event; - - constructor() { - super(); - this._register(toDisposable(() => { - this.userIdentities.clear(); - this.userLoginProviders.clear(); - })); - } - - registerUserIdentities(userIdentities: IUserIdentity[]): void { - const registered: IUserIdentity[] = []; - for (const userIdentity of userIdentities) { - if (!this.userIdentities.has(userIdentity.identity)) { - this.userIdentities.set(userIdentity.identity, userIdentity); - registered.push(userIdentity); - } - } - this._onDidRegisterUserIdentities.fire(registered); - } - - deregisterUserIdentities(identities: string[]): void { - const deregistered: IUserIdentity[] = []; - for (const identity of identities) { - const userIdentity = this.userIdentities.get(identity); - if (userIdentity) { - this.userIdentities.delete(identity); - deregistered.push(userIdentity); - } - } - this._onDidDeregisterUserIdentities.fire(deregistered); - } - - registerUserLoginProvider(identity: string, userLoginProvider: IUserLoginProvider): void { - if (!this.userLoginProviders.has(identity)) { - this.userLoginProviders.set(identity, userLoginProvider); - this._onDidRegisterUserLoginProvider.fire(identity); - } - } - - deregisterUserLoginProvider(identity: string): void { - if (this.userLoginProviders.has(identity)) { - this.userLoginProviders.delete(identity); - this._onDidDeregisterUserLoginProvider.fire(identity); - } - } - - getUserIdentity(identity: string): IUserIdentity | null { - return this.userIdentities.get(identity) || null; - } - - getUserIndetities(): ReadonlyArray { - return values(this.userIdentities); - } - - getUserLoginProvider(identity: string): IUserLoginProvider | null { - return this.userLoginProviders.get(identity) || null; - } - -} - -registerSingleton(IUserIdentityService, UserIdentityService);