diff --git a/src/vs/platform/credentials/common/credentials.ts b/src/vs/platform/credentials/common/credentials.ts index 2799abeed19..06af1a01af5 100644 --- a/src/vs/platform/credentials/common/credentials.ts +++ b/src/vs/platform/credentials/common/credentials.ts @@ -5,15 +5,16 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const ICredentialsService = createDecorator('ICredentialsService'); - -export interface ICredentialsService { - - readonly _serviceBrand: undefined; - +export interface ICredentialsProvider { getPassword(service: string, account: string): Promise; setPassword(service: string, account: string, password: string): Promise; deletePassword(service: string, account: string): Promise; findPassword(service: string): Promise; findCredentials(service: string): Promise>; } + +export const ICredentialsService = createDecorator('ICredentialsService'); + +export interface ICredentialsService extends ICredentialsProvider { + readonly _serviceBrand: undefined; +} diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 3457eabe31e..7eb554b7734 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -53,6 +53,10 @@ function isSyncData(thing: any): thing is ISyncData { return false; } +function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService): URI { + return joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`); +} + export interface IResourcePreview { readonly remoteResource: URI; @@ -133,7 +137,7 @@ export abstract class AbstractSynchroniser extends Disposable { this.syncResourceLogLabel = uppercaseFirstLetter(this.resource); this.syncFolder = joinPath(environmentService.userDataSyncHome, resource); this.syncPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME); - this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`); + this.lastSyncResource = getLastSyncResourceUri(resource, environmentService); this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService); } @@ -796,3 +800,62 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni } } + +export abstract class AbstractInitializer { + + private readonly lastSyncResource: URI; + + constructor( + readonly resource: SyncResource, + @IEnvironmentService protected readonly environmentService: IEnvironmentService, + @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, + @IFileService protected readonly fileService: IFileService, + ) { + this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService); + } + + async initialize({ ref, content }: IUserData): Promise { + if (!content) { + this.logService.info('Remote content does not exist.', this.resource); + return; + } + + const syncData = this.parseSyncData(content); + if (!syncData) { + return; + } + + const isPreviouslySynced = await this.fileService.exists(this.lastSyncResource); + if (isPreviouslySynced) { + this.logService.info('Remote content does not exist.', this.resource); + return; + } + + try { + await this.doInitialize({ ref, syncData }); + } catch (error) { + this.logService.error(error); + } + } + + private parseSyncData(content: string): ISyncData | undefined { + try { + const syncData: ISyncData = JSON.parse(content); + if (isSyncData(syncData)) { + return syncData; + } + } catch (error) { + this.logService.error(error); + } + this.logService.info('Cannot parse sync data as it is not compatible with the current version.', this.resource); + return undefined; + } + + protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary = {}): Promise { + const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: lastSyncRemoteUserData.syncData ? JSON.stringify(lastSyncRemoteUserData.syncData) : null, ...additionalProps }; + await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); + } + + protected abstract doInitialize(remoteUserData: IRemoteUserData): Promise; + +} diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d0c714df48b..0fe85a4a9e8 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -15,7 +15,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge'; -import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources'; @@ -42,6 +42,34 @@ interface ILastSyncUserData extends IRemoteUserData { skippedExtensions: ISyncExtension[] | undefined; } +async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagementService: IExtensionManagementService): Promise { + const extensions = JSON.parse(syncData.content); + if (syncData.version === 1 + || syncData.version === 2 + ) { + const systemExtensions = await extensionManagementService.getInstalled(ExtensionType.System); + for (const extension of extensions) { + // #region Migration from v1 (enabled -> disabled) + if (syncData.version === 1) { + if ((extension).enabled === false) { + extension.disabled = true; + } + delete (extension).enabled; + } + // #endregion + + // #region Migration from v2 (set installed property on extension) + if (syncData.version === 2) { + if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) { + extension.installed = true; + } + } + // #endregion + } + } + return extensions; +} + export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` }); @@ -84,9 +112,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null; + const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; - const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null; + const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await parseAndMigrateExtensions(lastSyncUserData.syncData!, this.extensionManagementService) : null; const installedExtensions = await this.extensionManagementService.getInstalled(); const localExtensions = this.getLocalExtensions(installedExtensions); @@ -385,34 +413,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return newSkippedExtensions; } - private async parseAndMigrateExtensions(syncData: ISyncData): Promise { - const extensions = this.parseExtensions(syncData); - if (syncData.version === 1 - || syncData.version === 2 - ) { - const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System); - for (const extension of extensions) { - // #region Migration from v1 (enabled -> disabled) - if (syncData.version === 1) { - if ((extension).enabled === false) { - extension.disabled = true; - } - delete (extension).enabled; - } - // #endregion - - // #region Migration from v2 (set installed property on extension) - if (syncData.version === 2) { - if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) { - extension.installed = true; - } - } - // #endregion - } - } - return extensions; - } - private parseExtensions(syncData: ISyncData): ISyncExtension[] { return JSON.parse(syncData.content); } @@ -433,3 +433,68 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } } + +export class ExtensionsInitializer extends AbstractInitializer { + + constructor( + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + ) { + super(SyncResource.Extensions, environmentService, logService, fileService); + } + + async doInitialize(remoteUserData: IRemoteUserData): Promise { + const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null; + if (!remoteExtensions) { + this.logService.info('Skipping initializing extensions because remote extensions does not exist.'); + return; + } + + const installedExtensions = await this.extensionManagementService.getInstalled(); + const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] }; + const toDisable: IExtensionIdentifier[] = []; + for (const extension of remoteExtensions) { + if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) { + if (extension.disabled) { + toDisable.push(extension.identifier); + } + } else { + if (extension.installed) { + if (extension.identifier.uuid) { + toInstall.uuids.push(extension.identifier.uuid); + } else { + toInstall.names.push(extension.identifier.id); + } + } + } + } + + if (toInstall.names.length || toInstall.uuids.length) { + const galleryExtensions = (await this.galleryService.query({ ids: toInstall.uuids, names: toInstall.names, pageSize: toInstall.uuids.length + toInstall.names.length }, CancellationToken.None)).firstPage; + for (const galleryExtension of galleryExtensions) { + try { + this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); + await this.extensionManagementService.installFromGallery(galleryExtension); + this.logService.info(`Installed extension.`, galleryExtension.identifier.id); + } catch (error) { + this.logService.error(error); + } + } + } + + if (toDisable.length) { + for (const identifier of toDisable) { + this.logService.trace(`Enabling extension...`, identifier.id); + await this.extensionEnablementService.disableExtension(identifier); + this.logService.info(`Enabled extension`, identifier.id); + } + } + } + +} + + diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 5929719d952..09a746baf50 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -16,7 +16,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { edit } from 'vs/platform/userDataSync/common/content'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { parse } from 'vs/base/common/json'; -import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; @@ -341,3 +341,55 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => ({ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))]; } } + +export class GlobalStateInitializer extends AbstractInitializer { + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + ) { + super(SyncResource.GlobalState, environmentService, logService, fileService); + } + + async doInitialize(remoteUserData: IRemoteUserData): Promise { + const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; + if (!remoteGlobalState) { + this.logService.info('Skipping initializing global state because remote global state does not exist.'); + return; + } + + const argv: IStringDictionary = {}; + const storage: IStringDictionary = {}; + for (const key of Object.keys(remoteGlobalState.storage)) { + if (key.startsWith(argvStoragePrefx)) { + argv[key.substring(argvStoragePrefx.length)] = remoteGlobalState.storage[key].value; + } else { + if (this.storageService.get(key, StorageScope.GLOBAL) === undefined) { + storage[key] = remoteGlobalState.storage[key].value; + } + } + } + + if (Object.keys(argv).length) { + let content = '{}'; + try { + const fileContent = await this.fileService.readFile(this.environmentService.argvResource); + content = fileContent.value.toString(); + } catch (error) { } + for (const argvProperty of Object.keys(argv)) { + content = edit(content, [argvProperty], argv[argvProperty], {}); + } + await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content)); + } + + if (Object.keys(storage).length) { + for (const key of Object.keys(storage)) { + this.storageService.store(key, storage[key].value, StorageScope.GLOBAL); + } + } + } + +} + diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 696be2c843b..412447685d1 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -18,11 +18,12 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { VSBuffer } from 'vs/base/common/buffer'; interface ISyncContent { mac?: string; @@ -35,6 +36,21 @@ interface IKeybindingsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; } +export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean): string | null { + const parsed = JSON.parse(syncContent); + if (platformSpecific) { + return isUndefined(parsed.all) ? null : parsed.all; + } + switch (OS) { + case OperatingSystem.Macintosh: + return isUndefined(parsed.mac) ? null : parsed.mac; + case OperatingSystem.Linux: + return isUndefined(parsed.linux) ? null : parsed.linux; + case OperatingSystem.Windows: + return isUndefined(parsed.windows) ? null : parsed.windows; + } +} + export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { /* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */ @@ -266,20 +282,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem return null; } - getKeybindingsContentFromSyncContent(syncContent: string): string | null { + private getKeybindingsContentFromSyncContent(syncContent: string): string | null { try { - const parsed = JSON.parse(syncContent); - if (!this.syncKeybindingsPerPlatform()) { - return isUndefined(parsed.all) ? null : parsed.all; - } - switch (OS) { - case OperatingSystem.Macintosh: - return isUndefined(parsed.mac) ? null : parsed.mac; - case OperatingSystem.Linux: - return isUndefined(parsed.linux) ? null : parsed.linux; - case OperatingSystem.Windows: - return isUndefined(parsed.windows) ? null : parsed.windows; - } + return getKeybindingsContentFromSyncContent(syncContent, this.syncKeybindingsPerPlatform()); } catch (e) { this.logService.error(e); return null; @@ -325,3 +330,52 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem } } + +export class KeybindingsInitializer extends AbstractInitializer { + + constructor( + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + ) { + super(SyncResource.Keybindings, environmentService, logService, fileService); + } + + async doInitialize(remoteUserData: IRemoteUserData): Promise { + const keybindingsContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; + if (!keybindingsContent) { + this.logService.info('Skipping initializing keybindings because remote keybindings does not exist.'); + return; + } + + const isEmpty = await this.isEmpty(); + if (!isEmpty) { + this.logService.info('Skipping initializing keybindings because local keybindings exist.'); + return; + } + + await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(keybindingsContent)); + + await this.updateLastSyncUserData(remoteUserData); + } + + private async isEmpty(): Promise { + try { + const fileContent = await this.fileService.readFile(this.environmentService.settingsResource); + const keybindings = parse(fileContent.value.toString()); + return !isNonEmptyArray(keybindings); + } catch (error) { + return (error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND; + } + } + + private getKeybindingsContentFromSyncContent(syncContent: string): string | null { + try { + return getKeybindingsContentFromSyncContent(syncContent, true); + } catch (e) { + this.logService.error(e); + return null; + } + } + +} diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index ad6800c6cbb..f4333ac84aa 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -275,8 +275,11 @@ export function areSame(localContent: string, remoteContent: string, ignoredSett } export function isEmpty(content: string): boolean { - const nodes = parseSettings(content); - return nodes.length === 0; + if (content) { + const nodes = parseSettings(content); + return nodes.length === 0; + } + return true; } function compare(from: IStringDictionary | null, to: IStringDictionary, ignored: Set): { added: Set, removed: Set, updated: Set } { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index cbba5c25c3d..f1734e5e838 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -17,7 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge'; import { edit } from 'vs/platform/userDataSync/common/content'; -import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -40,6 +40,11 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent { && Object.keys(thing).length === 1; } +export function parseSettingsSyncContent(syncContent: string): ISettingsSyncContent { + const parsed = JSON.parse(syncContent); + return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent }; +} + export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { /* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */ @@ -281,10 +286,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null; } - parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null { + private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null { try { - const parsed = JSON.parse(syncContent); - return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent }; + return parseSettingsSyncContent(syncContent); } catch (e) { this.logService.error(e); } @@ -350,6 +354,54 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } } +export class SettingsInitializer extends AbstractInitializer { + + constructor( + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + ) { + super(SyncResource.Settings, environmentService, logService, fileService); + } + + async doInitialize(remoteUserData: IRemoteUserData): Promise { + const settingsSyncContent = remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null; + if (!settingsSyncContent) { + this.logService.info('Skipping initializing settings because remote settings does not exist.'); + return; + } + + const isEmpty = await this.isEmpty(); + if (!isEmpty) { + this.logService.info('Skipping initializing settings because local settings exist.'); + return; + } + + await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(settingsSyncContent.settings)); + + await this.updateLastSyncUserData(remoteUserData); + } + + private async isEmpty(): Promise { + try { + const fileContent = await this.fileService.readFile(this.environmentService.settingsResource); + return isEmpty(fileContent.value.toString().trim()); + } catch (error) { + return (error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND; + } + } + + private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null { + try { + return parseSettingsSyncContent(syncContent); + } catch (e) { + this.logService.error(e); + } + return null; + } + +} + function isSyncData(thing: any): thing is ISyncData { if (thing && (thing.version !== undefined && typeof thing.version === 'number') diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index f00332c395a..c5b1029bd14 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -10,7 +10,7 @@ import { import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStringDictionary } from 'vs/base/common/collections'; import { URI } from 'vs/base/common/uri'; @@ -499,3 +499,49 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD return snippets; } } + +export class SnippetsInitializer extends AbstractInitializer { + + constructor( + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + ) { + super(SyncResource.Snippets, environmentService, logService, fileService); + } + + async doInitialize(remoteUserData: IRemoteUserData): Promise { + const remoteSnippets: IStringDictionary | null = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; + if (!remoteSnippets) { + this.logService.info('Skipping initializing snippets because remote snippets does not exist.'); + return; + } + + const isEmpty = await this.isEmpty(); + if (!isEmpty) { + this.logService.info('Skipping initializing snippets because local snippets exist.'); + return; + } + + for (const key of Object.keys(remoteSnippets)) { + const content = remoteSnippets[key]; + if (content) { + const resource = joinPath(this.environmentService.snippetsHome, key); + await this.fileService.createFile(resource, VSBuffer.fromString(content)); + this.logService.info('Created snippet', basename(resource)); + } + } + + await this.updateLastSyncUserData(remoteUserData); + } + + private async isEmpty(): Promise { + try { + const stat = await this.fileService.resolve(this.environmentService.snippetsHome); + return !stat.children?.length; + } catch (error) { + return (error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND; + } + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index dbc24ef949e..a870296eeb9 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -164,10 +164,7 @@ export interface IUserDataSyncStoreManagementService { getPreviousUserDataSyncStore(): Promise; } -export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); -export interface IUserDataSyncStoreService { - readonly _serviceBrand: undefined; - +export interface IUserDataSyncStoreClient { readonly onDidChangeDonotMakeRequestsUntil: Event; readonly donotMakeRequestsUntil: Date | undefined; @@ -186,6 +183,11 @@ export interface IUserDataSyncStoreService { resolveContent(resource: ServerResource, ref: string): Promise; } +export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); +export interface IUserDataSyncStoreService extends IUserDataSyncStoreClient { + readonly _serviceBrand: undefined; +} + export const IUserDataSyncBackupStoreService = createDecorator('IUserDataSyncBackupStoreService'); export interface IUserDataSyncBackupStoreService { readonly _serviceBrand: undefined; diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index d17ec3b39fc..3e4cbc6d664 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess as isSuccessContext, asJson } from 'vs/platform/request/common/request'; import { joinPath, relativePath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -125,9 +125,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor } } -export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { - - _serviceBrand: any; +export class UserDataSyncStoreClient extends Disposable implements IUserDataSyncStoreClient { private readonly userDataSyncStoreUrl: URI | undefined; @@ -147,16 +145,16 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event; constructor( + userDataSyncStoreUrl: URI | undefined, @IProductService productService: IProductService, @IRequestService private readonly requestService: IRequestService, - @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @IStorageService private readonly storageService: IStorageService, ) { super(); - this.userDataSyncStoreUrl = this.userDataSyncStoreManagementService.userDataSyncStore ? joinPath(this.userDataSyncStoreManagementService.userDataSyncStore.url, 'v1') : undefined; + this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined; this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService) .then(uuid => { const headers: IHeaders = { @@ -444,6 +442,23 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } +export class UserDataSyncStoreService extends UserDataSyncStoreClient implements IUserDataSyncStoreService { + + _serviceBrand: any; + + constructor( + @IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, + @IProductService productService: IProductService, + @IRequestService requestService: IRequestService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, + ) { + super(userDataSyncStoreManagementService.userDataSyncStore?.url, productService, requestService, logService, environmentService, fileService, storageService); + } +} + export class RequestsSession { private requests: string[] = []; diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index d1a6ca7516b..9007dcdb544 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -10,7 +10,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { IFileService } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; +import { getKeybindingsContentFromSyncContent, KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { VSBuffer } from 'vs/base/common/buffer'; suite('KeybindingsSync', () => { @@ -70,8 +70,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]'); - assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), '[]'); + assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]'); + assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), '[]'); assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), ''); }); @@ -95,8 +95,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content); - assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content); + assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); + assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content); }); @@ -110,8 +110,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), expectedContent); - assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), expectedContent); + assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), expectedContent); + assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), expectedContent); assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedContent); }); @@ -135,8 +135,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content); - assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content); + assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); + assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content); }); @@ -159,8 +159,8 @@ suite('KeybindingsSync', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content); - assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content); + assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content); + assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content); assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedLocalContent); }); @@ -183,7 +183,7 @@ suite('KeybindingsSync', () => { const remoteUserData = await testObject.getRemoteUserData(null); assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]'); + assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]'); }); test('test apply remote when keybindings file does not exist', async () => { diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 6117c2b55e2..e83d8a6aa73 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, ISyncData, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync'; +import { SettingsSynchroniser, ISettingsSyncContent, parseSettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { IFileService } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -88,8 +88,8 @@ suite('SettingsSync - Auto', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); - assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}'); + assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); + assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}'); assert.equal((await fileService.readFile(settingsResource)).value.toString(), ''); }); @@ -129,8 +129,8 @@ suite('SettingsSync - Auto', () => { const lastSyncUserData = await testObject.getLastSyncUserData(); const remoteUserData = await testObject.getRemoteUserData(null); - assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content); - assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content); + assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content); + assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content); assert.equal((await fileService.readFile(settingsResource)).value.toString(), content); }); @@ -154,7 +154,7 @@ suite('SettingsSync - Auto', () => { const remoteUserData = await testObject.getRemoteUserData(null); assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref); assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData); - assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); + assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}'); }); test('sync for first time to the server', async () => { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index ff18883e95e..ea841289048 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -28,12 +28,13 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Codicon } from 'vs/base/common/codicons'; import { isMacintosh } from 'vs/base/common/platform'; -import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { getAuthenticationSession, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { AuthenticationSession } from 'vs/editor/common/modes'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProductService } from 'vs/platform/product/common/productService'; export class ViewContainerActivityAction extends ActivityAction { @@ -125,7 +126,8 @@ export class AccountsActionViewItem extends ActivityActionViewItem { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService, ) { super(action, { draggable: false, colors, icon: true }, themeService); } @@ -178,10 +180,11 @@ export class AccountsActionViewItem extends ActivityActionViewItem { const result = await Promise.all(allSessions); let menus: IAction[] = []; + const authenticationSession = this.environmentService.options?.credentialsProvider ? await getAuthenticationSession(this.environmentService.options?.credentialsProvider, this.productService) : undefined; result.forEach(sessionInfo => { const providerDisplayName = this.authenticationService.getLabel(sessionInfo.providerId); Object.keys(sessionInfo.sessions).forEach(accountName => { - const hasEmbedderAccountSession = sessionInfo.sessions[accountName].some(session => session.id === this.environmentService.options?.authenticationSessionId); + const hasEmbedderAccountSession = sessionInfo.sessions[accountName].some(session => session.id === (authenticationSession?.id || this.environmentService.options?.authenticationSessionId)); const manageExtensionsAction = new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, _ => { return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName); }); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 0462617196b..3ee782d4e1b 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -50,6 +50,9 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IndexedDB, INDEXEDDB_LOGS_OBJECT_STORE, INDEXEDDB_USERDATA_OBJECT_STORE } from 'vs/platform/files/browser/indexedDBFileSystemProvider'; +import { BrowserRequestService } from 'vs/workbench/services/request/browser/requestService'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { initializeUserData } from 'vs/workbench/services/userData/browser/userDataInit'; class BrowserMain extends Disposable { @@ -180,7 +183,7 @@ class BrowserMain extends Disposable { await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath); // Long running services (workspace, config, storage) - const services = await Promise.all([ + const [configurationService, storageService] = await Promise.all([ this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(service => { // Workspace @@ -201,7 +204,14 @@ class BrowserMain extends Disposable { }) ]); - return { serviceCollection, logService, storageService: services[1] }; + // Request Service + const requestService = new BrowserRequestService(remoteAgentService, configurationService, logService); + serviceCollection.set(IRequestService, requestService); + + // initialize user data + await initializeUserData(environmentService, fileService, storageService, productService, requestService, logService); + + return { serviceCollection, logService, storageService }; } private async registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, logService: BufferLogService, logsPath: URI): Promise { diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 9741fd8fbcf..80de127f448 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -16,9 +16,27 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ICredentialsProvider } from 'vs/platform/credentials/common/credentials'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { isString } from 'vs/base/common/types'; export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; } +export async function getAuthenticationSession(credentialsProvider: ICredentialsProvider, productService: IProductService): Promise<{ id: string, accessToken: string, providerId: string } | undefined> { + const authenticationSessionValue = await credentialsProvider.getPassword(`${productService.urlProtocol}.login`, 'account'); + if (authenticationSessionValue) { + const authenticationSession: { id: string, accessToken: string, providerId: string } = JSON.parse(authenticationSessionValue); + if (authenticationSession + && isString(authenticationSession.id) + && isString(authenticationSession.accessToken) + && isString(authenticationSession.providerId) + ) { + return authenticationSession; + } + } + return undefined; +} + export const IAuthenticationService = createDecorator('IAuthenticationService'); export interface IAuthenticationService { diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts index 5d64ecf1d2f..c95b04b5ad1 100644 --- a/src/vs/workbench/services/credentials/browser/credentialsService.ts +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -3,21 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { ICredentialsProvider, ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { find } from 'vs/base/common/arrays'; -export interface ICredentialsProvider { - getPassword(service: string, account: string): Promise; - setPassword(service: string, account: string, password: string): Promise; - - deletePassword(service: string, account: string): Promise; - - findPassword(service: string): Promise; - findCredentials(service: string): Promise>; -} - export class BrowserCredentialsService implements ICredentialsService { declare readonly _serviceBrand: undefined; diff --git a/src/vs/workbench/services/request/browser/requestService.ts b/src/vs/workbench/services/request/browser/requestService.ts index b9315a52ea2..16aa984a3f3 100644 --- a/src/vs/workbench/services/request/browser/requestService.ts +++ b/src/vs/workbench/services/request/browser/requestService.ts @@ -10,8 +10,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { RequestChannelClient } from 'vs/platform/request/common/requestIpc'; import { IRemoteAgentService, IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RequestService } from 'vs/platform/request/browser/requestService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IRequestService } from 'vs/platform/request/common/request'; export class BrowserRequestService extends RequestService { @@ -44,5 +42,3 @@ export class BrowserRequestService extends RequestService { return connection.withChannel('request', channel => RequestChannelClient.request(channel, options, token)); } } - -registerSingleton(IRequestService, BrowserRequestService, true); diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts new file mode 100644 index 00000000000..3fb353a0cc4 --- /dev/null +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { AbstractInitializer } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { ExtensionsInitializer } from 'vs/platform/userDataSync/common/extensionsSync'; +import { GlobalStateInitializer } from 'vs/platform/userDataSync/common/globalStateSync'; +import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync'; +import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync'; +import { SnippetsInitializer } from 'vs/platform/userDataSync/common/snippetsSync'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { CONFIGURATION_SYNC_STORE_KEY } from 'vs/platform/userDataSync/common/userDataSync'; +import { URI } from 'vs/base/common/uri'; +import { getAuthenticationSession } from 'vs/workbench/services/authentication/browser/authenticationService'; + +export function initializeUserData( + environmentService: IWorkbenchEnvironmentService, + fileService: IFileService, + storageService: IStorageService, + productService: IProductService, + requestService: IRequestService, + logService: ILogService, +): Promise { + const initializers: AbstractInitializer[] = [ + new SettingsInitializer(fileService, environmentService, logService), + new KeybindingsInitializer(fileService, environmentService, logService), + new SnippetsInitializer(fileService, environmentService, logService), + new GlobalStateInitializer(storageService, fileService, environmentService, logService), + ]; + return initialize(initializers, 'user data', environmentService, fileService, storageService, productService, requestService, logService); +} + +export function initializeExtensions(instantiationService: IInstantiationService): Promise { + return instantiationService.invokeFunction(accessor => { + return initialize([instantiationService.createInstance(ExtensionsInitializer)], 'extensions', + accessor.get(IWorkbenchEnvironmentService), + accessor.get(IFileService), + accessor.get(IStorageService), + accessor.get(IProductService), + accessor.get(IRequestService), + accessor.get(ILogService)); + }); +} + +async function initialize( + initializers: AbstractInitializer[], + userDataLabel: string, + environmentService: IWorkbenchEnvironmentService, + fileService: IFileService, + storageService: IStorageService, + productService: IProductService, + requestService: IRequestService, + logService: ILogService, +): Promise { + + if (!environmentService.options?.enableSyncByDefault) { + logService.trace(`Skipping initializing ${userDataLabel} as sync is not enabled by default`); + return; + } + + if (!storageService.isNew(StorageScope.WORKSPACE)) { + logService.trace(`Skipping initializing ${userDataLabel} as workspace was opened before`); + return; + } + + const userDataSyncStore = productService[CONFIGURATION_SYNC_STORE_KEY]; + if (!userDataSyncStore) { + logService.trace(`Skipping initializing ${userDataLabel} as sync service is not provided`); + return; + } + + if (!environmentService.options?.credentialsProvider) { + return; + } + + let authenticationSession; + try { + authenticationSession = await getAuthenticationSession(environmentService.options.credentialsProvider, productService); + } catch (error) { + logService.error(error); + } + if (!authenticationSession) { + logService.trace(`Skipping initializing ${userDataLabel} as authentication session is not set`); + return; + } + + const userDataSyncStoreClient = new UserDataSyncStoreClient(URI.parse(userDataSyncStore.url), productService, requestService, logService, environmentService, fileService, storageService); + try { + userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); + logService.info(`Started initializing ${userDataLabel}`); + for (const initializer of initializers) { + try { + const userData = await userDataSyncStoreClient.read(initializer.resource, null); + await initializer.initialize(userData); + } catch (error) { + logService.info(`Error while initializing ${initializer.resource}`); + logService.error(error); + } + } + logService.info(`Initializing ${userDataLabel} completed`); + } finally { + userDataSyncStoreClient.dispose(); + } +} diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index bdac6f7a6c6..d5d2504a3e0 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -11,7 +11,7 @@ import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/edi import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { flatten, equals } from 'vs/base/common/arrays'; -import { getAuthenticationProviderActivationEvent, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { getAuthenticationProviderActivationEvent, getAuthenticationSession, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; @@ -153,8 +153,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async initialize(): Promise { - if (this.currentSessionId === undefined && this.useWorkbenchSessionId && this.environmentService.options?.authenticationSessionId) { - this.currentSessionId = this.environmentService.options.authenticationSessionId; + const authenticationSession = this.environmentService.options?.credentialsProvider ? await getAuthenticationSession(this.environmentService.options?.credentialsProvider, this.productService) : undefined; + if (this.currentSessionId === undefined && this.useWorkbenchSessionId && (authenticationSession?.id || this.environmentService.options?.authenticationSessionId)) { + this.currentSessionId = authenticationSession?.id || this.environmentService.options?.authenticationSessionId; this.useWorkbenchSessionId = false; } diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index ce168ccdd0a..2bd2d89baea 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -8,7 +8,6 @@ import { main } from 'vs/workbench/browser/web.main'; import { UriComponents, URI } from 'vs/base/common/uri'; import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, FileChangeType } from 'vs/platform/files/common/files'; import { IWebSocketFactory, IWebSocket } from 'vs/platform/remote/browser/browserSocketFactory'; -import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; import { LogLevel } from 'vs/platform/log/common/log'; @@ -19,6 +18,7 @@ import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/brows import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IProductConfiguration } from 'vs/platform/product/common/productService'; import { mark } from 'vs/base/common/performance'; +import { ICredentialsProvider } from 'vs/platform/credentials/common/credentials'; interface IResourceUriProvider { (uri: URI): URI; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 0669178db4c..b956a2b5918 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -46,7 +46,6 @@ import 'vs/workbench/services/workspaces/browser/workspaceEditingService'; import 'vs/workbench/services/dialogs/browser/dialogService'; import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; -import 'vs/workbench/services/request/browser/requestService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService';