diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index b34a93404a8..8dd72285963 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -21,6 +21,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { isString } from 'vs/base/common/types'; import { uppercaseFirstLetter } from 'vs/base/common/strings'; import { equals } from 'vs/base/common/arrays'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; +import { IStorageService } from 'vs/platform/storage/common/storage'; type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -33,19 +35,33 @@ export interface IRemoteUserData { export interface ISyncData { version: number; + machineId?: string; content: string; } function isSyncData(thing: any): thing is ISyncData { - return thing + if (thing && (thing.version && typeof thing.version === 'number') - && (thing.content && typeof thing.content === 'string') - && Object.keys(thing).length === 2; + && (thing.content && typeof thing.content === 'string')) { + + if (Object.keys(thing).length === 2) { + return true; + } + + if (Object.keys(thing).length === 3 + && (thing.machineId && typeof thing.machineId === 'string')) { + return true; + } + } + + return false; } + export abstract class AbstractSynchroniser extends Disposable { protected readonly syncFolder: URI; + private readonly currentMachineIdPromise: Promise; private _status: SyncStatus = SyncStatus.Idle; get status(): SyncStatus { return this._status; } @@ -68,6 +84,7 @@ export abstract class AbstractSynchroniser extends Disposable { readonly resource: SyncResource, @IFileService protected readonly fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, + @IStorageService storageService: IStorageService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -79,6 +96,7 @@ export abstract class AbstractSynchroniser extends Disposable { this.syncResourceLogLabel = uppercaseFirstLetter(this.resource); this.syncFolder = joinPath(environmentService.userDataSyncHome, resource); this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`); + this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService); } protected async triggerLocalChange(): Promise { @@ -268,6 +286,18 @@ export abstract class AbstractSynchroniser extends Disposable { return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${this.resource}/${ref}` }); } + async getMachineId({ uri }: ISyncResourceHandle): Promise { + const ref = basename(uri); + if (isEqual(uri, this.toRemoteBackupResource(ref))) { + const { content } = await this.getUserData(ref); + if (content) { + const syncData = this.parseSyncData(content); + return syncData?.machineId; + } + } + return undefined; + } + async resolveContent(uri: URI): Promise { const ref = basename(uri); if (isEqual(uri, this.toRemoteBackupResource(ref))) { @@ -352,7 +382,8 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async updateRemoteUserData(content: string, ref: string | null): Promise { - const syncData: ISyncData = { version: this.version, content }; + const machineId = await this.currentMachineIdPromise; + const syncData: ISyncData = { version: this.version, machineId, content }; ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref); return { ref, syncData }; } @@ -387,6 +418,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, + @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -394,7 +426,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, ) { - super(resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(resource, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } @@ -492,6 +524,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni resource: SyncResource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, + @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -500,7 +533,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, ) { - super(file, resource, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(file, resource, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); } protected hasErrors(content: string): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 364230de5c1..38944c6df1d 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -20,6 +20,7 @@ import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; import { compare } from 'vs/base/common/strings'; +import { IStorageService } from 'vs/platform/storage/common/storage'; interface IExtensionsSyncPreviewResult extends ISyncPreviewResult { readonly localExtensions: ISyncExtension[]; @@ -46,6 +47,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @@ -56,7 +58,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncResource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.Extensions, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register( Event.debounce( Event.any( diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index bc6355b88d1..ec24f757805 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -56,7 +56,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IStorageService private readonly storageService: IStorageService, @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, ) { - super(SyncResource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.GlobalState, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register( Event.any( diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index b2d71b4cfac..4df153850ff 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -20,6 +20,7 @@ import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData, import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources'; +import { IStorageService } from 'vs/platform/storage/common/storage'; interface ISyncContent { mac?: string; @@ -42,10 +43,11 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, + @IStorageService storageService: IStorageService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } async pull(): Promise { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 4a7b0151ba3..810f98dda03 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -19,6 +19,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources'; +import { IStorageService } from 'vs/platform/storage/common/storage'; export interface ISettingsSyncContent { settings: string; @@ -41,6 +42,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { constructor( @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, + @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -50,7 +52,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { @ITelemetryService telemetryService: ITelemetryService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, ) { - super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); + super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } protected setStatus(status: SyncStatus): void { diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 5bbfe1cd019..856cf589d82 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -16,6 +16,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { merge } from 'vs/platform/userDataSync/common/snippetsMerge'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IStorageService } from 'vs/platform/storage/common/storage'; interface ISinppetsSyncPreviewResult extends ISyncPreviewResult { readonly local: IStringDictionary; @@ -39,6 +40,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -46,7 +48,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncResource.Snippets, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); + super(SyncResource.Snippets, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this.snippetsFolder = environmentService.snippetsHome; this.snippetsPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME); this._register(this.fileService.watch(environmentService.userRoamingDataHome)); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 004d6fa5909..ae44d59d072 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -296,6 +296,7 @@ export interface IUserDataSynchroniser { getRemoteSyncResourceHandles(): Promise; getLocalSyncResourceHandles(): Promise; getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>; + getMachineId(syncResourceHandle: ISyncResourceHandle): Promise; } //#endregion @@ -349,6 +350,7 @@ export interface IUserDataSyncService { getLocalSyncResourceHandles(resource: SyncResource): Promise; getRemoteSyncResourceHandles(resource: SyncResource): Promise; getAssociatedResources(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>; + getMachineId(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise; } export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index e40608949a9..4bba8a7ce48 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -54,6 +54,7 @@ export class UserDataSyncChannel implements IServerChannel { case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]); case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0]); case 'getAssociatedResources': return this.service.getAssociatedResources(args[0], { created: args[1].created, uri: URI.revive(args[1].uri) }); + case 'getMachineId': return this.service.getMachineId(args[0], { created: args[1].created, uri: URI.revive(args[1].uri) }); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 0a772ef4a81..1d5bd441601 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -255,6 +255,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.getSynchroniser(resource).getAssociatedResources(syncResourceHandle); } + getMachineId(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise { + return this.getSynchroniser(resource).getMachineId(syncResourceHandle); + } + async isFirstTimeSyncWithMerge(): Promise { await this.checkEnablement(); if (!await this.userDataSyncStoreService.manifest()) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 6c111d035e6..be04055a9a0 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -32,7 +32,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IAction, Action } from 'vs/base/common/actions'; import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSyncWorkbenchService'; -import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; +import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class UserDataSyncViewPaneContainer extends ViewPaneContainer { @@ -96,7 +96,8 @@ export class UserDataSyncDataViews extends Disposable { const disposable = treeView.onDidChangeVisibility(visible => { if (visible && !treeView.dataProvider) { disposable.dispose(); - treeView.dataProvider = new UserDataSyncHistoryViewDataProvider(remote, this.userDataSyncService, this.userDataSyncEnablementService); + treeView.dataProvider = remote ? new RemoteUserDataSyncHistoryViewDataProvider(this.userDataSyncService, this.userDataSyncEnablementService, this.userDataSyncMachinesService) + : new LocalUserDataSyncHistoryViewDataProvider(this.userDataSyncService, this.userDataSyncEnablementService); } }); this._register(Event.any(this.userDataSyncEnablementService.onDidChangeResourceEnablement, this.userDataSyncEnablementService.onDidChangeEnablement)(() => treeView.refresh())); @@ -168,6 +169,7 @@ export class UserDataSyncDataViews extends Disposable { treeView.dataProvider = new UserDataSyncMachinesViewDataProvider(treeView, this.userDataSyncMachinesService); } }); + this._register(Event.any(this.userDataSyncEnablementService.onDidChangeResourceEnablement, this.userDataSyncEnablementService.onDidChangeEnablement)(() => treeView.refresh())); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViews([{ id, @@ -202,12 +204,17 @@ export class UserDataSyncDataViews extends Disposable { return new Promise((c, e) => { inputBox.onDidAccept(async () => { const name = inputBox.value; - if (name) { - await that.userDataSyncMachinesService.updateName(name); - await treeView.refresh(); - } inputBox.dispose(); - c(); + if (name) { + try { + await that.userDataSyncMachinesService.updateName(name); + await treeView.refresh(); + c(); + } catch (error) { + e(error); + return; + } + } }); }); } @@ -326,57 +333,124 @@ interface SyncResourceTreeItem extends ITreeItem { resourceHandle: ISyncResourceHandle; } -class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { +abstract class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider { constructor( - private readonly remote: boolean, - private readonly userDataSyncService: IUserDataSyncService, + protected readonly userDataSyncService: IUserDataSyncService, private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, ) { } async getChildren(element?: ITreeItem): Promise { if (!element) { - return ALL_SYNC_RESOURCES.map(resourceKey => ({ - handle: resourceKey, - collapsibleState: TreeItemCollapsibleState.Collapsed, - label: { label: getSyncAreaLabel(resourceKey) }, - description: !this.userDataSyncEnablementService.isEnabled() || this.userDataSyncEnablementService.isResourceEnabled(resourceKey) ? undefined : localize('not syncing', "Not syncing"), - themeIcon: FolderThemeIcon, - contextValue: resourceKey - })); + return this.getRoots(); } const syncResource = ALL_SYNC_RESOURCES.filter(key => key === element.handle)[0] as SyncResource; if (syncResource) { - const refHandles = this.remote ? await this.userDataSyncService.getRemoteSyncResourceHandles(syncResource) : await this.userDataSyncService.getLocalSyncResourceHandles(syncResource); - return refHandles.map(({ uri, created }) => { - const handle = JSON.stringify({ resource: uri.toString(), syncResource }); - return { - handle, - collapsibleState: TreeItemCollapsibleState.Collapsed, - label: { label: label(new Date(created)) }, - description: fromNow(created, true), - resourceUri: uri, - resource: syncResource, - resourceHandle: { uri, created }, - contextValue: `sync-resource-${syncResource}` - }; - }); + return this.getChildrenForSyncResource(syncResource); } if ((element).resourceHandle) { - const associatedResources = await this.userDataSyncService.getAssociatedResources((element).resource, (element).resourceHandle); - return associatedResources.map(({ resource, comparableResource }) => { - const handle = JSON.stringify({ resource: resource.toString(), comparableResource: comparableResource?.toString() }); - return { - handle, - collapsibleState: TreeItemCollapsibleState.None, - resourceUri: resource, - command: { id: `workbench.actions.sync.commpareWithLocal`, title: '', arguments: [{ $treeViewId: '', $treeItemHandle: handle }] }, - contextValue: `sync-associatedResource-${(element).resource}` - }; - }); + return this.getChildrenForSyncResourceTreeItem(element); } return []; } + + protected async getRoots(): Promise { + return ALL_SYNC_RESOURCES.map(resourceKey => ({ + handle: resourceKey, + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: { label: getSyncAreaLabel(resourceKey) }, + description: !this.userDataSyncEnablementService.isEnabled() || this.userDataSyncEnablementService.isResourceEnabled(resourceKey) ? undefined : localize('not syncing', "Not syncing"), + themeIcon: FolderThemeIcon, + contextValue: resourceKey + })); + } + + protected async getChildrenForSyncResource(syncResource: SyncResource): Promise { + const refHandles = await this.getSyncResourceHandles(syncResource); + return refHandles.map(({ uri, created }) => { + const handle = JSON.stringify({ resource: uri.toString(), syncResource }); + return { + handle, + collapsibleState: TreeItemCollapsibleState.Collapsed, + label: { label: label(new Date(created)) }, + description: fromNow(created, true), + resourceUri: uri, + resource: syncResource, + resourceHandle: { uri, created }, + contextValue: `sync-resource-${syncResource}` + }; + }); + } + + protected async getChildrenForSyncResourceTreeItem(element: SyncResourceTreeItem): Promise { + const associatedResources = await this.userDataSyncService.getAssociatedResources((element).resource, (element).resourceHandle); + return associatedResources.map(({ resource, comparableResource }) => { + const handle = JSON.stringify({ resource: resource.toString(), comparableResource: comparableResource?.toString() }); + return { + handle, + collapsibleState: TreeItemCollapsibleState.None, + resourceUri: resource, + command: { id: `workbench.actions.sync.commpareWithLocal`, title: '', arguments: [{ $treeViewId: '', $treeItemHandle: handle }] }, + contextValue: `sync-associatedResource-${(element).resource}` + }; + }); + } + + protected abstract getSyncResourceHandles(syncResource: SyncResource): Promise; +} + +class LocalUserDataSyncHistoryViewDataProvider extends UserDataSyncHistoryViewDataProvider { + + protected getSyncResourceHandles(syncResource: SyncResource): Promise { + return this.userDataSyncService.getLocalSyncResourceHandles(syncResource); + } +} + +class RemoteUserDataSyncHistoryViewDataProvider extends UserDataSyncHistoryViewDataProvider { + + private machinesPromise: Promise | undefined; + + constructor( + userDataSyncService: IUserDataSyncService, + userDataSyncEnablementService: IUserDataSyncEnablementService, + private readonly userDataSyncMachinesService: IUserDataSyncMachinesService, + ) { + super(userDataSyncService, userDataSyncEnablementService); + } + + async getChildren(element?: ITreeItem): Promise { + if (!element) { + this.machinesPromise = undefined; + } + return super.getChildren(element); + } + + private getMachines(): Promise { + if (this.machinesPromise === undefined) { + this.machinesPromise = this.userDataSyncMachinesService.getMachines(); + } + return this.machinesPromise; + } + + protected getSyncResourceHandles(syncResource: SyncResource): Promise { + return this.userDataSyncService.getRemoteSyncResourceHandles(syncResource); + } + + protected async getChildrenForSyncResourceTreeItem(element: SyncResourceTreeItem): Promise { + const children = await super.getChildrenForSyncResourceTreeItem(element); + const machineId = await this.userDataSyncService.getMachineId(element.resource, element.resourceHandle); + if (machineId) { + const machines = await this.getMachines(); + const machine = machines.find(({ id }) => id === machineId); + children.push({ + handle: machineId, + label: { label: machine?.name || machineId }, + collapsibleState: TreeItemCollapsibleState.None, + themeIcon: Codicon.vm, + }); + } + return children; + } } class UserDataSyncMachinesViewDataProvider implements ITreeViewDataProvider { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index e53ff702420..dfa5189671c 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -116,6 +116,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return result.map(({ resource, comparableResource }) => ({ resource: URI.revive(resource), comparableResource: URI.revive(comparableResource) })); } + async getMachineId(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise { + return this.channel.call('getMachineId', [resource, syncResourceHandle]); + } + private async updateStatus(status: SyncStatus): Promise { this._status = status; this._onDidChangeStatus.fire(status);