From 1f056c2abef663ca4d69b4222916fe219bf81a29 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Oct 2022 21:55:56 +0200 Subject: [PATCH] refactor: - extract local data reading and writing into a provider for reuse. --- .../common/abstractSynchronizer.ts | 8 +- .../userDataSync/common/extensionsSync.ts | 171 +++++++++--------- .../userDataSync/common/globalStateSync.ts | 119 ++++++------ .../common/userDataSyncService.ts | 2 +- 4 files changed, 163 insertions(+), 137 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index d7cdbaaa39c6c..1f03fd4edb9b9 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -27,7 +27,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, ISyncResourceHandle, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataInitializer, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getPathSegments, IUserDataSyncResourceConflicts, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; type IncompatibleSyncSourceClassification = { owner: 'sandy081'; @@ -54,6 +54,10 @@ export function isSyncData(thing: any): thing is ISyncData { return false; } +export function getSyncResourceLogLabel(syncResource: SyncResource, profile: IUserDataProfile): string { + return `${uppercaseFirstLetter(syncResource)}${profile.isDefault ? '' : ` (${profile.name})`}`; +} + export interface IResourcePreview { readonly baseResource: URI; @@ -140,7 +144,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa @IUriIdentityService uriIdentityService: IUriIdentityService, ) { super(); - this.syncResourceLogLabel = `${uppercaseFirstLetter(this.resource)}${syncResource.profile.isDefault ? '' : ` (${syncResource.profile.name})`}`; + this.syncResourceLogLabel = getSyncResourceLogLabel(syncResource.syncResource, syncResource.profile); this.extUri = uriIdentityService.extUri; this.syncFolder = this.extUri.joinPath(environmentService.userDataSyncHome, ...getPathSegments(syncResource.profile.isDefault ? undefined : syncResource.profile.id, syncResource.syncResource)); this.syncPreviewFolder = this.extUri.joinPath(this.syncFolder, PREVIEW_DIR_NAME); diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 118b998774a6c..ff63fd4b7cab8 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -27,7 +27,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { IMergeResult as IExtensionMergeResult, merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { Change, IRemoteUserData, ISyncData, ISyncExtension, ISyncExtensionWithVersion, ISyncResourceHandle, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; @@ -94,11 +94,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); - // profileLocation changes for default profile - profileLocation: URI | undefined; + private readonly localExtensionsProvider: LocalExtensionsProvider; constructor( - profile: IUserDataProfile, + // profileLocation changes for default profile + public profile: IUserDataProfile, collection: string | undefined, @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @@ -108,17 +108,16 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IExtensionStorageService extensionStorageService: IExtensionStorageService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, + @IUserDataSyncProfilesStorageService userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super({ syncResource: SyncResource.Extensions, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); - this.profileLocation = profile.extensionsResource; + this.localExtensionsProvider = this.instantiationService.createInstance(LocalExtensionsProvider); this._register( Event.any( Event.filter(this.extensionManagementService.onDidInstallExtensions, (e => e.some(({ local }) => !!local))), @@ -133,9 +132,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const builtinExtensions: IExtensionIdentifier[] = lastSyncUserData?.builtinExtensions || []; const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData?.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null; - const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation); - const localExtensions = await this.getLocalExtensions(installedExtensions); - const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); + const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.profile); if (remoteExtensions) { this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`); @@ -173,9 +170,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse protected async hasRemoteChanged(lastSyncUserData: ILastSyncUserData): Promise { const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null; - const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation); - const localExtensions = await this.getLocalExtensions(installedExtensions); - const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); + const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.profile); const { remote } = merge(localExtensions, lastSyncExtensions, lastSyncExtensions, lastSyncUserData.skippedExtensions || [], ignoredExtensions, lastSyncUserData.builtinExtensions || []); return remote !== null; } @@ -229,7 +224,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise { - const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation); + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profile.location); const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions, resourcePreview.builtinExtensions); const { local, remote } = mergeResult; @@ -243,7 +238,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise { - const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation); + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profile.location); const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; if (remoteExtensions !== null) { @@ -277,7 +272,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (localChange !== Change.None) { await this.backupLocal(JSON.stringify(localExtensions)); - skippedExtensions = await this.updateLocalExtensions(local.added, local.removed, local.updated, skippedExtensions); + skippedExtensions = await this.localExtensionsProvider.updateLocalExtensions(local.added, local.removed, local.updated, skippedExtensions, this.profile); } if (remote) { @@ -303,10 +298,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { - const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation); - const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); - const localExtensions = (await this.getLocalExtensions(installedExtensions)).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier))); - return this.stringify(localExtensions, true); + const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.profile); + return this.stringify(localExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier))), true); } if (this.extUri.isEqual(this.remoteResource, uri) @@ -352,8 +345,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async hasLocalData(): Promise { try { - const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation); - const localExtensions = await this.getLocalExtensions(installedExtensions); + const { localExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.profile); if (localExtensions.some(e => e.installed || e.disabled)) { return true; } @@ -363,18 +355,71 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return false; } - private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise { - return this.withProfileScopedServices(async (extensionEnablementService, extensionStorageService) => { + private parseExtensions(syncData: ISyncData): ISyncExtension[] { + return JSON.parse(syncData.content); + } + +} + +export class LocalExtensionsProvider { + + constructor( + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { } + + async getLocalExtensions(profile: IUserDataProfile): Promise<{ localExtensions: ISyncExtensionWithVersion[]; ignoredExtensions: string[] }> { + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource); + const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions); + const localExtensions = await this.withProfileScopedServices(profile, async (extensionEnablementService, extensionStorageService) => { + const disabledExtensions = extensionEnablementService.getDisabledExtensions(); + return installedExtensions + .map(extension => { + const { identifier, isBuiltin, manifest, preRelease } = extension; + const syncExntesion: ISyncExtensionWithVersion = { identifier, preRelease, version: manifest.version }; + if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { + syncExntesion.disabled = true; + } + if (!isBuiltin) { + syncExntesion.installed = true; + } + try { + const keys = extensionStorageService.getKeysForSync({ id: identifier.id, version: manifest.version }); + if (keys) { + const extensionStorageState = extensionStorageService.getExtensionState(extension, true) || {}; + syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary, key) => { + if (keys.includes(key)) { + state[key] = extensionStorageState[key]; + } + return state; + }, {}); + } + } catch (error) { + this.logService.info(`${getSyncResourceLogLabel(SyncResource.Extensions, profile)}: Error while parsing extension state`, getErrorMessage(error)); + } + return syncExntesion; + }); + }); + return { localExtensions, ignoredExtensions }; + } + + async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[], profile: IUserDataProfile): Promise { + const syncResourceLogLabel = getSyncResourceLogLabel(SyncResource.Extensions, profile); + return this.withProfileScopedServices(profile, async (extensionEnablementService, extensionStorageService) => { const removeFromSkipped: IExtensionIdentifier[] = []; const addToSkipped: ISyncExtension[] = []; - const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profileLocation); + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource); if (removed.length) { const extensionsToRemove = installedExtensions.filter(({ identifier, isBuiltin }) => !isBuiltin && removed.some(r => areSameExtensions(identifier, r))); await Promises.settled(extensionsToRemove.map(async extensionToRemove => { - this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id); - await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true, profileLocation: this.profileLocation }); - this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id); + this.logService.trace(`${syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id); + await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true, profileLocation: profile.extensionsResource }); + this.logService.info(`${syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id); removeFromSkipped.push(extensionToRemove.identifier); })); } @@ -391,13 +436,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const isDisabled = extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); if (isDisabled !== !!e.disabled) { if (e.disabled) { - this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id); + this.logService.trace(`${syncResourceLogLabel}: Disabling extension...`, e.identifier.id); await extensionEnablementService.disableExtension(e.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id); + this.logService.info(`${syncResourceLogLabel}: Disabled extension`, e.identifier.id); } else { - this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id); + this.logService.trace(`${syncResourceLogLabel}: Enabling extension...`, e.identifier.id); await extensionEnablementService.enableExtension(e.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id); + this.logService.info(`${syncResourceLogLabel}: Enabled extension`, e.identifier.id); } } removeFromSkipped.push(e.identifier); @@ -423,13 +468,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const isDisabled = extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); if (isDisabled !== !!e.disabled) { if (e.disabled) { - this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version); + this.logService.trace(`${syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version); await extensionEnablementService.disableExtension(extension.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version); + this.logService.info(`${syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version); } else { - this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version); + this.logService.trace(`${syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version); await extensionEnablementService.enableExtension(extension.identifier); - this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version); + this.logService.info(`${syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version); } } @@ -437,27 +482,27 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse || installedExtension.preRelease !== e.preRelease // Install if the extension pre-release preference has changed ) { if (await this.extensionManagementService.canInstall(extension)) { - this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease, profileLocation: this.profileLocation } /* set isMachineScoped value to prevent install and sync dialog in web */); - this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version); + this.logService.trace(`${syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version); + await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease, profileLocation: profile.extensionsResource } /* set isMachineScoped value to prevent install and sync dialog in web */); + this.logService.info(`${syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version); removeFromSkipped.push(extension.identifier); } else { - this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because it cannot be installed.`, extension.displayName || extension.identifier.id); + this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because it cannot be installed.`, extension.displayName || extension.identifier.id); addToSkipped.push(e); } } } catch (error) { addToSkipped.push(e); if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatiblePreRelease, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) { - this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, extension.displayName || extension.identifier.id); + this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, extension.displayName || extension.identifier.id); } else { this.logService.error(error); - this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id); + this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id); } } } else { addToSkipped.push(e); - this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because the extension is not found.`, e.identifier.id); + this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because the extension is not found.`, e.identifier.id); } })); } @@ -488,44 +533,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse extensionStorageService.setExtensionState(extension, extensionState, true); } - private parseExtensions(syncData: ISyncData): ISyncExtension[] { - return JSON.parse(syncData.content); - } - - private getLocalExtensions(installedExtensions: ILocalExtension[]): Promise { - return this.withProfileScopedServices(async (extensionEnablementService, extensionStorageService) => { - const disabledExtensions = extensionEnablementService.getDisabledExtensions(); - return installedExtensions - .map(extension => { - const { identifier, isBuiltin, manifest, preRelease } = extension; - const syncExntesion: ISyncExtensionWithVersion = { identifier, preRelease, version: manifest.version }; - if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { - syncExntesion.disabled = true; - } - if (!isBuiltin) { - syncExntesion.installed = true; - } - try { - const keys = extensionStorageService.getKeysForSync({ id: identifier.id, version: manifest.version }); - if (keys) { - const extensionStorageState = extensionStorageService.getExtensionState(extension, true) || {}; - syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary, key) => { - if (keys.includes(key)) { - state[key] = extensionStorageState[key]; - } - return state; - }, {}); - } - } catch (error) { - this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error)); - } - return syncExntesion; - }); - }); - } - - private async withProfileScopedServices(fn: (extensionEnablementService: IGlobalExtensionEnablementService, extensionStorageService: IExtensionStorageService) => Promise): Promise { - return this.userDataSyncProfilesStorageService.withProfileScopedStorageService(this.syncResource.profile, + private async withProfileScopedServices(profile: IUserDataProfile, fn: (extensionEnablementService: IGlobalExtensionEnablementService, extensionStorageService: IExtensionStorageService) => Promise): Promise { + return this.userDataSyncProfilesStorageService.withProfileScopedStorageService(profile, async storageService => { const disposables = new DisposableStore(); const instantiationService = this.instantiationService.createChild(new ServiceCollection([IStorageService, storageService])); diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index eabf532605146..d6a09a80247e9 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -22,13 +22,14 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, isSyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAcceptResult, IMergeResult, IResourcePreview, isSyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { edit } from 'vs/platform/userDataSync/common/content'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, ISyncResourceHandle, IUserData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, SYNC_SERVICE_URL_TYPE, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreType, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const argvStoragePrefx = 'globalState.argv.'; const argvProperties: string[] = ['locale']; @@ -75,6 +76,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); + private readonly localGlobalStateProvider: LocalGlobalStateProvider; + constructor( profile: IUserDataProfile, collection: string | undefined, @@ -89,14 +92,16 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @IUriIdentityService uriIdentityService: IUriIdentityService, + @IInstantiationService instantiationService: IInstantiationService, ) { super({ syncResource: SyncResource.GlobalState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + this.localGlobalStateProvider = instantiationService.createInstance(LocalGlobalStateProvider); this._register(fileService.watch(this.extUri.dirname(this.environmentService.argvResource))); this._register( Event.any( /* Locale change */ Event.filter(fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)), - Event.filter(this.userDataSyncProfilesStorageService.onDidChange, e => { + Event.filter(userDataSyncProfilesStorageService.onDidChange, e => { /* StorageTarget has changed in profile storage */ if (e.targetChanges.some(profile => this.syncResource.profile.id === profile.id)) { return true; @@ -118,7 +123,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs lastSyncUserData = lastSyncUserData === null && isRemoteDataFromCurrentMachine ? remoteUserData : lastSyncUserData; const lastSyncGlobalState: IGlobalState | null = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; - const localGlobalState = await this.getLocalGlobalState(); + const localGlobalState = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile); if (remoteGlobalState) { this.logService.trace(`${this.syncResourceLogLabel}: Merging remote ui state with local ui state...`); @@ -159,7 +164,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (lastSyncGlobalState === null) { return true; } - const localGlobalState = await this.getLocalGlobalState(); + const localGlobalState = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile); const storageKeys = await this.getStorageKeys(lastSyncGlobalState); const { remote } = merge(localGlobalState.storage, lastSyncGlobalState.storage, lastSyncGlobalState.storage, storageKeys, this.logService); return remote.all !== null; @@ -233,7 +238,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs // update local this.logService.trace(`${this.syncResourceLogLabel}: Updating local ui state...`); await this.backupLocal(JSON.stringify(localUserData)); - await this.writeLocalGlobalState(local); + await this.localGlobalStateProvider.writeLocalGlobalState(local, this.syncResource.profile); this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`); } @@ -259,7 +264,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs override async resolveContent(uri: URI): Promise { if (this.extUri.isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) { - const localGlobalState = await this.getLocalGlobalState(); + const localGlobalState = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile); return stringify(localGlobalState, true); } @@ -293,7 +298,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs async hasLocalData(): Promise { try { - const { storage } = await this.getLocalGlobalState(); + const { storage } = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile); if (Object.keys(storage).length > 1 || storage[`${argvStoragePrefx}.locale`]?.value !== 'en') { return true; } @@ -303,9 +308,41 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return false; } - private async getLocalGlobalState(): Promise { + private async getStorageKeys(lastSyncGlobalState: IGlobalState | null): Promise { + const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.syncResource.profile); + const user: string[] = [], machine: string[] = []; + for (const [key, value] of storageData) { + if (value.target === StorageTarget.USER) { + user.push(key); + } else if (value.target === StorageTarget.MACHINE) { + machine.push(key); + } + } + const registered = [...user, ...machine]; + const unregistered = lastSyncGlobalState?.storage ? Object.keys(lastSyncGlobalState.storage).filter(key => !key.startsWith(argvStoragePrefx) && !registered.includes(key) && storageData.get(key) !== undefined) : []; + + if (!isWeb) { + // Following keys are synced only in web. Do not sync these keys in other platforms + const keysSyncedOnlyInWeb = [...ALL_SYNC_RESOURCES.map(resource => getEnablementKey(resource)), SYNC_SERVICE_URL_TYPE]; + unregistered.push(...keysSyncedOnlyInWeb); + machine.push(...keysSyncedOnlyInWeb); + } + + return { user, machine, unregistered }; + } +} + +export class LocalGlobalStateProvider { + constructor( + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService + ) { } + + async getLocalGlobalState(profile: IUserDataProfile): Promise { const storage: IStringDictionary = {}; - if (this.syncResource.profile.isDefault) { + if (profile.isDefault) { const argvContent: string = await this.getLocalArgvContent(); const argvValue: IStringDictionary = parse(argvContent); for (const argvProperty of argvProperties) { @@ -314,7 +351,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } } - const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.syncResource.profile); + const storageData = await this.userDataSyncProfilesStorageService.readStorageData(profile); for (const [key, value] of storageData) { if (value.value && value.target === StorageTarget.USER) { storage[key] = { version: 1, value: value.value }; @@ -335,10 +372,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return '{}'; } - private async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary; updated: IStringDictionary; removed: string[] }): Promise { + async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary; updated: IStringDictionary; removed: string[] }, profile: IUserDataProfile): Promise { + const syncResourceLogLabel = getSyncResourceLogLabel(SyncResource.GlobalState, profile); const argv: IStringDictionary = {}; const updatedStorage = new Map(); - const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.syncResource.profile); + const storageData = await this.userDataSyncProfilesStorageService.readStorageData(profile); const handleUpdatedStorage = (keys: string[], storage?: IStringDictionary): void => { for (const key of keys) { if (key.startsWith(argvStoragePrefx)) { @@ -360,52 +398,27 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs handleUpdatedStorage(Object.keys(added), added); handleUpdatedStorage(Object.keys(updated), updated); handleUpdatedStorage(removed); - if (Object.keys(argv).length) { - this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`); - await this.updateArgv(argv); - this.logService.info(`${this.syncResourceLogLabel}: Updated locale`); - } - if (updatedStorage.size) { - this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`); - await this.userDataSyncProfilesStorageService.updateStorageData(this.syncResource.profile, updatedStorage, StorageTarget.USER); - this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, [...updatedStorage.keys()]); - } - } - - private async updateArgv(argv: IStringDictionary): Promise { - const argvContent = await this.getLocalArgvContent(); - let content = argvContent; - for (const argvProperty of Object.keys(argv)) { - content = edit(content, [argvProperty], argv[argvProperty], {}); - } - if (argvContent !== content) { - this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`); - await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content)); - this.logService.info(`${this.syncResourceLogLabel}: Updated locale.`); - } - } - private async getStorageKeys(lastSyncGlobalState: IGlobalState | null): Promise { - const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.syncResource.profile); - const user: string[] = [], machine: string[] = []; - for (const [key, value] of storageData) { - if (value.target === StorageTarget.USER) { - user.push(key); - } else if (value.target === StorageTarget.MACHINE) { - machine.push(key); + if (Object.keys(argv).length) { + this.logService.trace(`${syncResourceLogLabel}: Updating locale...`); + const argvContent = await this.getLocalArgvContent(); + let content = argvContent; + for (const argvProperty of Object.keys(argv)) { + content = edit(content, [argvProperty], argv[argvProperty], {}); + } + if (argvContent !== content) { + this.logService.trace(`${syncResourceLogLabel}: Updating locale...`); + await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content)); + this.logService.info(`${syncResourceLogLabel}: Updated locale.`); } + this.logService.info(`${syncResourceLogLabel}: Updated locale`); } - const registered = [...user, ...machine]; - const unregistered = lastSyncGlobalState?.storage ? Object.keys(lastSyncGlobalState.storage).filter(key => !key.startsWith(argvStoragePrefx) && !registered.includes(key) && storageData.get(key) !== undefined) : []; - if (!isWeb) { - // Following keys are synced only in web. Do not sync these keys in other platforms - const keysSyncedOnlyInWeb = [...ALL_SYNC_RESOURCES.map(resource => getEnablementKey(resource)), SYNC_SERVICE_URL_TYPE]; - unregistered.push(...keysSyncedOnlyInWeb); - machine.push(...keysSyncedOnlyInWeb); + if (updatedStorage.size) { + this.logService.trace(`${syncResourceLogLabel}: Updating global state...`); + await this.userDataSyncProfilesStorageService.updateStorageData(profile, updatedStorage, StorageTarget.USER); + this.logService.info(`${syncResourceLogLabel}: Updated global state`, [...updatedStorage.keys()]); } - - return { user, machine, unregistered }; } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 813a43ad91a57..fbf1f1316816d 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -559,7 +559,7 @@ class ProfileSynchronizer extends Disposable { this._profile = userDataProfilesService.defaultProfile; for (const [synchronizer] of this._enabled) { if (synchronizer instanceof ExtensionsSynchroniser) { - synchronizer.profileLocation = this._profile.extensionsResource; + synchronizer.profile = this._profile; } } }