diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 8ecbd09d7ac88070b6e3f8f9ae6c3f5584e0f291..06b1d0d95d74ef99c29a57cb1015a65c82964e7c 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, ISyncResourceHandle, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources'; import { CancelablePromise } from 'vs/base/common/async'; @@ -144,6 +144,16 @@ export abstract class AbstractSynchroniser extends Disposable { } } + async getSyncPreview(): Promise { + if (!this.isEnabled()) { + return { hasLocalChanged: false, hasRemoteChanged: false }; + } + + const lastSyncUserData = await this.getLastSyncUserData(); + const remoteUserData = await this.getRemoteUserData(lastSyncUserData); + return this.getPreview(remoteUserData, lastSyncUserData); + } + protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { // current version is not compatible with cloud version @@ -285,15 +295,14 @@ export abstract class AbstractSynchroniser extends Disposable { protected abstract readonly version: number; protected abstract performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; + protected abstract getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; } -export interface IFileSyncPreviewResult { +export interface IFileSyncPreviewResult extends ISyncPreviewResult { readonly fileContent: IFileContent | null; readonly remoteUserData: IRemoteUserData; readonly lastSyncUserData: IRemoteUserData | null; readonly content: string | null; - readonly hasLocalChanged: boolean; - readonly hasRemoteChanged: boolean; readonly hasConflicts: boolean; } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 70b478c182855ca46fd0c33e274fcd6a4df7aa97..01dd9e12658d1ffd5184bae123c71f0cdaa2db8e 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -20,7 +20,7 @@ import { joinPath, dirname, basename } from 'vs/base/common/resources'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; -interface ISyncPreviewResult { +interface IExtensionsSyncPreviewResult extends ISyncPreviewResult { readonly localExtensions: ISyncExtension[]; readonly remoteUserData: IRemoteUserData; readonly lastSyncUserData: ILastSyncUserData | null; @@ -82,7 +82,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const localExtensions = await this.getLocalExtensions(); const remoteExtensions = this.parseExtensions(remoteUserData.syncData); const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions()); - await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }); + await this.apply({ + added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, + hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, + hasRemoteChanged: remote !== null + }); } // No remote exists to pull @@ -112,7 +116,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions()); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }, true); + await this.apply({ + added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData, + hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, + hasRemoteChanged: remote !== null + }, true); this.logService.info(`${this.syncResourceLogLabel}: Finished pushing extensions.`); } finally { @@ -168,7 +176,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return SyncStatus.Idle; } - private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { + protected async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? this.parseExtensions(remoteUserData.syncData) : null; const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? this.parseExtensions(lastSyncUserData.syncData!) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; @@ -183,22 +191,31 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions()); - return { added, removed, updated, remote, skippedExtensions, remoteUserData, localExtensions, lastSyncUserData }; + return { + added, + removed, + updated, + remote, + skippedExtensions, + remoteUserData, + localExtensions, + lastSyncUserData, + hasLocalChanged: added.length > 0 || removed.length > 0 || updated.length > 0, + hasRemoteChanged: remote !== null + }; } private getIgnoredExtensions() { return this.configurationService.getValue('sync.ignoredExtensions') || []; } - private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions }: ISyncPreviewResult, forcePush?: boolean): Promise { + private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions, hasLocalChanged, hasRemoteChanged }: IExtensionsSyncPreviewResult, forcePush?: boolean): Promise { - const hasChanges = added.length || removed.length || updated.length || remote; - - if (!hasChanges) { + if (!hasLocalChanged && !hasRemoteChanged) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing extensions.`); } - if (added.length || removed.length || updated.length) { + if (hasLocalChanged) { // back up all disabled or market place extensions const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid); await this.backupLocal(JSON.stringify(backUpExtensions)); diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 3c11106f1871f61a1f7f346eead4bd85ea3cfb2e..e7beda1be2fc531dc59c229a04995a33875b20e5 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -25,7 +25,7 @@ import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDa const argvStoragePrefx = 'globalState.argv.'; const argvProperties: string[] = ['locale']; -interface ISyncPreviewResult { +interface IGlobalSyncPreviewResult extends ISyncPreviewResult { readonly local: { added: IStringDictionary, removed: string[], updated: IStringDictionary }; readonly remote: IStringDictionary | null; readonly localUserData: IGlobalState; @@ -74,7 +74,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const localGlobalState = await this.getLocalGlobalState(); const remoteGlobalState: IGlobalState = JSON.parse(remoteUserData.syncData.content); const { local, remote } = merge(localGlobalState.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), this.logService); - await this.apply({ local, remote, remoteUserData, localUserData: localGlobalState, lastSyncUserData }); + await this.apply({ + local, remote, remoteUserData, localUserData: localGlobalState, lastSyncUserData, + hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0, + hasRemoteChanged: remote !== null + }); } // No remote exists to pull @@ -103,7 +107,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const localUserData = await this.getLocalGlobalState(); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - await this.apply({ local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData }, true); + await this.apply({ + local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData, + hasLocalChanged: false, + hasRemoteChanged: true + }, true); this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`); } finally { @@ -159,7 +167,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return SyncStatus.Idle; } - private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + protected async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | 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; @@ -173,13 +181,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const { local, remote } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), this.logService); - return { local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData }; + return { + local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData, + hasLocalChanged: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0, + hasRemoteChanged: remote !== null + }; } - private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData }: ISyncPreviewResult, forcePush?: boolean): Promise { - - const hasLocalChanged = Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0; - const hasRemoteChanged = remote !== null; + private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData, hasLocalChanged, hasRemoteChanged }: IGlobalSyncPreviewResult, forcePush?: boolean): Promise { if (!hasLocalChanged && !hasRemoteChanged) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`); diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 0ea9b58ccdde4f756e7fb07ba4c6f4130c866944..4d16ffb66e9936f469d290c0157f3a2aee934419 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -258,7 +258,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem this.syncPreviewResultPromise = null; } - private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + protected getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { if (!this.syncPreviewResultPromise) { this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token)); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 9b4d848b5a6ea1aa41bea98e5680b48744fa369d..a908a8e5c3a5ba1840a810af16a4a4a15aba0a54 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -309,7 +309,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { this.syncPreviewResultPromise = null; } - private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise { + protected getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[] = []): Promise { if (!this.syncPreviewResultPromise) { this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, resolvedConflicts, token)); } diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index e12cef7ce1fc70a62586f19f75f6539e2a73a1f0..3afd332c520498684b9d919fe4dbc00b70d95b2c 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, UserDataSyncError, UserDataSyncErrorCode, ISyncResourceHandle, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -17,7 +17,7 @@ import { merge } from 'vs/platform/userDataSync/common/snippetsMerge'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -interface ISyncPreviewResult { +interface ISinppetsSyncPreviewResult extends ISyncPreviewResult { readonly local: IStringDictionary; readonly remoteUserData: IRemoteUserData; readonly lastSyncUserData: IRemoteUserData | null; @@ -34,7 +34,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD protected readonly version: number = 1; private readonly snippetsFolder: URI; private readonly snippetsPreviewFolder: URI; - private syncPreviewResultPromise: CancelablePromise | null = null; + private syncPreviewResultPromise: CancelablePromise | null = null; constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -94,8 +94,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD const localSnippets = this.toSnippetsContents(local); const remoteSnippets = this.parseSnippets(remoteUserData.syncData); const { added, updated, remote, removed } = merge(localSnippets, remoteSnippets, localSnippets); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ - added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {} + 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 })); await this.apply(); } @@ -128,8 +130,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD const { added, removed, updated, remote } = merge(localSnippets, null, null); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ - added, removed, updated, remote, remoteUserData, local, lastSyncUserData, conflicts: [], resolvedConflicts: {} + 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 })); await this.apply(true); @@ -252,7 +256,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } } - private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + protected getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { if (!this.syncPreviewResultPromise) { this.syncPreviewResultPromise = createCancelablePromise(token => this.getSnippetsFileContents() .then(local => this.generatePreview(local, remoteUserData, lastSyncUserData, {}, token))); @@ -274,7 +278,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } } - private async generatePreview(local: IStringDictionary, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: IStringDictionary, token: CancellationToken): Promise { + private async generatePreview(local: IStringDictionary, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: IStringDictionary, token: CancellationToken): Promise { const localSnippets = this.toSnippetsContents(local); const remoteSnippets: IStringDictionary | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null; const lastSyncSnippets: IStringDictionary | null = lastSyncUserData ? this.parseSnippets(lastSyncUserData.syncData!) : null; @@ -309,7 +313,18 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } } - return { remoteUserData, local, lastSyncUserData, added: mergeResult.added, removed: mergeResult.removed, updated: mergeResult.updated, conflicts, remote: mergeResult.remote, resolvedConflicts }; + return { + remoteUserData, local, + lastSyncUserData, + added: mergeResult.added, + removed: mergeResult.removed, + updated: mergeResult.updated, + conflicts, + 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 + }; } private async apply(forcePush?: boolean): Promise { @@ -317,15 +332,13 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD return; } - let { added, removed, updated, local, remote, remoteUserData, lastSyncUserData } = await this.syncPreviewResultPromise; + let { added, removed, updated, local, remote, remoteUserData, lastSyncUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; - const hasChanges = Object.keys(added).length || removed.length || Object.keys(updated).length || remote; - - if (!hasChanges) { + if (!hasLocalChanged && !hasRemoteChanged) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`); } - if (Object.keys(added).length || removed.length || Object.keys(updated).length) { + if (hasLocalChanged) { // back up all snippets await this.backupLocal(JSON.stringify(this.toSnippetsContents(local))); await this.updateLocalSnippets(added, removed, updated, local); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 53134ef71f73e6ffeea5eba46cba650cfaa8a66a..f88be9c8d933be8c29325e57ba18d292b7b5234b 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -254,6 +254,11 @@ export interface ISyncResourceHandle { export type Conflict = { remote: URI, local: URI }; +export interface ISyncPreviewResult { + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; +} + export interface IUserDataSynchroniser { readonly resource: SyncResource; @@ -268,6 +273,7 @@ export interface IUserDataSynchroniser { sync(ref?: string): Promise; stop(): Promise; + getSyncPreview(): Promise hasPreviouslySynced(): Promise hasLocalData(): Promise; resetLocal(): Promise; diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index d601761a2716ce8c604cae9a67d8cfaaaf928acb..0ce8e2b6422fe4d51292629ccc2cd27b45626620 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -217,7 +217,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (await this.hasPreviouslySynced()) { return false; } - return await this.hasLocalData(); + if (!(await this.hasLocalData())) { + return false; + } + for (const synchroniser of this.synchronisers) { + const preview = await synchroniser.getSyncPreview(); + if (preview.hasLocalChanged || preview.hasRemoteChanged) { + return true; + } + } + return false; } async reset(): Promise { diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 9b9ffb82e55e283d7368cb3f6d5d37df544efefb..94eeeed83a3f416a8c51c30c7f963232a51d70b7 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncEnablementService, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; @@ -49,6 +49,10 @@ class TestSynchroniser extends AbstractSynchroniser { this.syncBarrier.open(); } + protected async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { + return { hasLocalChanged: false, hasRemoteChanged: false }; + } + } suite('TestSynchronizer', () => { diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index d51a3c11e911f219ea9a37cfd458c4fa41447a2d..fc18f89ff08aadd96a345342dbd3003e7d9b29f6 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -103,17 +103,18 @@ suite('UserDataSyncService', () => { await testObject.pull(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, - // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, - // Snippets { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, - // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - // Extensions + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + /* pull */ + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]); @@ -143,17 +144,14 @@ suite('UserDataSyncService', () => { await testObject.pull(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, - // Keybindings + /* pull */ + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, - // Snippets { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, - // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - // Extensions { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]); @@ -178,18 +176,19 @@ suite('UserDataSyncService', () => { await testObject.sync(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, + /* sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, - // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, - // Snippets { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, - // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - // Extensions { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]); @@ -220,21 +219,19 @@ suite('UserDataSyncService', () => { await testObject.sync(); assert.deepEqual(target.requests, [ - // Manifest + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, + { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, + + /* first time sync */ { type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }, - // Settings { type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } }, - // Keybindings { type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } }, - // Snippets { type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} }, { type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } }, - // Global state { type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} }, - // Extensions { type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} }, ]);