From 6fce0c6cb683c364a7a4d7f699338cbdcd4fc311 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 14 Sep 2023 16:03:52 +0200 Subject: [PATCH] Add settings sync troubleshoot tooling (#193114) fix #192271 --- .../node/sharedProcess/sharedProcessMain.ts | 8 +- src/vs/platform/request/common/request.ts | 2 +- .../common/abstractSynchronizer.ts | 14 +- .../userDataSync/common/extensionsSync.ts | 6 +- .../userDataSync/common/globalStateSync.ts | 6 +- .../userDataSync/common/keybindingsSync.ts | 6 +- .../userDataSync/common/settingsSync.ts | 6 +- .../userDataSync/common/snippetsSync.ts | 6 +- .../platform/userDataSync/common/tasksSync.ts | 6 +- .../common/userDataProfilesManifestSync.ts | 6 +- .../userDataSync/common/userDataSync.ts | 41 ++-- ...ce.ts => userDataSyncLocalStoreService.ts} | 33 ++- .../userDataSync/common/userDataSyncLog.ts | 5 +- .../common/userDataSyncMachines.ts | 4 +- .../common/userDataSyncResourceProvider.ts | 110 +++++++--- .../common/userDataSyncService.ts | 54 +++-- .../common/userDataSyncServiceIpc.ts | 42 ++-- .../common/userDataSyncStoreService.ts | 24 ++- .../test/common/userDataSyncClient.ts | 6 +- .../editSessions/common/workspaceStateSync.ts | 14 +- .../userDataSync/browser/userDataSync.ts | 67 +++++- .../userDataSync/browser/userDataSyncViews.ts | 190 ++++++++++++++---- .../browser/userDataSyncWorkbenchService.ts | 36 +++- .../userDataSync/common/userDataSync.ts | 3 + .../electron-sandbox/userDataSyncService.ts | 3 +- src/vs/workbench/workbench.web.main.ts | 6 +- 26 files changed, 507 insertions(+), 197 deletions(-) rename src/vs/platform/userDataSync/common/{userDataSyncBackupStoreService.ts => userDataSyncLocalStoreService.ts} (73%) diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 2a46b4ddaf5..42e2e070957 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -60,9 +60,9 @@ import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryServi import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; -import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { UserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSyncLocalStoreService'; import { UserDataAutoSyncChannel, UserDataSyncAccountServiceChannel, UserDataSyncMachinesServiceChannel, UserDataSyncStoreManagementServiceChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; @@ -340,7 +340,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService, undefined, true)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService, undefined, true)); services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService, undefined, true)); - services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService, undefined, false /* Eagerly cleans up old backups */)); + services.set(IUserDataSyncLocalStoreService, new SyncDescriptor(UserDataSyncLocalStoreService, undefined, false /* Eagerly cleans up old backups */)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService, undefined, true)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService, undefined, false /* Initializes the Sync State */)); services.set(IUserDataProfileStorageService, new SyncDescriptor(NativeUserDataProfileStorageService, undefined, true)); @@ -414,6 +414,8 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); + this.server.registerChannel('IUserDataSyncResourceProviderService', ProxyChannel.fromService(accessor.get(IUserDataSyncResourceProviderService), disposables)); + // Tunnel const sharedProcessTunnelChannel = ProxyChannel.fromService(accessor.get(ISharedProcessTunnelService), disposables); this.server.registerChannel(ipcSharedProcessTunnelChannelName, sharedProcessTunnelChannel); diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 125b79f2556..6dfcb237289 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -85,7 +85,7 @@ export function isSuccess(context: IRequestContext): boolean { return (context.res.statusCode && context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223; } -function hasNoContent(context: IRequestContext): boolean { +export function hasNoContent(context: IRequestContext): boolean { return context.res.statusCode === 204; } diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index d70ba14220d..eb24d9ca6b5 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -26,7 +26,7 @@ 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 { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, 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 { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLocalStoreService, 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 { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; type IncompatibleSyncSourceClassification = { @@ -153,7 +153,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa @IEnvironmentService protected readonly environmentService: IEnvironmentService, @IStorageService protected readonly storageService: IStorageService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService protected readonly userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService protected readonly telemetryService: ITelemetryService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, @@ -770,7 +770,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa protected async backupLocal(content: string): Promise { const syncData: ISyncData = { version: this.version, content }; - return this.userDataSyncBackupStoreService.backup(this.syncResource.profile, this.resource, JSON.stringify(syncData)); + return this.userDataSyncLocalStoreService.writeResource(this.resource, JSON.stringify(syncData), new Date(), this.syncResource.profile.isDefault ? undefined : this.syncResource.profile.id); } async stop(): Promise { @@ -820,14 +820,14 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super(syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this._register(this.fileService.watch(this.extUri.dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } @@ -888,7 +888,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -896,7 +896,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @IConfigurationService configurationService: IConfigurationService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super(file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); } protected hasErrors(content: string, isArray: boolean): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index bd85da7b12c..be0c0e0bbb4 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -30,7 +30,7 @@ import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userData 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, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ILocalSyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, ISyncData, ISyncExtension, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ILocalSyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; type IExtensionResourceMergeResult = IAcceptResult & IExtensionMergeResult; @@ -120,7 +120,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IFileService fileService: IFileService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -132,7 +132,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataProfileStorageService userDataProfileStorageService: IUserDataProfileStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super({ syncResource: SyncResource.Extensions, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.Extensions, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this.localExtensionsProvider = this.instantiationService.createInstance(LocalExtensionsProvider); this._register( Event.any( diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index a227f78e492..4c698553970 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -25,7 +25,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' 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, 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 { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, IUserData, IUserDataSyncLocalStoreService, 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 { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; @@ -83,7 +83,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -93,7 +93,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IUriIdentityService uriIdentityService: IUriIdentityService, @IInstantiationService instantiationService: IInstantiationService, ) { - super({ syncResource: SyncResource.GlobalState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.GlobalState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this.localGlobalStateProvider = instantiationService.createInstance(LocalGlobalStateProvider); this._register(fileService.watch(this.extUri.dirname(this.environmentService.argvResource))); this._register( diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 95e228c72fa..715707a19cc 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -22,7 +22,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM } from 'vs/platform/userDataSync/common/userDataSync'; interface ISyncContent { mac?: string; @@ -73,7 +73,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem profile: IUserDataProfile, collection: string | undefined, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -84,7 +84,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(profile.keybindingsResource, { syncResource: SyncResource.Keybindings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); + super(profile.keybindingsResource, { syncResource: SyncResource.Keybindings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.keybindingsPerPlatform'))(() => this.triggerLocalChange())); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 8e48d445f5b..e16e0073a28 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -19,7 +19,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { getIgnoredSettings, isEmpty, merge, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest } from 'vs/platform/userDataSync/common/userDataSync'; interface ISettingsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; @@ -57,7 +57,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, @@ -66,7 +66,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(profile.settingsResource, { syncResource: SyncResource.Settings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); + super(profile.settingsResource, { syncResource: SyncResource.Settings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); } async getRemoteUserDataSyncConfiguration(manifest: IUserDataResourceManifest | null): Promise { diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 7a6a086ca59..d89e5a95fe8 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -18,7 +18,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { areSame, IMergeResult as ISnippetsMergeResult, merge } from 'vs/platform/userDataSync/common/snippetsMerge'; -import { Change, IRemoteUserData, ISyncData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, ISyncData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; interface ISnippetsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; @@ -44,14 +44,14 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD @IFileService fileService: IFileService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super({ syncResource: SyncResource.Snippets, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.Snippets, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this.snippetsFolder = profile.snippetsHome; this._register(this.fileService.watch(environmentService.userRoamingDataHome)); this._register(this.fileService.watch(this.snippetsFolder)); diff --git a/src/vs/platform/userDataSync/common/tasksSync.ts b/src/vs/platform/userDataSync/common/tasksSync.ts index ae77bac5301..3c764f33bdd 100644 --- a/src/vs/platform/userDataSync/common/tasksSync.ts +++ b/src/vs/platform/userDataSync/common/tasksSync.ts @@ -15,7 +15,7 @@ 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 { AbstractFileSynchroniser, AbstractInitializer, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; interface ITasksSyncContent { tasks?: string; @@ -48,7 +48,7 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser profile: IUserDataProfile, collection: string | undefined, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -58,7 +58,7 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(profile.tasksResource, { syncResource: SyncResource.Tasks, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super(profile.tasksResource, { syncResource: SyncResource.Tasks, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); } protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts index d655cf5984e..eee01fe850c 100644 --- a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts +++ b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts @@ -15,7 +15,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { merge } from 'vs/platform/userDataSync/common/userDataProfilesManifestMerge'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ISyncUserDataProfile, ISyncData, IUserDataResourceManifest, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ISyncUserDataProfile, ISyncData, IUserDataResourceManifest, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; interface IUserDataProfileManifestResourceMergeResult extends IAcceptResult { readonly local: { added: ISyncUserDataProfile[]; removed: IUserDataProfile[]; updated: ISyncUserDataProfile[] }; @@ -44,14 +44,14 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super({ syncResource: SyncResource.Profiles, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.Profiles, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this._register(userDataProfilesService.onDidChangeProfiles(() => this.triggerLocalChange())); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 245552ec53a..13f99fc4aa5 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { distinct } from 'vs/base/common/arrays'; +import { VSBufferReadableStream } from 'vs/base/common/buffer'; import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -22,6 +23,7 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; import { IUserDataProfile, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; export function getDisallowedIgnoredSettings(): string[] { const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); @@ -167,6 +169,19 @@ export interface IUserDataManifest { readonly collections?: IUserDataCollectionManifest; } +export interface IUserDataActivityData { + resources?: { + [resourceId: string]: { created: number; content: string }[]; + }; + collections?: { + [collectionId: string]: { + resources?: { + [resourceId: string]: { created: number; content: string }[]; + } | undefined; + }; + }; +} + export interface IResourceRefHandle { ref: string; created: number; @@ -205,15 +220,17 @@ export interface IUserDataSyncStoreService { createCollection(headers?: IHeaders): Promise; deleteCollection(collection?: string, headers?: IHeaders): Promise; + getActivityData(): Promise; + clear(): Promise; } -export const IUserDataSyncBackupStoreService = createDecorator('IUserDataSyncBackupStoreService'); -export interface IUserDataSyncBackupStoreService { +export const IUserDataSyncLocalStoreService = createDecorator('IUserDataSyncLocalStoreService'); +export interface IUserDataSyncLocalStoreService { readonly _serviceBrand: undefined; - backup(profile: IUserDataProfile, resource: SyncResource, content: string): Promise; - getAllRefs(profile: IUserDataProfile, resource: SyncResource): Promise; - resolveContent(profile: IUserDataProfile, resource: SyncResource, ref: string): Promise; + writeResource(resource: ServerResource, content: string, cTime: Date, collection?: string, root?: URI): Promise; + getAllResourceRefs(resource: ServerResource, collection?: string, root?: URI): Promise; + resolveResourceContent(resource: ServerResource, ref: string, collection?: string, root?: URI): Promise; } //#endregion @@ -539,22 +556,22 @@ export interface IUserDataSyncService { hasLocalData(): Promise; hasPreviouslySynced(): Promise; - getRemoteProfiles(): Promise; - getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise; - getLocalSyncResourceHandles(syncResource: SyncResource, profile?: IUserDataProfile): Promise; - getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]>; - getMachineId(syncResourceHandle: ISyncResourceHandle): Promise; replace(syncResourceHandle: ISyncResourceHandle): Promise; + + saveRemoteActivityData(location: URI): Promise; + extractActivityData(activityDataResource: URI, location: URI): Promise; } export const IUserDataSyncResourceProviderService = createDecorator('IUserDataSyncResourceProviderService'); export interface IUserDataSyncResourceProviderService { _serviceBrand: any; getRemoteSyncedProfiles(): Promise; - getLocalSyncResourceHandles(syncResource: SyncResource, profile: IUserDataProfile): Promise; - getRemoteSyncResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise; + getLocalSyncedProfiles(location?: URI): Promise; + getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise; + getLocalSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile, location?: URI): Promise; getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]>; getMachineId(syncResourceHandle: ISyncResourceHandle): Promise; + getLocalSyncedMachines(location?: URI): Promise; resolveContent(resource: URI): Promise; resolveUserDataSyncResource(syncResourceHandle: ISyncResourceHandle): IUserDataSyncResource | undefined; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts similarity index 73% rename from src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts rename to src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts index dc19ee03ce9..28d2400cfee 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts @@ -12,10 +12,10 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileOperationResult, IFileService, IFileStat, toFileOperationResult } from 'vs/platform/files/common/files'; -import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ALL_SYNC_RESOURCES, IResourceRefHandle, IUserDataSyncBackupStoreService, IUserDataSyncLogService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ALL_SYNC_RESOURCES, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncLogService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -export class UserDataSyncBackupStoreService extends Disposable implements IUserDataSyncBackupStoreService { +export class UserDataSyncLocalStoreService extends Disposable implements IUserDataSyncLocalStoreService { _serviceBrand: any; @@ -34,7 +34,7 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD for (const profile of this.userDataProfilesService.profiles) { for (const resource of ALL_SYNC_RESOURCES) { try { - await this.cleanUpBackup(this.getResourceBackupHome(profile, resource)); + await this.cleanUpBackup(this.getResourceBackupHome(resource, profile.isDefault ? undefined : profile.id)); } catch (error) { this.logService.error(error); } @@ -65,12 +65,12 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } } - async getAllRefs(profile: IUserDataProfile, resource: SyncResource): Promise { - const folder = this.getResourceBackupHome(profile, resource); + async getAllResourceRefs(resource: SyncResource, collection?: string, root?: URI): Promise { + const folder = this.getResourceBackupHome(resource, collection, root); try { const stat = await this.fileService.resolve(folder); if (stat.children) { - const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); + const all = stat.children.filter(stat => stat.isFile && !stat.name.startsWith('lastSync')).sort().reverse(); return all.map(stat => ({ ref: stat.name, created: this.getCreationTime(stat) @@ -84,8 +84,8 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD return []; } - async resolveContent(profile: IUserDataProfile, resourceKey: SyncResource, ref: string): Promise { - const folder = this.getResourceBackupHome(profile, resourceKey); + async resolveResourceContent(resourceKey: SyncResource, ref: string, collection?: string, root?: URI): Promise { + const folder = this.getResourceBackupHome(resourceKey, collection, root); const file = joinPath(folder, ref); try { const content = await this.fileService.readFile(file); @@ -96,21 +96,18 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } } - async backup(profile: IUserDataProfile, resourceKey: SyncResource, content: string): Promise { - const folder = this.getResourceBackupHome(profile, resourceKey); - const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); + async writeResource(resourceKey: SyncResource, content: string, cTime: Date, collection?: string, root?: URI): Promise { + const folder = this.getResourceBackupHome(resourceKey, collection, root); + const resource = joinPath(folder, `${toLocalISOString(cTime).replace(/-|:|\.\d+Z$/g, '')}.json`); try { await this.fileService.writeFile(resource, VSBuffer.fromString(content)); } catch (e) { this.logService.error(e); } - try { - this.cleanUpBackup(folder); - } catch (e) { /* Ignore */ } } - private getResourceBackupHome(profile: IUserDataProfile, resource: SyncResource): URI { - return joinPath(this.environmentService.userDataSyncHome, ...(profile.isDefault ? [resource] : [profile.id, resource])); + private getResourceBackupHome(resource: SyncResource, collection?: string, root: URI = this.environmentService.userDataSyncHome): URI { + return joinPath(root, ...(collection ? [collection, resource] : [resource])); } private async cleanUpBackup(folder: URI): Promise { @@ -142,7 +139,7 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } private getCreationTime(stat: IFileStat) { - return stat.ctime || new Date( + return new Date( parseInt(stat.name.substring(0, 4)), parseInt(stat.name.substring(4, 6)) - 1, parseInt(stat.name.substring(6, 8)), diff --git a/src/vs/platform/userDataSync/common/userDataSyncLog.ts b/src/vs/platform/userDataSync/common/userDataSyncLog.ts index d458fc3e8db..4dffdbc947e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncLog.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncLog.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log'; import { IUserDataSyncLogService, USER_DATA_SYNC_LOG_ID } from 'vs/platform/userDataSync/common/userDataSync'; @@ -14,9 +16,10 @@ export class UserDataSyncLogService extends AbstractLogger implements IUserDataS constructor( @ILoggerService loggerService: ILoggerService, + @IEnvironmentService environmentService: IEnvironmentService, ) { super(); - this.logger = this._register(loggerService.createLogger(USER_DATA_SYNC_LOG_ID, { name: localize('userDataSyncLog', "Settings Sync") })); + this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${USER_DATA_SYNC_LOG_ID}.log`), { id: USER_DATA_SYNC_LOG_ID, name: localize('userDataSyncLog', "Settings Sync") })); } trace(message: string, ...args: any[]): void { diff --git a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts index 6460ed5792d..ad3f8f63a1c 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts @@ -16,14 +16,14 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IUserData, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; -interface IMachineData { +export interface IMachineData { id: string; name: string; disabled?: boolean; platform?: string; } -interface IMachinesData { +export interface IMachinesData { version: number; machines: IMachineData[]; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts b/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts index 7086de977e6..e31e85e0e57 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts @@ -11,7 +11,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { getServiceMachineId } from 'vs/platform/externalServices/common/serviceMachineId'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { ISyncData, ISyncResourceHandle, IUserData, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncStoreService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncResourceProviderService, ISyncUserDataProfile, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISyncData, ISyncResourceHandle, IUserData, IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncStoreService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncResourceProviderService, ISyncUserDataProfile, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { isSyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { parseSnippets } from 'vs/platform/userDataSync/common/snippetsSync'; @@ -24,6 +24,8 @@ import { LocalGlobalStateProvider, stringify as stringifyGlobalState } from 'vs/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { parseUserDataProfilesManifest, stringifyLocalProfiles } from 'vs/platform/userDataSync/common/userDataProfilesManifestSync'; import { toFormattedString } from 'vs/base/common/jsonFormatter'; +import { trim } from 'vs/base/common/strings'; +import { IMachinesData, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; interface ISyncResourceUriInfo { readonly remote: boolean; @@ -32,6 +34,7 @@ interface ISyncResourceUriInfo { readonly collection: string | undefined; readonly ref: string | undefined; readonly node: string | undefined; + readonly location: URI | undefined; } export class UserDataSyncResourceProviderService implements IUserDataSyncResourceProviderService { @@ -46,7 +49,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService private readonly userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -68,7 +71,31 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc return []; } - async getRemoteSyncResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise { + async getLocalSyncedProfiles(location?: URI): Promise { + const refs = await this.userDataSyncLocalStoreService.getAllResourceRefs(SyncResource.Profiles, undefined, location); + if (refs.length) { + const content = await this.userDataSyncLocalStoreService.resolveResourceContent(SyncResource.Profiles, refs[0].ref, undefined, location); + if (content) { + const syncData = this.parseSyncData(content, SyncResource.Profiles); + return parseUserDataProfilesManifest(syncData); + } + } + return []; + } + + async getLocalSyncedMachines(location?: URI): Promise { + const refs = await this.userDataSyncLocalStoreService.getAllResourceRefs('machines', undefined, location); + if (refs.length) { + const content = await this.userDataSyncLocalStoreService.resolveResourceContent('machines', refs[0].ref, undefined, location); + if (content) { + const machinesData: IMachinesData = JSON.parse(content); + return machinesData.machines.map(m => ({ ...m, isCurrent: false })); + } + } + return []; + } + + async getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { const handles = await this.userDataSyncStoreService.getAllResourceRefs(syncResource, profile?.collection); return handles.map(({ created, ref }) => ({ created, @@ -76,6 +103,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: true, syncResource, profile: profile?.id ?? this.userDataProfilesService.defaultProfile.id, + location: undefined, collection: profile?.collection, ref, node: undefined, @@ -83,17 +111,18 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc })); } - async getLocalSyncResourceHandles(syncResource: SyncResource, profile: IUserDataProfile): Promise { - const handles = await this.userDataSyncBackupStoreService.getAllRefs(profile, syncResource); + async getLocalSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile, location?: URI): Promise { + const handles = await this.userDataSyncLocalStoreService.getAllResourceRefs(syncResource, profile?.collection, location); return handles.map(({ created, ref }) => ({ created, uri: this.toUri({ remote: false, syncResource, - profile: profile.id, - collection: undefined, + profile: profile?.id ?? this.userDataProfilesService.defaultProfile.id, + collection: profile?.collection, ref, node: undefined, + location, }) })); } @@ -138,6 +167,18 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc } return undefined; } + + if (resolved.location) { + if (resolved.ref) { + const content = await this.userDataSyncLocalStoreService.resolveResourceContent(resolved.syncResource, resolved.ref, resolved.collection, resolved.location); + if (content) { + const syncData = this.parseSyncData(content, resolved.syncResource); + return syncData?.machineId; + } + } + return undefined; + } + return getServiceMachineId(this.environmentService, this.fileService, this.storageService); } @@ -152,7 +193,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc } if (resolved.ref) { - const content = await this.getContentFromStore(resolved.remote, resolved.syncResource, resolved.profile, resolved.collection, resolved.ref); + const content = await this.getContentFromStore(resolved.remote, resolved.syncResource, resolved.collection, resolved.ref, resolved.location); if (resolved.node && content) { return this.resolveNodeContent(resolved.syncResource, content, resolved.node); } @@ -166,16 +207,12 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc return null; } - private async getContentFromStore(remote: boolean, syncResource: SyncResource, profileId: string, collection: string | undefined, ref: string): Promise { + private async getContentFromStore(remote: boolean, syncResource: SyncResource, collection: string | undefined, ref: string, location?: URI): Promise { if (remote) { const { content } = await this.getUserData(syncResource, ref, collection); return content; } - const profile = this.userDataProfilesService.profiles.find(p => p.id === profileId); - if (profile) { - return this.userDataSyncBackupStoreService.resolveContent(profile, syncResource, ref); - } - return null; + return this.userDataSyncLocalStoreService.resolveResourceContent(syncResource, ref, collection, location); } private resolveNodeContent(syncResource: SyncResource, content: string, node: string): string | null { @@ -280,6 +317,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: false, syncResource: SyncResource.Extensions, profile: profile.id, + location: undefined, collection: undefined, ref: undefined, node: undefined, @@ -308,6 +346,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: false, syncResource: SyncResource.GlobalState, profile: profile.id, + location: undefined, collection: undefined, ref: undefined, node: undefined, @@ -335,6 +374,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: false, syncResource: SyncResource.Profiles, profile: this.userDataProfilesService.defaultProfile.id, + location: undefined, collection: undefined, ref: undefined, node: undefined, @@ -356,10 +396,15 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc private toUri(syncResourceUriInfo: ISyncResourceUriInfo): URI { const authority = syncResourceUriInfo.remote ? UserDataSyncResourceProviderService.REMOTE_BACKUP_AUTHORITY : UserDataSyncResourceProviderService.LOCAL_BACKUP_AUTHORITY; - const paths = [ - syncResourceUriInfo.syncResource, - syncResourceUriInfo.profile, - ]; + const paths = []; + if (syncResourceUriInfo.location) { + paths.push(`scheme:${syncResourceUriInfo.location.scheme}`); + paths.push(`authority:${syncResourceUriInfo.location.authority}`); + paths.push(trim(syncResourceUriInfo.location.path, '/')); + } + paths.push(`syncResource:${syncResourceUriInfo.syncResource}`); + paths.push(`profile:${syncResourceUriInfo.profile}`); + paths.push(syncResourceUriInfo.profile); if (syncResourceUriInfo.collection) { paths.push(`collection:${syncResourceUriInfo.collection}`); } @@ -369,16 +414,13 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc if (syncResourceUriInfo.node) { paths.push(syncResourceUriInfo.node); } - return this.extUri.joinPath(URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority, path: `/` }), ...paths); + return this.extUri.joinPath(URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority, path: `/`, query: syncResourceUriInfo.location?.query, fragment: syncResourceUriInfo.location?.fragment }), ...paths); } private resolveUri(uri: URI): ISyncResourceUriInfo | undefined { if (uri.scheme !== USER_DATA_SYNC_SCHEME) { return undefined; } - if (uri.authority !== UserDataSyncResourceProviderService.LOCAL_BACKUP_AUTHORITY && uri.authority !== UserDataSyncResourceProviderService.REMOTE_BACKUP_AUTHORITY) { - return undefined; - } const paths: string[] = []; while (uri.path !== '/') { paths.unshift(this.extUri.basename(uri)); @@ -388,28 +430,42 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc return undefined; } const remote = uri.authority === UserDataSyncResourceProviderService.REMOTE_BACKUP_AUTHORITY; - const syncResource = paths.shift()! as SyncResource; - const profile = paths.shift()!; + let scheme: string | undefined; + let authority: string | undefined; + const locationPaths: string[] = []; + let syncResource: SyncResource | undefined; + let profile: string | undefined; let collection: string | undefined; let ref: string | undefined; let node: string | undefined; while (paths.length) { const path = paths.shift()!; - if (path.startsWith('collection:')) { + if (path.startsWith('scheme:')) { + scheme = path.substring('scheme:'.length); + } else if (path.startsWith('authority:')) { + authority = path.substring('authority:'.length); + } else if (path.startsWith('syncResource:')) { + syncResource = path.substring('syncResource:'.length) as SyncResource; + } else if (path.startsWith('profile:')) { + profile = path.substring('profile:'.length); + } else if (path.startsWith('collection:')) { collection = path.substring('collection:'.length); } else if (path.startsWith('ref:')) { ref = path.substring('ref:'.length); + } else if (!syncResource) { + locationPaths.push(path); } else { node = path; } } return { remote, - syncResource, - profile, + syncResource: syncResource!, + profile: profile!, collection, ref, node, + location: scheme && authority !== undefined ? this.extUri.joinPath(URI.from({ scheme, authority, query: uri.query, fragment: uri.fragment, path: '/' }), ...locationPaths) : undefined }; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index a58320c263e..59ac96cda5a 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -30,7 +31,7 @@ import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, IUserDataManualSyncTask, IUserDataSyncResourceConflicts, IUserDataSyncResourceError, IUserDataSyncResource, ISyncResourceHandle, IUserDataSyncTask, ISyncUserDataProfile, IUserDataManifest, IUserDataResourceManifest, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, - MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE, IUserDataSyncResourceProviderService + MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE, IUserDataSyncResourceProviderService, IUserDataActivityData, IUserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSync'; type SyncErrorClassification = { @@ -81,6 +82,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private activeProfileSynchronizers = new Map(); constructor( + @IFileService private readonly fileService: IFileService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -90,6 +92,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IUserDataSyncResourceProviderService private readonly userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, + @IUserDataSyncLocalStoreService private readonly userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, ) { super(); this._status = userDataSyncStoreManagementService.userDataSyncStore ? SyncStatus.Idle : SyncStatus.Uninitialized; @@ -337,26 +340,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ }); } - getRemoteProfiles(): Promise { - return this.userDataSyncResourceProviderService.getRemoteSyncedProfiles(); - } - - getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { - return this.userDataSyncResourceProviderService.getRemoteSyncResourceHandles(syncResource, profile); - } - - async getLocalSyncResourceHandles(syncResource: SyncResource, profile?: IUserDataProfile): Promise { - return this.userDataSyncResourceProviderService.getLocalSyncResourceHandles(syncResource, profile ?? this.userDataProfilesService.defaultProfile); - } - - async getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]> { - return this.userDataSyncResourceProviderService.getAssociatedResources(syncResourceHandle); - } - - async getMachineId(syncResourceHandle: ISyncResourceHandle): Promise { - return this.userDataSyncResourceProviderService.getMachineId(syncResourceHandle); - } - async hasLocalData(): Promise { const result = await this.performAction(this.userDataProfilesService.defaultProfile, async synchronizer => { // skip global state synchronizer @@ -437,6 +420,35 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } + async saveRemoteActivityData(location: URI): Promise { + this.checkEnablement(); + const data = await this.userDataSyncStoreService.getActivityData(); + await this.fileService.writeFile(location, data); + } + + async extractActivityData(activityDataResource: URI, location: URI): Promise { + const content = (await this.fileService.readFile(activityDataResource)).value.toString(); + const activityData: IUserDataActivityData = JSON.parse(content); + + if (activityData.resources) { + for (const resource in activityData.resources) { + for (const version of activityData.resources[resource]) { + await this.userDataSyncLocalStoreService.writeResource(resource as SyncResource, version.content, new Date(version.created * 1000), undefined, location); + } + } + } + + if (activityData.collections) { + for (const collection in activityData.collections) { + for (const resource in activityData.collections[collection].resources) { + for (const version of activityData.collections[collection].resources?.[resource] ?? []) { + await this.userDataSyncLocalStoreService.writeResource(resource as SyncResource, version.content, new Date(version.created * 1000), collection, location); + } + } + } + } + } + private async performAction(profile: IUserDataProfile, action: (synchroniser: IUserDataSynchroniser) => Promise): Promise { const disposables = new DisposableStore(); try { diff --git a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts index 39c6dd16b90..94e3fdd0028 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts @@ -9,10 +9,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { ILogService } from 'vs/platform/log/common/log'; -import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataManualSyncTask, IUserDataSyncResourceConflicts, IUserDataSyncResourceError, IUserDataSyncResource, ISyncResourceHandle, IUserDataSyncTask, IUserDataSyncService, - SyncResource, SyncStatus, UserDataSyncError, ISyncUserDataProfile + SyncResource, SyncStatus, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; type ManualSyncTaskEvent = { manualSyncTaskId: string; data: T }; @@ -76,13 +76,10 @@ export class UserDataSyncChannel implements IServerChannel { case 'hasLocalData': return this.service.hasLocalData(); case 'resolveContent': return this.service.resolveContent(URI.revive(args[0])); case 'accept': return this.service.accept(reviewSyncResource(args[0], this.userDataProfilesService), URI.revive(args[1]), args[2], args[3]); - case 'getRemoteProfiles': return this.service.getRemoteProfiles(); - case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0], args[1] ? reviveProfile(args[1], this.userDataProfilesService.profilesHome.scheme) : undefined); - case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0], args[1]); case 'replace': return this.service.replace(reviewSyncResourceHandle(args[0])); - case 'getAssociatedResources': return this.service.getAssociatedResources(reviewSyncResourceHandle(args[0])); - case 'getMachineId': return this.service.getMachineId(reviewSyncResourceHandle(args[0])); case 'cleanUpRemoteData': return this.service.cleanUpRemoteData(); + case 'getRemoteActivityData': return this.service.saveRemoteActivityData(URI.revive(args[0])); + case 'extractActivityData': return this.service.extractActivityData(URI.revive(args[0]), URI.revive(args[1])); case 'createManualSyncTask': return this.createManualSyncTask(); } @@ -224,29 +221,6 @@ export class UserDataSyncChannelClient extends Disposable implements IUserDataSy return this.channel.call('resolveContent', [resource]); } - getRemoteProfiles(): Promise { - return this.channel.call('getRemoteProfiles'); - } - - async getLocalSyncResourceHandles(syncResource: SyncResource, profile?: IUserDataProfile): Promise { - const handles = await this.channel.call('getLocalSyncResourceHandles', [syncResource, profile]); - return handles.map(({ created, uri }) => ({ created, uri: URI.revive(uri) })); - } - - async getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { - const handles = await this.channel.call('getRemoteSyncResourceHandles', [syncResource, profile]); - return handles.map(({ created, uri }) => ({ created, uri: URI.revive(uri) })); - } - - async getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]> { - const result = await this.channel.call<{ resource: URI; comparableResource: URI }[]>('getAssociatedResources', [syncResourceHandle]); - return result.map(({ resource, comparableResource }) => ({ resource: URI.revive(resource), comparableResource: URI.revive(comparableResource) })); - } - - getMachineId(syncResourceHandle: ISyncResourceHandle): Promise { - return this.channel.call('getMachineId', [syncResourceHandle]); - } - cleanUpRemoteData(): Promise { return this.channel.call('cleanUpRemoteData'); } @@ -255,6 +229,14 @@ export class UserDataSyncChannelClient extends Disposable implements IUserDataSy return this.channel.call('replace', [syncResourceHandle]); } + saveRemoteActivityData(location: URI): Promise { + return this.channel.call('getRemoteActivityData', [location]); + } + + extractActivityData(activityDataResource: URI, location: URI): Promise { + return this.channel.call('extractActivityData', [activityDataResource, location]); + } + private async updateStatus(status: SyncStatus): Promise { this._status = status; this._onDidChangeStatus.fire(status); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index c3a47d18c50..046f969de91 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -20,10 +20,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; -import { asJson, asText, asTextOrError, IRequestService, isSuccess as isSuccessContext } from 'vs/platform/request/common/request'; +import { asJson, asText, asTextOrError, hasNoContent, IRequestService, isSuccess, isSuccess as isSuccessContext } from 'vs/platform/request/common/request'; import { getServiceMachineId } from 'vs/platform/externalServices/common/serviceMachineId'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { HEADER_EXECUTION_ID, HEADER_OPERATION_ID, IAuthenticationProvider, IResourceRefHandle, IUserData, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncStore, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, ServerResource, SYNC_SERVICE_URL_TYPE, UserDataSyncErrorCode, UserDataSyncStoreError, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync'; +import { VSBufferReadableStream } from 'vs/base/common/buffer'; const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; const SYNC_PREVIOUS_STORE = 'sync.previous.store'; @@ -457,6 +458,27 @@ export class UserDataSyncStoreClient extends Disposable { this.clearSession(); } + async getActivityData(): Promise { + if (!this.userDataSyncStoreUrl) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(this.userDataSyncStoreUrl, 'download').toString(); + const headers: IHeaders = {}; + + const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]); + } + + if (hasNoContent(context)) { + throw new UserDataSyncStoreError('Empty response', url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]); + } + + return context.stream; + } + private getResourceUrl(userDataSyncStoreUrl: URI, collection: string | undefined, resource: ServerResource): URI { return collection ? joinPath(userDataSyncStoreUrl, 'collection', collection, 'resource', resource) : joinPath(userDataSyncStoreUrl, 'resource', resource); } diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 730850b4b69..a1ba702c728 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -34,9 +34,9 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource, IUserDataSynchroniser, IUserDataResourceManifest, IUserDataCollectionManifest, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource, IUserDataSynchroniser, IUserDataResourceManifest, IUserDataCollectionManifest, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; -import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { UserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSyncLocalStoreService'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -113,7 +113,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IUserDataSyncAccountService, userDataSyncAccountService); this.instantiationService.stub(IUserDataSyncMachinesService, this._register(this.instantiationService.createInstance(UserDataSyncMachinesService))); - this.instantiationService.stub(IUserDataSyncBackupStoreService, this._register(this.instantiationService.createInstance(UserDataSyncBackupStoreService))); + this.instantiationService.stub(IUserDataSyncLocalStoreService, this._register(this.instantiationService.createInstance(UserDataSyncLocalStoreService))); this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService()); this.instantiationService.stub(IUserDataSyncEnablementService, this._register(this.instantiationService.createInstance(UserDataSyncEnablementService))); diff --git a/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts b/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts index aea881b28eb..83372f3c0c7 100644 --- a/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts +++ b/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts @@ -16,20 +16,20 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, ISyncResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; -import { IRemoteUserData, IResourceRefHandle, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IRemoteUserData, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { EditSession, IEditSessionsStorageService } from 'vs/workbench/contrib/editSessions/common/editSessions'; import { IWorkspaceIdentityService } from 'vs/workbench/services/workspaces/common/workspaceIdentityService'; -class NullBackupStoreService implements IUserDataSyncBackupStoreService { +class NullBackupStoreService implements IUserDataSyncLocalStoreService { _serviceBrand: undefined; - async backup(profile: IUserDataProfile, resource: SyncResource, content: string): Promise { + async writeResource(): Promise { return; } - async getAllRefs(profile: IUserDataProfile, resource: SyncResource): Promise { + async getAllResourceRefs(): Promise { return []; } - async resolveContent(profile: IUserDataProfile, resource: SyncResource, ref: string): Promise { + async resolveResourceContent(): Promise { return null; } @@ -70,9 +70,9 @@ export class WorkspaceStateSynchroniser extends AbstractSynchroniser implements @IWorkspaceIdentityService private readonly workspaceIdentityService: IWorkspaceIdentityService, @IEditSessionsStorageService private readonly editSessionsStorageService: IEditSessionsStorageService, ) { - const userDataSyncBackupStoreService = new NullBackupStoreService(); + const userDataSyncLocalStoreService = new NullBackupStoreService(); const userDataSyncEnablementService = new NullEnablementService(); - super({ syncResource: SyncResource.WorkspaceState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.WorkspaceState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); } override async sync(): Promise { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index e037a4298f8..1549fc0631e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -19,7 +19,7 @@ import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/acti import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, ContextKeyTrueExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { QuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -54,6 +54,11 @@ import { ctxIsMergeResultEditor, ctxMergeBaseUri } from 'vs/workbench/contrib/me import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IFileService } from 'vs/platform/files/common/files'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; type ConfigureSyncQuickPickItem = { id: SyncResource; label: string; description?: string }; @@ -712,6 +717,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerShowLogAction(); this.registerResetSyncDataAction(); this.registerAcceptMergesAction(); + this.registerDownloadSyncActivityAction(); } private registerTurnOnSyncAction(): void { @@ -1125,6 +1131,65 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo })); } + private registerDownloadSyncActivityAction(): void { + this._register(registerAction2(class DownloadSyncActivityAction extends Action2 { + constructor() { + super({ + id: 'workbench.userDataSync.actions.downloadSyncActivity', + title: { original: 'Download Settings Sync Activity', value: localize('download sync activity title', "Download Settings Sync Activity") }, + category: Categories.Developer, + f1: true, + precondition: ContextKeyExpr.and(CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) + }); + } + + async run(accessor: ServicesAccessor): Promise { + const userDataSyncWorkbenchService = accessor.get(IUserDataSyncWorkbenchService); + const fileDialogService = accessor.get(IFileDialogService); + const progressService = accessor.get(IProgressService); + const uriIdentityService = accessor.get(IUriIdentityService); + const fileService = accessor.get(IFileService); + const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService); + + const result = await fileDialogService.showOpenDialog({ + title: localize('download sync activity dialog title', "Select folder to download Settings Sync activity"), + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: localize('download sync activity dialog open label', "Save"), + }); + + if (!result?.[0]) { + return; + } + + await progressService.withProgress({ location: ProgressLocation.Window }, async () => { + const machines = await userDataSyncMachinesService.getMachines(); + const currentMachine = machines.find(m => m.isCurrent); + const name = (currentMachine ? currentMachine.name + ' - ' : '') + 'Settings Sync Activity'; + const stat = await fileService.resolve(result[0]); + + const nameRegEx = new RegExp(`${escapeRegExpCharacters(name)}\\s(\\d+)`); + const indexes: number[] = []; + for (const child of stat.children ?? []) { + if (child.name === name) { + indexes.push(0); + } else { + const matches = nameRegEx.exec(child.name); + if (matches) { + indexes.push(parseInt(matches[1])); + } + } + } + indexes.sort((a, b) => a - b); + + return userDataSyncWorkbenchService.downloadSyncActivity(uriIdentityService.extUri.joinPath(result[0], indexes[0] !== 0 ? name : `${name} ${indexes[indexes.length - 1] + 1}`)); + }); + } + + })); + } + private registerViews(): void { const container = this.registerViewContainer(); this.registerDataViews(container); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index e15c5c3bc79..e3ae0c168a9 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -9,14 +9,14 @@ import { localize } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ALL_SYNC_RESOURCES, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncEnablementService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, getLastSyncResourceUri, SyncResource, ISyncUserDataProfile, USER_DATA_SYNC_LOG_ID } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncEnablementService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, getLastSyncResourceUri, SyncResource, ISyncUserDataProfile, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI, UriDto } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { FolderThemeIcon } from 'vs/platform/theme/common/themeService'; import { fromNow } from 'vs/base/common/date'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; @@ -55,6 +55,7 @@ export class UserDataSyncDataViews extends Disposable { this.registerActivityView(container, false); this.registerTroubleShootView(container); + this.registerExternalActivityView(container); } private registerConflictsView(container: ViewContainer): void { @@ -167,6 +168,58 @@ export class UserDataSyncDataViews extends Disposable { this.registerDataViewActions(id); } + private registerExternalActivityView(container: ViewContainer): void { + const id = `workbench.views.sync.externalActivity`; + const name = localize('downloaded sync activity title', "Sync Activity (Developer)"); + const dataProvider = this.instantiationService.createInstance(ExtractedUserDataSyncActivityViewDataProvider, undefined); + const treeView = this.instantiationService.createInstance(TreeView, id, name); + treeView.showCollapseAllAction = false; + treeView.showRefreshAction = false; + treeView.dataProvider = dataProvider; + + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + viewsRegistry.registerViews([{ + id, + name, + ctorDescriptor: new SyncDescriptor(TreeViewPane), + when: CONTEXT_ENABLE_ACTIVITY_VIEWS, + canToggleVisibility: true, + canMoveView: false, + treeView, + collapsed: false, + hideByDefault: false, + }], container); + + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.sync.extractActivity`, + title: localize('workbench.actions.sync.extractActivity', "Extract Sync Activity"), + icon: Codicon.cloudUpload, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', id), + group: 'navigation', + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + const fileDialogService = accessor.get(IFileDialogService); + const result = await fileDialogService.showOpenDialog({ + title: localize('select sync activity file', "Select Sync Activity File or Folder"), + canSelectFiles: true, + canSelectFolders: true, + canSelectMany: false, + }); + if (!result?.[0]) { + return; + } + dataProvider.activityDataResource = result[0]; + await treeView.refresh(); + } + })); + } + private registerDataViewActions(viewId: string) { registerAction2(class extends Action2 { constructor() { @@ -289,6 +342,7 @@ abstract class UserDataSyncActivityViewDataProvider implements ITre constructor( @IUserDataSyncService protected readonly userDataSyncService: IUserDataSyncService, + @IUserDataSyncResourceProviderService protected readonly userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, @IUserDataAutoSyncService protected readonly userDataAutoSyncService: IUserDataAutoSyncService, @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @INotificationService private readonly notificationService: INotificationService, @@ -365,8 +419,8 @@ abstract class UserDataSyncActivityViewDataProvider implements ITre protected async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { const syncResourceHandle = (element).syncResourceHandle; - const associatedResources = await this.userDataSyncService.getAssociatedResources(syncResourceHandle); - const previousAssociatedResources = syncResourceHandle.previous ? await this.userDataSyncService.getAssociatedResources(syncResourceHandle.previous) : []; + const associatedResources = await this.userDataSyncResourceProviderService.getAssociatedResources(syncResourceHandle); + const previousAssociatedResources = syncResourceHandle.previous ? await this.userDataSyncResourceProviderService.getAssociatedResources(syncResourceHandle.previous) : []; return associatedResources.map(({ resource, comparableResource }) => { const handle = JSON.stringify({ resource: resource.toString(), comparableResource: comparableResource.toString() }); const previousResource = previousAssociatedResources.find(previous => basename(previous.resource) === basename(resource))?.resource; @@ -419,14 +473,20 @@ abstract class UserDataSyncActivityViewDataProvider implements ITre protected abstract getResourceHandles(syncResource: SyncResource, profile?: T): Promise; } -class LocalUserDataSyncActivityViewDataProvider extends UserDataSyncActivityViewDataProvider { +class LocalUserDataSyncActivityViewDataProvider extends UserDataSyncActivityViewDataProvider { - protected getResourceHandles(syncResource: SyncResource, profile: IUserDataProfile | undefined): Promise { - return this.userDataSyncService.getLocalSyncResourceHandles(syncResource, profile); + protected getResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise { + return this.userDataSyncResourceProviderService.getLocalSyncResourceHandles(syncResource, profile); } - protected async getProfiles(): Promise { - return this.userDataProfilesService.profiles.filter(p => !p.isDefault); + protected async getProfiles(): Promise { + return this.userDataProfilesService.profiles + .filter(p => !p.isDefault) + .map(p => ({ + id: p.id, + collection: p.id, + name: p.name, + })); } } @@ -436,13 +496,14 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie constructor( @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IUserDataSyncResourceProviderService userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, @IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService, @IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @INotificationService notificationService: INotificationService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, ) { - super(userDataSyncService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService, userDataProfilesService); + super(userDataSyncService, userDataSyncResourceProviderService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService, userDataProfilesService); } override async getChildren(element?: ITreeItem): Promise { @@ -460,17 +521,17 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie } protected getResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { - return this.userDataSyncService.getRemoteSyncResourceHandles(syncResource, profile); + return this.userDataSyncResourceProviderService.getRemoteSyncResourceHandles(syncResource, profile); } protected getProfiles(): Promise { - return this.userDataSyncService.getRemoteProfiles(); + return this.userDataSyncResourceProviderService.getRemoteSyncedProfiles(); } protected override async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { const children = await super.getChildrenForSyncResourceTreeItem(element); if (children.length) { - const machineId = await this.userDataSyncService.getMachineId(element.syncResourceHandle); + const machineId = await this.userDataSyncResourceProviderService.getMachineId(element.syncResourceHandle); if (machineId) { const machines = await this.getMachines(); const machine = machines.find(({ id }) => id === machineId); @@ -481,6 +542,73 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie } } +class ExtractedUserDataSyncActivityViewDataProvider extends UserDataSyncActivityViewDataProvider { + + private machinesPromise: Promise | undefined; + + private activityDataLocation: URI | undefined; + + constructor( + public activityDataResource: URI | undefined, + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IUserDataSyncResourceProviderService userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, + @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, + @IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, + @INotificationService notificationService: INotificationService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, + @IFileService private readonly fileService: IFileService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + super(userDataSyncService, userDataSyncResourceProviderService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService, userDataProfilesService); + } + + override async getChildren(element?: ITreeItem): Promise { + if (!element) { + this.machinesPromise = undefined; + if (!this.activityDataResource) { + return []; + } + const stat = await this.fileService.resolve(this.activityDataResource); + if (stat.isDirectory) { + this.activityDataLocation = this.activityDataResource; + } else { + this.activityDataLocation = this.uriIdentityService.extUri.joinPath(this.uriIdentityService.extUri.dirname(this.activityDataResource), 'remoteActivity'); + try { await this.fileService.del(this.activityDataLocation, { recursive: true }); } catch (e) {/* ignore */ } + await this.userDataSyncService.extractActivityData(this.activityDataResource, this.activityDataLocation); + } + } + return super.getChildren(element); + } + + protected getResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise { + return this.userDataSyncResourceProviderService.getLocalSyncResourceHandles(syncResource, profile, this.activityDataLocation); + } + + protected override async getProfiles(): Promise { + return this.userDataSyncResourceProviderService.getLocalSyncedProfiles(this.activityDataLocation); + } + + protected override async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { + const children = await super.getChildrenForSyncResourceTreeItem(element); + if (children.length) { + const machineId = await this.userDataSyncResourceProviderService.getMachineId(element.syncResourceHandle); + if (machineId) { + const machines = await this.getMachines(); + const machine = machines.find(({ id }) => id === machineId); + children[0].description = machine?.isCurrent ? localize({ key: 'current', comment: ['Represents current machine'] }, "Current") : machine?.name; + } + } + return children; + } + + private getMachines(): Promise { + if (this.machinesPromise === undefined) { + this.machinesPromise = this.userDataSyncResourceProviderService.getLocalSyncedMachines(this.activityDataLocation); + } + return this.machinesPromise; + } +} + class UserDataSyncMachinesViewDataProvider implements ITreeViewDataProvider { private machinesPromise: Promise | undefined; @@ -599,6 +727,7 @@ class UserDataSyncTroubleshootViewDataProvider implements ITreeViewDataProvider constructor( @IFileService private readonly fileService: IFileService, + @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { @@ -648,31 +777,18 @@ class UserDataSyncTroubleshootViewDataProvider implements ITreeViewDataProvider } private async getSyncLogs(): Promise { - const logsFolders: URI[] = []; - const stat = await this.fileService.resolve(this.uriIdentityService.extUri.dirname(this.environmentService.logsHome)); - if (stat.children) { - logsFolders.push(...stat.children - .filter(stat => stat.isDirectory && /^\d{8}T\d{6}$/.test(stat.name)) - .sort() - .reverse() - .map(d => d.resource)); - } - + const logResources = await this.userDataSyncWorkbenchService.getAllLogResources(); const result: ITreeItem[] = []; - for (const logFolder of logsFolders) { - const folderStat = await this.fileService.resolve(logFolder); - const childStat = folderStat.children?.find(stat => this.uriIdentityService.extUri.basename(stat.resource).startsWith(`${USER_DATA_SYNC_LOG_ID}.`)); - if (childStat) { - const syncLogResource = childStat.resource; - result.push({ - handle: syncLogResource.toString(), - collapsibleState: TreeItemCollapsibleState.None, - resourceUri: syncLogResource, - label: { label: this.uriIdentityService.extUri.basename(logFolder) }, - description: this.uriIdentityService.extUri.isEqual(logFolder, this.environmentService.logsHome) ? localize({ key: 'current', comment: ['Represents current log file'] }, "Current") : undefined, - command: { id: API_OPEN_EDITOR_COMMAND_ID, title: '', arguments: [syncLogResource, undefined, undefined] }, - }); - } + for (const syncLogResource of logResources) { + const logFolder = this.uriIdentityService.extUri.dirname(syncLogResource); + result.push({ + handle: syncLogResource.toString(), + collapsibleState: TreeItemCollapsibleState.None, + resourceUri: syncLogResource, + label: { label: this.uriIdentityService.extUri.basename(logFolder) }, + description: this.uriIdentityService.extUri.isEqual(logFolder, this.environmentService.logsHome) ? localize({ key: 'current', comment: ['Represents current log file'] }, "Current") : undefined, + command: { id: API_OPEN_EDITOR_COMMAND_ID, title: '', arguments: [syncLogResource, undefined, undefined] }, + }); } return result; } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index e5587cb3cda..82ba6f40652 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, IUserDataSyncStoreManagementService, SyncStatus, IUserDataSyncEnablementService, IUserDataSyncResource, IResourcePreview, USER_DATA_SYNC_SCHEME, } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, IUserDataSyncStoreManagementService, SyncStatus, IUserDataSyncEnablementService, IUserDataSyncResource, IResourcePreview, USER_DATA_SYNC_SCHEME, USER_DATA_SYNC_LOG_ID, } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_CONFLICTS_VIEW_ID, CONTEXT_ENABLE_SYNC_CONFLICTS_VIEW, CONTEXT_HAS_CONFLICTS, IUserDataSyncConflictsView } from 'vs/workbench/services/userDataSync/common/userDataSync'; @@ -38,6 +38,7 @@ import { isDiffEditorInput } from 'vs/workbench/common/editor'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; +import { IFileService } from 'vs/platform/files/common/files'; type AccountQuickPickItem = { label: string; authenticationProvider: IAuthenticationProvider; account?: UserDataSyncAccount; description?: string }; @@ -113,6 +114,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IUserDataInitializationService private readonly userDataInitializationService: IUserDataInitializationService, + @IFileService private readonly fileService: IFileService, ) { super(); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); @@ -448,12 +450,44 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } + async getAllLogResources(): Promise { + const logsFolders: URI[] = []; + const stat = await this.fileService.resolve(this.uriIdentityService.extUri.dirname(this.environmentService.logsHome)); + if (stat.children) { + logsFolders.push(...stat.children + .filter(stat => stat.isDirectory && /^\d{8}T\d{6}$/.test(stat.name)) + .sort() + .reverse() + .map(d => d.resource)); + } + const result: URI[] = []; + for (const logFolder of logsFolders) { + const folderStat = await this.fileService.resolve(logFolder); + const childStat = folderStat.children?.find(stat => this.uriIdentityService.extUri.basename(stat.resource).startsWith(`${USER_DATA_SYNC_LOG_ID}.`)); + if (childStat) { + result.push(childStat.resource); + } + } + return result; + } + async showSyncActivity(): Promise { this.activityViewsEnablementContext.set(true); await this.waitForActiveSyncViews(); await this.viewsService.openViewContainer(SYNC_VIEW_CONTAINER_ID); } + async downloadSyncActivity(location: URI): Promise { + await Promise.all([ + this.userDataSyncService.saveRemoteActivityData(this.uriIdentityService.extUri.joinPath(location, 'remoteActivity.json')), + (async () => { + const logResources = await this.getAllLogResources(); + await Promise.all(logResources.map(async logResource => this.fileService.copy(logResource, this.uriIdentityService.extUri.joinPath(location, 'logs', `${this.uriIdentityService.extUri.basename(this.uriIdentityService.extUri.dirname(logResource))}.log`)))); + })(), + this.fileService.copy(this.environmentService.userDataSyncHome, this.uriIdentityService.extUri.joinPath(location, 'localActivity')), + ]); + } + private async waitForActiveSyncViews(): Promise { const viewContainer = this.viewDescriptorService.getViewContainerById(SYNC_VIEW_CONTAINER_ID); if (viewContainer) { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index ace123f1533..0bc33984d51 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -43,6 +43,9 @@ export interface IUserDataSyncWorkbenchService { showConflicts(conflictToOpen?: IResourcePreview): Promise; accept(resource: IUserDataSyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean): Promise; + + getAllLogResources(): Promise; + downloadSyncActivity(location: URI): Promise; } export function getSyncAreaLabel(source: SyncResource): string { diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts index 59ca1f122e8..4825bb8d580 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncResourceProviderService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { UserDataSyncChannelClient } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; registerSharedProcessRemoteService(IUserDataSyncService, 'userDataSync', { channelClientCtor: UserDataSyncChannelClient }); +registerSharedProcessRemoteService(IUserDataSyncResourceProviderService, 'IUserDataSyncResourceProviderService'); diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 649a54cdb8c..d6109358040 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -75,9 +75,9 @@ import { IWorkbenchExtensionManagementService } from 'vs/workbench/services/exte import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { LogLevel } from 'vs/platform/log/common/log'; import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncLocalStoreService, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { UserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSyncLocalStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; @@ -96,7 +96,7 @@ registerSingleton(IAccessibilityService, AccessibilityService, InstantiationType registerSingleton(IContextMenuService, ContextMenuService, InstantiationType.Delayed); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService, InstantiationType.Delayed); registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService, InstantiationType.Delayed); -registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncLocalStoreService, UserDataSyncLocalStoreService, InstantiationType.Delayed); registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService, InstantiationType.Delayed); registerSingleton(IUserDataSyncService, UserDataSyncService, InstantiationType.Delayed); registerSingleton(IUserDataSyncResourceProviderService, UserDataSyncResourceProviderService, InstantiationType.Delayed);