diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 536613d4001..68d508d54a7 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -226,7 +226,7 @@ export abstract class AbstractSynchroniser extends Disposable { async getSyncPreview(): Promise { if (!this.isEnabled()) { - return { hasLocalChanged: false, hasRemoteChanged: false }; + return { hasLocalChanged: false, hasRemoteChanged: false, isLastSyncFromCurrentMachine: false }; } const lastSyncUserData = await this.getLastSyncUserData(); @@ -269,6 +269,11 @@ export abstract class AbstractSynchroniser extends Disposable { return !!lastSyncData; } + protected async isLastSyncFromCurrentMachine(remoteUserData: IRemoteUserData): Promise { + const machineId = await this.currentMachineIdPromise; + return !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId; + } + async getRemoteSyncResourceHandles(): Promise { const handles = await this.userDataSyncStoreService.getAllRefs(this.resource); return handles.map(({ created, ref }) => ({ created, uri: this.toRemoteBackupResource(ref) })); diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 1741086e3db..866ad0804f3 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -12,7 +12,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; @@ -95,7 +94,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, - hasRemoteChanged: remote !== null + hasRemoteChanged: remote !== null, + isLastSyncFromCurrentMachine: false }); } @@ -131,7 +131,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, - hasRemoteChanged: remote !== null + hasRemoteChanged: remote !== null, + isLastSyncFromCurrentMachine: false }, true); this.logService.info(`${this.syncResourceLogLabel}: Finished pushing extensions.`); @@ -196,7 +197,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse try { const installedExtensions = await this.extensionManagementService.getInstalled(); const localExtensions = this.getLocalExtensions(installedExtensions); - if (isNonEmptyArray(localExtensions)) { + if (localExtensions.some(e => e.installed || e.disabled)) { return true; } } catch (error) { @@ -221,14 +222,23 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse await this.apply({ added, removed, updated, remote: syncExtensions, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, - hasRemoteChanged: true + hasRemoteChanged: true, + isLastSyncFromCurrentMachine: false }); } protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null; - const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; + const isLastSyncFromCurrentMachine = await this.isLastSyncFromCurrentMachine(remoteUserData); + let lastSyncExtensions: ISyncExtension[] | null = null; + if (lastSyncUserData === null) { + if (isLastSyncFromCurrentMachine) { + lastSyncExtensions = await this.parseAndMigrateExtensions(remoteUserData.syncData!); + } + } else { + lastSyncExtensions = await this.parseAndMigrateExtensions(lastSyncUserData.syncData!); + } const installedExtensions = await this.extensionManagementService.getInstalled(); const localExtensions = this.getLocalExtensions(installedExtensions); @@ -252,7 +262,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse localExtensions, lastSyncUserData, hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, - hasRemoteChanged: remote !== null + hasRemoteChanged: remote !== null, + isLastSyncFromCurrentMachine }; } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index ec24f757805..f19f415fe67 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -93,7 +93,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs local, remote, remoteUserData, localUserData: localGlobalState, lastSyncUserData, skippedStorageKeys: skipped, hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0, - hasRemoteChanged: remote !== null + hasRemoteChanged: remote !== null, + isLastSyncFromCurrentMachine: false }); } @@ -127,7 +128,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData, skippedStorageKeys: [], hasLocalChanged: false, - hasRemoteChanged: true + hasRemoteChanged: true, + isLastSyncFromCurrentMachine: false }, true); this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`); @@ -208,13 +210,22 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs local, remote: syncGlobalState.storage, remoteUserData, localUserData, lastSyncUserData, skippedStorageKeys: skipped, hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0, - hasRemoteChanged: true + hasRemoteChanged: true, + isLastSyncFromCurrentMachine: false, }); } protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; - const lastSyncGlobalState: IGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; + const isLastSyncFromCurrentMachine = await this.isLastSyncFromCurrentMachine(remoteUserData); + let lastSyncGlobalState: IGlobalState | null = null; + if (lastSyncUserData === null) { + if (isLastSyncFromCurrentMachine) { + lastSyncGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; + } + } else { + lastSyncGlobalState = lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; + } const localGloablState = await this.getLocalGlobalState(); @@ -230,7 +241,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData, skippedStorageKeys: skipped, hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0, - hasRemoteChanged: remote !== null + hasRemoteChanged: remote !== null, + isLastSyncFromCurrentMachine }; } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index da61c3e8931..07368f5c685 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -76,6 +76,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem hasConflicts: false, hasLocalChanged: true, hasRemoteChanged: false, + isLastSyncFromCurrentMachine: false })); await this.apply(); } @@ -117,6 +118,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem hasLocalChanged: false, hasRemoteChanged: true, hasConflicts: false, + isLastSyncFromCurrentMachine: false })); await this.apply(true); } @@ -227,6 +229,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem hasConflicts: false, hasLocalChanged: true, hasRemoteChanged: true, + isLastSyncFromCurrentMachine: false })); await this.apply(); } @@ -287,7 +290,16 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken = CancellationToken.None): Promise { const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; - const lastSyncContent = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null; + const isLastSyncFromCurrentMachine = await this.isLastSyncFromCurrentMachine(remoteUserData); + let lastSyncContent: string | null = null; + if (lastSyncUserData === null) { + if (isLastSyncFromCurrentMachine) { + lastSyncContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; + } + } else { + lastSyncContent = lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null; + } + // Get file content last to get the latest const fileContent = await this.getLocalFileContent(); const formattingOptions = await this.getFormattingOptions(); @@ -332,7 +344,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []); - return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; + return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts, isLastSyncFromCurrentMachine }; } getKeybindingsContentFromSyncContent(syncContent: string): string | null { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 3c5d1a61311..5e87e009449 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -94,6 +94,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { hasLocalChanged: true, hasRemoteChanged: false, hasConflicts: false, + isLastSyncFromCurrentMachine: false })); await this.apply(); @@ -140,6 +141,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { hasRemoteChanged: true, hasLocalChanged: false, hasConflicts: false, + isLastSyncFromCurrentMachine: false })); await this.apply(true); @@ -276,6 +278,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { hasLocalChanged: true, hasRemoteChanged: true, hasConflicts: false, + isLastSyncFromCurrentMachine: false })); await this.apply(); @@ -340,7 +343,15 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { const fileContent = await this.getLocalFileContent(); const formattingOptions = await this.getFormattingOptions(); const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - const lastSettingsSyncContent = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null; + const isLastSyncFromCurrentMachine = await this.isLastSyncFromCurrentMachine(remoteUserData); + let lastSettingsSyncContent: ISettingsSyncContent | null = null; + if (lastSyncUserData === null) { + if (isLastSyncFromCurrentMachine) { + lastSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); + } + } else { + lastSettingsSyncContent = this.getSettingsSyncContent(lastSyncUserData); + } let content: string | null = null; let hasLocalChanged: boolean = false; @@ -375,7 +386,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []); - return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; + return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts, isLastSyncFromCurrentMachine }; } private getSettingsSyncContent(remoteUserData: IRemoteUserData): ISettingsSyncContent | null { diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 856cf589d82..b28535ea524 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -99,7 +99,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}, hasLocalChanged: Object.keys(added).length > 0 || removed.length > 0 || Object.keys(updated).length > 0, - hasRemoteChanged: remote !== null + hasRemoteChanged: remote !== null, + isLastSyncFromCurrentMachine: false })); await this.apply(); } @@ -135,7 +136,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}, hasLocalChanged: Object.keys(added).length > 0 || removed.length > 0 || Object.keys(updated).length > 0, - hasRemoteChanged: remote !== null + hasRemoteChanged: remote !== null, + isLastSyncFromCurrentMachine: false })); await this.apply(true); @@ -266,7 +268,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ added, removed, updated, remote: snippets, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {}, hasLocalChanged: Object.keys(added).length > 0 || removed.length > 0 || Object.keys(updated).length > 0, - hasRemoteChanged: true + hasRemoteChanged: true, + isLastSyncFromCurrentMachine: false })); await this.apply(); } @@ -300,7 +303,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD private async doGeneratePreview(local: IStringDictionary, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: IStringDictionary = {}, token: CancellationToken = CancellationToken.None): Promise { const localSnippets = this.toSnippetsContents(local); const remoteSnippets: IStringDictionary | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null; - const lastSyncSnippets: IStringDictionary | null = lastSyncUserData && lastSyncUserData.syncData ? this.parseSnippets(lastSyncUserData.syncData) : null; + const isLastSyncFromCurrentMachine = await this.isLastSyncFromCurrentMachine(remoteUserData); + + let lastSyncSnippets: IStringDictionary | null = null; + if (lastSyncUserData === null) { + if (isLastSyncFromCurrentMachine) { + lastSyncSnippets = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null; + } + } else { + lastSyncSnippets = lastSyncUserData.syncData ? this.parseSnippets(lastSyncUserData.syncData) : null; + } if (remoteSnippets) { this.logService.trace(`${this.syncResourceLogLabel}: Merging remote snippets with local snippets...`); @@ -342,7 +354,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remote: mergeResult.remote, resolvedConflicts, hasLocalChanged: Object.keys(mergeResult.added).length > 0 || mergeResult.removed.length > 0 || Object.keys(mergeResult.updated).length > 0, - hasRemoteChanged: mergeResult.remote !== null + hasRemoteChanged: mergeResult.remote !== null, + isLastSyncFromCurrentMachine }; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 40a8afea10d..57e993d84b4 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -267,6 +267,7 @@ export interface ISyncResourceHandle { export type Conflict = { remote: URI, local: URI }; export interface ISyncPreviewResult { + readonly isLastSyncFromCurrentMachine: boolean; readonly hasLocalChanged: boolean; readonly hasRemoteChanged: boolean; } @@ -344,7 +345,7 @@ export interface IUserDataSyncService { reset(): Promise; resetLocal(): Promise; - isFirstTimeSyncWithMerge(): Promise; + isFirstTimeSyncingWithAnotherMachine(): Promise; resolveContent(resource: URI): Promise; acceptConflict(conflictResource: URI, content: string): Promise; diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index af7b4efd8c9..9608c10359d 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -48,7 +48,7 @@ export class UserDataSyncChannel implements IServerChannel { case 'replace': return this.service.replace(URI.revive(args[0])); case 'reset': return this.service.reset(); case 'resetLocal': return this.service.resetLocal(); - case 'isFirstTimeSyncWithMerge': return this.service.isFirstTimeSyncWithMerge(); + case 'isFirstTimeSyncingWithAnotherMachine': return this.service.isFirstTimeSyncingWithAnotherMachine(); case 'acceptConflict': return this.service.acceptConflict(URI.revive(args[0]), args[1]); case 'resolveContent': return this.service.resolveContent(URI.revive(args[0])); case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index c2508e82b78..f57df8e4365 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -277,23 +277,35 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.getSynchroniser(resource).getMachineId(syncResourceHandle); } - async isFirstTimeSyncWithMerge(): Promise { + async isFirstTimeSyncingWithAnotherMachine(): Promise { await this.checkEnablement(); + if (!await this.userDataSyncStoreService.manifest()) { return false; } - if (await this.hasPreviouslySynced()) { + + // skip global state synchronizer + const synchronizers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.extensionsSynchroniser]; + + let hasLocalData: boolean = false; + for (const synchroniser of synchronizers) { + if (await synchroniser.hasLocalData()) { + hasLocalData = true; + break; + } + } + + if (!hasLocalData) { return false; } - if (!(await this.hasLocalData())) { - return false; - } - for (const synchroniser of [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.extensionsSynchroniser]) { + + for (const synchroniser of synchronizers) { const preview = await synchroniser.getSyncPreview(); - if (preview.hasLocalChanged || preview.hasRemoteChanged) { + if (!preview.isLastSyncFromCurrentMachine && (preview.hasLocalChanged || preview.hasRemoteChanged)) { return true; } } + return false; } @@ -329,15 +341,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return false; } - private async hasLocalData(): Promise { - for (const synchroniser of this.synchronisers) { - if (await synchroniser.hasLocalData()) { - return true; - } - } - return false; - } - private async resetRemote(): Promise { await this.checkEnablement(); try { diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 1d01143bd90..24c95ffeac4 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -62,7 +62,7 @@ class TestSynchroniser extends AbstractSynchroniser { } protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - return { hasLocalChanged: false, hasRemoteChanged: false }; + return { hasLocalChanged: false, hasRemoteChanged: false, isLastSyncFromCurrentMachine: false }; } } diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 86f379baeca..0493a52be3f 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -103,7 +103,7 @@ suite('UserDataSyncService', () => { // Sync (pull) from the test client target.reset(); - await testObject.isFirstTimeSyncWithMerge(); + await testObject.isFirstTimeSyncingWithAnotherMachine(); await testObject.pull(); assert.deepEqual(target.requests, [ @@ -143,7 +143,7 @@ suite('UserDataSyncService', () => { // Sync (pull) from the test client target.reset(); - await testObject.isFirstTimeSyncWithMerge(); + await testObject.isFirstTimeSyncingWithAnotherMachine(); await testObject.pull(); assert.deepEqual(target.requests, [ @@ -175,7 +175,7 @@ suite('UserDataSyncService', () => { // Sync (merge) from the test client target.reset(); - await testObject.isFirstTimeSyncWithMerge(); + await testObject.isFirstTimeSyncingWithAnotherMachine(); await testObject.sync(); assert.deepEqual(target.requests, [ @@ -219,7 +219,7 @@ suite('UserDataSyncService', () => { // Sync (merge) from the test client target.reset(); - await testObject.isFirstTimeSyncWithMerge(); + await testObject.isFirstTimeSyncingWithAnotherMachine(); await testObject.sync(); assert.deepEqual(target.requests, [ diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 6b80e0d7529..4a75e49a59f 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -31,7 +31,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; 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, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, ENABLE_SYNC_VIEWS_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, ENABLE_SYNC_VIEWS_COMMAND_ID, SHOW_SYNCED_DATA_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -117,7 +117,7 @@ export class UserDataSyncDataViews extends Disposable { registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.actions.showSync${remote ? 'Remote' : 'Local'}DataView`, + id: remote ? SHOW_SYNCED_DATA_COMMAND_ID : 'workbench.userDataSync.actions.showLocalBackupData', title: remote ? { value: localize('workbench.action.showSyncRemoteBackup', "Show Synced Data"), original: `Show Synced Data` } : { value: localize('workbench.action.showSyncLocalBackup', "Show Local Backup"), original: `Show Local Backup` }, diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 49edd5cf928..81a3e2c1a50 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -6,7 +6,7 @@ import { IUserDataSyncService, IUserDataSyncEnablementService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNCED_DATA_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -26,6 +26,7 @@ import { canceled } from 'vs/base/common/errors'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; type UserAccountClassification = { id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' }; @@ -89,6 +90,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, + @ICommandService private readonly commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, ) { super(); @@ -235,31 +237,36 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } private async handleFirstTimeSync(): Promise { - const isFirstSyncWithMerge = await this.userDataSyncService.isFirstTimeSyncWithMerge(); - if (!isFirstSyncWithMerge) { + const isFirstTimeSyncingWithAnotherMachine = await this.userDataSyncService.isFirstTimeSyncingWithAnotherMachine(); + if (!isFirstTimeSyncingWithAnotherMachine) { return; } const result = await this.dialogService.show( Severity.Info, - localize('firs time sync', "Sync"), + localize('Replace or Merge', "Replace or Merge"), [ - localize('merge', "Merge"), + localize('show synced data', "Show Synced Data"), localize('cancel', "Cancel"), - localize('replace', "Replace Local"), + localize('merge', "Merge"), + localize('replace local', "Replace Local"), ], { cancelId: 1, - detail: localize('first time sync detail', "It looks like this is the first time sync is set up.\nWould you like to merge or replace with the data from the cloud?"), + detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to replace or merge with the synced data?"), } ); switch (result.choice) { case 0: - this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' }); - break; + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'showSyncedData' }); + await this.commandService.executeCommand(SHOW_SYNCED_DATA_COMMAND_ID); + throw canceled(); case 1: this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' }); throw canceled(); case 2: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' }); + break; + case 3: this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'replace-local' }); await this.userDataSyncService.pull(); break; diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 798b6377d67..2ee77970650 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -57,3 +57,4 @@ export const CONTEXT_ENABLE_VIEWS = new RawContextKey(`showUserDataSync export const ENABLE_SYNC_VIEWS_COMMAND_ID = 'workbench.userDataSync.actions.enableViews'; export const CONFIGURE_SYNC_COMMAND_ID = 'workbench.userDataSync.actions.configure'; export const SHOW_SYNC_LOG_COMMAND_ID = 'workbench.userDataSync.actions.showLog'; +export const SHOW_SYNCED_DATA_COMMAND_ID = 'workbench.userDataSync.actions.showSyncedData'; diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index dfa5189671c..283000480f8 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -89,8 +89,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('resetLocal'); } - isFirstTimeSyncWithMerge(): Promise { - return this.channel.call('isFirstTimeSyncWithMerge'); + isFirstTimeSyncingWithAnotherMachine(): Promise { + return this.channel.call('isFirstTimeSyncingWithAnotherMachine'); } acceptConflict(conflict: URI, content: string): Promise {