diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 466d0f81a84..17671187059 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -53,20 +53,38 @@ function isSyncData(thing: any): thing is ISyncData { return false; } -export interface IMergableResourcePreview extends IBaseResourcePreview { +export interface IResourcePreview { + + readonly remoteResource: URI; readonly remoteContent: string | null; + readonly remoteChange: Change; + + readonly localResource: URI; readonly localContent: string | null; - readonly previewContent: string | null; - readonly acceptedContent: string | null; + readonly localChange: Change; + + readonly previewResource: URI; +} + +export interface IAcceptResult { + readonly content: string | null; + readonly localChange: Change; + readonly remoteChange: Change; +} + +export interface IMergeResult extends IAcceptResult { readonly hasConflicts: boolean; } -export type IResourcePreview = Omit; +export interface IResourcePreviewResult extends IBaseResourcePreview, IResourcePreview { + mergeResult?: IMergeResult; + acceptResult?: IAcceptResult; +} export interface ISyncResourcePreview extends IBaseSyncResourcePreview { readonly remoteUserData: IRemoteUserData; readonly lastSyncUserData: IRemoteUserData | null; - readonly resourcePreviews: IMergableResourcePreview[]; + readonly resourcePreviews: IResourcePreviewResult[]; } export abstract class AbstractSynchroniser extends Disposable { @@ -82,10 +100,10 @@ export abstract class AbstractSynchroniser extends Disposable { private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - private _conflicts: IMergableResourcePreview[] = []; - get conflicts(): IMergableResourcePreview[] { return this._conflicts; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _conflicts: IResourcePreviewResult[] = []; + get conflicts(): IResourcePreviewResult[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; private readonly localChangeTriggerScheduler = new RunOnceScheduler(() => this.doTriggerLocalChange(), 50); private readonly _onDidChangeLocal: Emitter = this._register(new Emitter()); @@ -162,53 +180,6 @@ export abstract class AbstractSynchroniser extends Disposable { } } - async pull(): Promise { - if (!this.isEnabled()) { - this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling ${this.syncResourceLogLabel.toLowerCase()} as it is disabled.`); - return; - } - - await this.stop(); - - try { - this.logService.info(`${this.syncResourceLogLabel}: Started pulling ${this.syncResourceLogLabel.toLowerCase()}...`); - this.setStatus(SyncStatus.Syncing); - - const lastSyncUserData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - const preview = await this.generatePullPreview(remoteUserData, lastSyncUserData, CancellationToken.None); - - await this.applyPreview(remoteUserData, lastSyncUserData, preview, false); - this.logService.info(`${this.syncResourceLogLabel}: Finished pulling ${this.syncResourceLogLabel.toLowerCase()}.`); - } finally { - this.setStatus(SyncStatus.Idle); - } - } - - async push(): Promise { - if (!this.isEnabled()) { - this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing ${this.syncResourceLogLabel.toLowerCase()} as it is disabled.`); - return; - } - - this.stop(); - - try { - this.logService.info(`${this.syncResourceLogLabel}: Started pushing ${this.syncResourceLogLabel.toLowerCase()}...`); - this.setStatus(SyncStatus.Syncing); - - const lastSyncUserData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - const preview = await this.generatePushPreview(remoteUserData, lastSyncUserData, CancellationToken.None); - - await this.applyPreview(remoteUserData, lastSyncUserData, preview, true); - this.logService.info(`${this.syncResourceLogLabel}: Finished pushing ${this.syncResourceLogLabel.toLowerCase()}.`); - - } finally { - this.setStatus(SyncStatus.Idle); - } - } - async sync(manifest: IUserDataManifest | null, headers: IHeaders = {}): Promise { await this._sync(manifest, true, headers); } @@ -292,8 +263,17 @@ export abstract class AbstractSynchroniser extends Disposable { this.setStatus(SyncStatus.Syncing); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getLatestRemoteUserData(null, lastSyncUserData); - const preview = await this.generateReplacePreview(syncData, remoteUserData, lastSyncUserData); - await this.applyPreview(remoteUserData, lastSyncUserData, preview, false); + + const resourcePreviewResults = await this.generateSyncPreview(remoteUserData, lastSyncUserData, CancellationToken.None); + + const resourcePreviews: [IResourcePreview, IAcceptResult][] = []; + for (const resourcePreviewResult of resourcePreviewResults) { + /* Accept remote resource */ + const acceptResult: IAcceptResult = await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.remoteResource, resourcePreviewResult.remoteContent, CancellationToken.None); + resourcePreviews.push([resourcePreviewResult, acceptResult]); + } + + await this.applyResult(remoteUserData, lastSyncUserData, resourcePreviews, false); this.logService.info(`${this.syncResourceLogLabel}: Finished resetting ${this.resource.toLowerCase()}.`); } finally { this.setStatus(SyncStatus.Idle); @@ -384,23 +364,35 @@ export abstract class AbstractSynchroniser extends Disposable { } } - async accept(resource: URI, content: string | null): Promise { + async merge(resource: URI): Promise { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { - const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content); + const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None); + await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(mergeResult?.content || '')); + const acceptResult: IAcceptResult | undefined = mergeResult && !mergeResult.hasConflicts + ? await this.getAcceptResult(resourcePreview, resourcePreview.previewResource, undefined, CancellationToken.None) + : undefined; return { - ...updatedResourcePreview, - mergeState: MergeState.Accepted + ...resourcePreview, + mergeResult, + acceptResult, + mergeState: mergeResult.hasConflicts ? MergeState.Conflict : acceptResult ? MergeState.Accepted : MergeState.Preview, + localChange: acceptResult ? acceptResult.localChange : mergeResult.localChange, + remoteChange: acceptResult ? acceptResult.remoteChange : mergeResult.remoteChange }; }); return this.syncPreviewPromise; } - async merge(resource: URI): Promise { + async accept(resource: URI, content?: string | null): Promise { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { - const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent); + const acceptResult = await this.getAcceptResult(resourcePreview, resource, content, CancellationToken.None); return { - ...updatedResourcePreview, - mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted + ...resourcePreview, + acceptResult, + mergeResult: undefined, + mergeState: MergeState.Accepted, + localChange: acceptResult.localChange, + remoteChange: acceptResult.remoteChange }; }); return this.syncPreviewPromise; @@ -408,17 +400,21 @@ export abstract class AbstractSynchroniser extends Disposable { async discard(resource: URI): Promise { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { - await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || '')); - const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent); + const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None); + await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(mergeResult.content || '')); return { - ...updatedResourcePreview, - mergeState: MergeState.Preview + ...resourcePreview, + mergeResult: undefined, + acceptResult: undefined, + mergeState: MergeState.Preview, + localChange: mergeResult.localChange, + remoteChange: mergeResult.remoteChange }; }); return this.syncPreviewPromise; } - private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise): Promise { + private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IResourcePreviewResult) => Promise): Promise { if (!this.syncPreviewPromise) { return; } @@ -448,13 +444,6 @@ export abstract class AbstractSynchroniser extends Disposable { } } - protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise { - return { - ...resourcePreview, - acceptedContent - }; - } - private async doApply(force: boolean): Promise { if (!this.syncPreviewPromise) { return SyncStatus.Idle; @@ -463,17 +452,17 @@ export abstract class AbstractSynchroniser extends Disposable { const preview = await this.syncPreviewPromise; // check for conflicts - if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) { + if (preview.resourcePreviews.some(({ mergeResult }) => mergeResult?.hasConflicts)) { return SyncStatus.HasConflicts; } // check if all are accepted - if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) { + if (preview.resourcePreviews.some(({ localChange, remoteChange, acceptResult }) => !acceptResult)) { return SyncStatus.Syncing; } // apply preview - await this.applyPreview(preview.remoteUserData, preview.lastSyncUserData, preview.resourcePreviews, force); + await this.applyResult(preview.remoteUserData, preview.lastSyncUserData, preview.resourcePreviews.map(resourcePreview => ([resourcePreview, resourcePreview.acceptResult!])), force); // reset preview this.syncPreviewPromise = null; @@ -490,8 +479,8 @@ export abstract class AbstractSynchroniser extends Disposable { } catch (error) { /* Ignore */ } } - private updateConflicts(previews: IMergableResourcePreview[]): void { - const conflicts = previews.filter(p => p.mergeState === MergeState.Conflict); + private updateConflicts(resourcePreviews: IResourcePreviewResult[]): void { + const conflicts = resourcePreviews.filter(r => r.mergeResult?.hasConflicts); if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) { this._conflicts = conflicts; this._onDidChangeConflicts.fire(conflicts); @@ -550,7 +539,7 @@ export abstract class AbstractSynchroniser extends Disposable { if (syncPreview) { for (const resourcePreview of syncPreview.resourcePreviews) { if (isEqual(resourcePreview.acceptedResource, uri)) { - return resourcePreview.acceptedContent; + return resourcePreview.acceptResult ? resourcePreview.acceptResult.content : null; } if (isEqual(resourcePreview.remoteResource, uri)) { return resourcePreview.remoteContent; @@ -575,22 +564,47 @@ export abstract class AbstractSynchroniser extends Disposable { // For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData; - const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token); + const resourcePreviewResults = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token); - const resourcePreviews: IMergableResourcePreview[] = []; - for (const resourcePreview of result) { - if (token.isCancellationRequested) { - break; + const resourcePreviews: IResourcePreviewResult[] = []; + for (const resourcePreviewResult of resourcePreviewResults) { + const acceptedResource = resourcePreviewResult.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); + + /* No change -> Accept */ + if (resourcePreviewResult.localChange === Change.None && resourcePreviewResult.remoteChange === Change.None) { + resourcePreviews.push({ + ...resourcePreviewResult, + acceptedResource, + acceptResult: { content: null, localChange: Change.None, remoteChange: Change.None }, + mergeState: MergeState.Accepted + }); } - if (!apply) { - await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || '')); + + /* Changed -> Apply ? (Merge ? Conflict | Accept) : Preview */ + else { + /* Merge */ + const mergeResult = apply ? await this.getMergeResult(resourcePreviewResult, token) : undefined; + if (token.isCancellationRequested) { + break; + } + await this.fileService.writeFile(resourcePreviewResult.previewResource, VSBuffer.fromString(mergeResult?.content || '')); + + /* Conflict | Accept */ + const acceptResult = mergeResult && !mergeResult.hasConflicts + /* Accept if merged and there are no conflicts */ + ? await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.previewResource, undefined, token) + : undefined; + + resourcePreviews.push({ + ...resourcePreviewResult, + acceptedResource: resourcePreviewResult.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), + mergeResult, + acceptResult, + mergeState: mergeResult?.hasConflicts ? MergeState.Conflict : acceptResult ? MergeState.Accepted : MergeState.Preview, + localChange: acceptResult ? acceptResult.localChange : mergeResult ? mergeResult.localChange : resourcePreviewResult.localChange, + remoteChange: acceptResult ? acceptResult.remoteChange : mergeResult ? mergeResult.remoteChange : resourcePreviewResult.remoteChange + }); } - resourcePreviews.push({ - ...resourcePreview, - mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */ - : apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted) - : MergeState.Preview - }); } return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine }; @@ -687,11 +701,10 @@ export abstract class AbstractSynchroniser extends Disposable { } protected abstract readonly version: number; - protected abstract generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise; - protected abstract generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise; - protected abstract generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise; protected abstract generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise; - protected abstract applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IResourcePreview[], forcePush: boolean): Promise; + protected abstract getMergeResult(resourcePreview: IResourcePreview, token: CancellationToken): Promise; + protected abstract getAcceptResult(resourcePreview: IResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise; + protected abstract applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, result: [IResourcePreview, IAcceptResult][], force: boolean): Promise; } export interface IFileResourcePreview extends IResourcePreview { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 7cc8738fdf9..f5d19610f35 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -15,7 +15,7 @@ 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 { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources'; @@ -25,13 +25,17 @@ import { compare } from 'vs/base/common/strings'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { CancellationToken } from 'vs/base/common/cancellation'; -export interface IExtensionResourcePreview extends IResourcePreview { - readonly localExtensions: ISyncExtension[]; +interface IExtensionResourceMergeResult extends IAcceptResult { readonly added: ISyncExtension[]; readonly removed: IExtensionIdentifier[]; readonly updated: ISyncExtension[]; readonly remote: ISyncExtension[] | null; +} + +interface IExtensionResourcePreview extends IResourcePreview { + readonly localExtensions: ISyncExtension[]; readonly skippedExtensions: ISyncExtension[]; + readonly previewResult: IExtensionResourceMergeResult; } interface ILastSyncUserData extends IRemoteUserData { @@ -75,47 +79,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse () => undefined, 500)(() => this.triggerLocalChange())); } - protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise { - const remoteExtensions = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null; - const pullPreview = await this.getPullPreview(remoteExtensions); - return [pullPreview]; - } - - protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise { - const remoteExtensions = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null; - const pushPreview = await this.getPushPreview(remoteExtensions); - return [pushPreview]; - } - - protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - const installedExtensions = await this.extensionManagementService.getInstalled(); - const localExtensions = this.getLocalExtensions(installedExtensions); - const remoteExtensions = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null; - const syncExtensions = await this.parseAndMigrateExtensions(syncData); - const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); - const mergeResult = merge(localExtensions, syncExtensions, localExtensions, [], ignoredExtensions); - const { added, removed, updated } = mergeResult; - return [{ - localResource: this.localResource, - localContent: this.format(localExtensions), - remoteResource: this.remoteResource, - remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, - previewResource: this.previewResource, - previewContent: null, - acceptedResource: this.acceptedResource, - acceptedContent: null, - added, - removed, - updated, - remote: syncExtensions, - localExtensions, - skippedExtensions: [], - localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None, - remoteChange: Change.Modified, - hasConflicts: false, - }]; - } - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; @@ -131,32 +94,100 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse this.logService.trace(`${this.syncResourceLogLabel}: Remote extensions does not exist. Synchronizing extensions for the first time.`); } - const mergeResult = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions); - const { added, removed, updated, remote } = mergeResult; - - return [{ - localResource: this.localResource, - localContent: this.format(localExtensions), - remoteResource: this.remoteResource, - remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, - previewResource: this.previewResource, - previewContent: null, - acceptedResource: this.acceptedResource, - acceptedContent: null, + const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions); + const previewResult: IExtensionResourceMergeResult = { added, removed, updated, remote, - localExtensions, - skippedExtensions, + content: null, localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None, remoteChange: remote !== null ? Change.Modified : Change.None, - hasConflicts: false, + }; + + return [{ + skippedExtensions, + localResource: this.localResource, + localContent: this.format(localExtensions), + localExtensions, + remoteResource: this.remoteResource, + remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, + previewResource: this.previewResource, + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }]; } - protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: IExtensionResourcePreview[], force: boolean): Promise { - let { added, removed, updated, remote, skippedExtensions, localExtensions, localChange, remoteChange } = resourcePreviews[0]; + protected async getMergeResult(resourcePreview: IExtensionResourcePreview, token: CancellationToken): Promise { + return { ...resourcePreview.previewResult, hasConflicts: false }; + } + + protected async getAcceptResult(resourcePreview: IExtensionResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise { + + /* Accept local resource */ + if (isEqual(resource, this.localResource)) { + return this.acceptLocal(resourcePreview); + } + + /* Accept remote resource */ + if (isEqual(resource, this.remoteResource)) { + return this.acceptRemote(resourcePreview); + } + + /* Accept preview resource */ + if (isEqual(resource, this.previewResource)) { + return resourcePreview.previewResult; + } + + throw new Error(`Invalid Resource: ${resource.toString()}`); + } + + private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise { + const installedExtensions = await this.extensionManagementService.getInstalled(); + const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions); + const { added, removed, updated, remote } = mergeResult; + return { + content: resourcePreview.localContent, + added, + removed, + updated, + remote, + localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None, + remoteChange: remote !== null ? Change.Modified : Change.None, + }; + } + + private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise { + const installedExtensions = await this.extensionManagementService.getInstalled(); + const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); + const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; + if (remoteExtensions !== null) { + const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, resourcePreview.localExtensions, [], ignoredExtensions); + const { added, removed, updated, remote } = mergeResult; + return { + content: resourcePreview.remoteContent, + added, + removed, + updated, + remote, + localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None, + remoteChange: remote !== null ? Change.Modified : Change.None, + }; + } else { + return { + content: resourcePreview.remoteContent, + added: [], removed: [], updated: [], remote: null, + localChange: Change.None, + remoteChange: Change.None, + }; + } + } + + protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IExtensionResourcePreview, IExtensionResourceMergeResult][], force: boolean): Promise { + let { skippedExtensions, localExtensions } = resourcePreviews[0][0]; + let { added, removed, updated, remote, localChange, remoteChange } = resourcePreviews[0][1]; if (localChange === Change.None && remoteChange === Change.None) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing extensions.`); @@ -183,97 +214,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } } - protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string | null): Promise { - if (isEqual(resource, this.localResource)) { - const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; - return this.getPushPreview(remoteExtensions); - } - return { - ...resourcePreview, - acceptedContent, - hasConflicts: false, - localChange: Change.Modified, - remoteChange: Change.Modified, - }; - } - - private async getPullPreview(remoteExtensions: ISyncExtension[] | null): Promise { - const installedExtensions = await this.extensionManagementService.getInstalled(); - const localExtensions = this.getLocalExtensions(installedExtensions); - const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); - const localResource = this.localResource; - const localContent = this.format(localExtensions); - const remoteResource = this.remoteResource; - const previewResource = this.previewResource; - const acceptedResource = this.acceptedResource; - const previewContent = null; - if (remoteExtensions !== null) { - const mergeResult = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions); - const { added, removed, updated, remote } = mergeResult; - return { - localResource, - localContent, - remoteResource, - remoteContent: this.format(remoteExtensions), - previewResource, - previewContent, - acceptedResource, - acceptedContent: previewContent, - added, - removed, - updated, - remote, - localExtensions, - skippedExtensions: [], - localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None, - remoteChange: remote !== null ? Change.Modified : Change.None, - hasConflicts: false, - }; - } else { - return { - localResource, - localContent, - remoteResource, - remoteContent: null, - previewResource, - previewContent, - acceptedResource, - acceptedContent: previewContent, - added: [], removed: [], updated: [], remote: null, localExtensions, skippedExtensions: [], - localChange: Change.None, - remoteChange: Change.None, - hasConflicts: false, - }; - } - } - - private async getPushPreview(remoteExtensions: ISyncExtension[] | null): Promise { - const installedExtensions = await this.extensionManagementService.getInstalled(); - const localExtensions = this.getLocalExtensions(installedExtensions); - const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService); - const mergeResult = merge(localExtensions, null, null, [], ignoredExtensions); - const { added, removed, updated, remote } = mergeResult; - return { - localResource: this.localResource, - localContent: this.format(localExtensions), - remoteResource: this.remoteResource, - remoteContent: remoteExtensions ? this.format(remoteExtensions) : null, - previewResource: this.previewResource, - previewContent: null, - acceptedResource: this.acceptedResource, - acceptedContent: null, - added, - removed, - updated, - remote, - localExtensions, - skippedExtensions: [], - localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None, - remoteChange: remote !== null ? Change.Modified : Change.None, - hasConflicts: false, - }; - } - async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { return [{ resource: joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }]; } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index de10325223a..3866cf7e27e 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -5,7 +5,7 @@ import { IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncResourceEnablementService, - IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, USER_DATA_SYNC_SCHEME, IRemoteUserData, ISyncData, Change + IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, USER_DATA_SYNC_SCHEME, IRemoteUserData, Change } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; @@ -16,7 +16,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { edit } from 'vs/platform/userDataSync/common/content'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { parse } from 'vs/base/common/json'; -import { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; @@ -30,11 +30,15 @@ import { CancellationToken } from 'vs/base/common/cancellation'; const argvStoragePrefx = 'globalState.argv.'; const argvProperties: string[] = ['locale']; -export interface IGlobalStateResourcePreview extends IResourcePreview { +interface IGlobalStateResourceMergeResult extends IAcceptResult { readonly local: { added: IStringDictionary, removed: string[], updated: IStringDictionary }; readonly remote: IStringDictionary | null; +} + +export interface IGlobalStateResourcePreview extends IResourcePreview { readonly skippedStorageKeys: string[]; readonly localUserData: IGlobalState; + readonly previewResult: IGlobalStateResourceMergeResult; } interface ILastSyncUserData extends IRemoteUserData { @@ -76,43 +80,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs ); } - protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise { - const remoteContent = remoteUserData.syncData !== null ? remoteUserData.syncData.content : null; - const pullPreview = await this.getPullPreview(remoteContent, lastSyncUserData?.skippedStorageKeys || []); - return [pullPreview]; - } - - protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise { - const remoteContent = remoteUserData.syncData !== null ? remoteUserData.syncData.content : null; - const pushPreview = await this.getPushPreview(remoteContent); - return [pushPreview]; - } - - protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise { - const localUserData = await this.getLocalGlobalState(); - const syncGlobalState: IGlobalState = JSON.parse(syncData.content); - const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; - const mergeResult = merge(localUserData.storage, syncGlobalState.storage, localUserData.storage, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService); - const { local, skipped } = mergeResult; - return [{ - localResource: this.localResource, - localContent: this.format(localUserData), - remoteResource: this.remoteResource, - remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, - previewResource: this.previewResource, - previewContent: null, - acceptedResource: this.acceptedResource, - acceptedContent: null, - local, - remote: syncGlobalState.storage, - localUserData, - skippedStorageKeys: skipped, - localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None, - remoteChange: Change.Modified, - hasConflicts: false, - }]; - } - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise { const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; const lastSyncGlobalState: IGlobalState | null = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; @@ -125,30 +92,88 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`); } - const mergeResult = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService); - const { local, remote, skipped } = mergeResult; + const { local, remote, skipped } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService); + const previewResult: IGlobalStateResourceMergeResult = { + content: null, + local, + remote, + localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None, + remoteChange: remote !== null ? Change.Modified : Change.None, + }; return [{ + skippedStorageKeys: skipped, localResource: this.localResource, localContent: this.format(localGloablState), + localUserData: localGloablState, remoteResource: this.remoteResource, remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, previewResource: this.previewResource, - previewContent: null, - acceptedResource: this.acceptedResource, - acceptedContent: null, - local, - remote, - localUserData: localGloablState, - skippedStorageKeys: skipped, - localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None, - remoteChange: remote !== null ? Change.Modified : Change.None, - hasConflicts: false, + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }]; } - protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: IGlobalStateResourcePreview[], force: boolean): Promise { - let { local, remote, localUserData, localChange, remoteChange, skippedStorageKeys } = resourcePreviews[0]; + protected async getMergeResult(resourcePreview: IGlobalStateResourcePreview, token: CancellationToken): Promise { + return { ...resourcePreview.previewResult, hasConflicts: false }; + } + + protected async getAcceptResult(resourcePreview: IGlobalStateResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise { + + /* Accept local resource */ + if (isEqual(resource, this.localResource)) { + return this.acceptLocal(resourcePreview); + } + + /* Accept remote resource */ + if (isEqual(resource, this.remoteResource)) { + return this.acceptRemote(resourcePreview); + } + + /* Accept preview resource */ + if (isEqual(resource, this.previewResource)) { + return resourcePreview.previewResult; + } + + throw new Error(`Invalid Resource: ${resource.toString()}`); + } + + private async acceptLocal(resourcePreview: IGlobalStateResourcePreview): Promise { + return { + content: resourcePreview.localContent, + local: { added: {}, removed: [], updated: {} }, + remote: resourcePreview.localUserData.storage, + localChange: Change.None, + remoteChange: Change.Modified, + }; + } + + private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise { + if (resourcePreview.remoteContent !== null) { + const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent); + const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), resourcePreview.skippedStorageKeys, this.logService); + return { + content: resourcePreview.remoteContent, + local, + remote, + localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None, + remoteChange: remote !== null ? Change.Modified : Change.None, + }; + } else { + return { + content: resourcePreview.remoteContent, + local: { added: {}, removed: [], updated: {} }, + remote: null, + localChange: Change.None, + remoteChange: Change.None, + }; + } + } + + protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise { + let { localUserData, skippedStorageKeys } = resourcePreviews[0][0]; + let { local, remote, localChange, remoteChange } = resourcePreviews[0][1]; if (localChange === Change.None && remoteChange === Change.None) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`); @@ -178,88 +203,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string | null): Promise { - if (isEqual(this.localResource, resource)) { - return this.getPushPreview(resourcePreview.remoteContent); - } - if (isEqual(this.remoteResource, resource)) { - return this.getPullPreview(resourcePreview.remoteContent, resourcePreview.skippedStorageKeys); - } - return resourcePreview; - } - - private async getPullPreview(remoteContent: string | null, skippedStorageKeys: string[]): Promise { - const localGlobalState = await this.getLocalGlobalState(); - const localResource = this.localResource; - const localContent = this.format(localGlobalState); - const remoteResource = this.remoteResource; - const previewResource = this.previewResource; - const acceptedResource = this.acceptedResource; - const previewContent = null; - if (remoteContent !== null) { - const remoteGlobalState: IGlobalState = JSON.parse(remoteContent); - const mergeResult = merge(localGlobalState.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), skippedStorageKeys, this.logService); - const { local, remote, skipped } = mergeResult; - return { - localResource, - localContent, - remoteResource, - remoteContent: this.format(remoteGlobalState), - previewResource, - previewContent, - acceptedResource, - acceptedContent: previewContent, - local, - remote, - localUserData: localGlobalState, - skippedStorageKeys: skipped, - localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None, - remoteChange: remote !== null ? Change.Modified : Change.None, - hasConflicts: false, - }; - } else { - return { - localResource, - localContent, - remoteResource, - remoteContent: null, - previewResource, - previewContent, - acceptedResource, - acceptedContent: previewContent, - local: { added: {}, removed: [], updated: {} }, - remote: null, - localUserData: localGlobalState, - skippedStorageKeys: [], - localChange: Change.None, - remoteChange: Change.None, - hasConflicts: false, - }; - } - } - - private async getPushPreview(remoteContent: string | null): Promise { - const localUserData = await this.getLocalGlobalState(); - const remoteGlobalState: IGlobalState = remoteContent ? JSON.parse(remoteContent) : null; - return { - localResource: this.localResource, - localContent: this.format(localUserData), - remoteResource: this.remoteResource, - remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null, - previewResource: this.previewResource, - previewContent: null, - acceptedResource: this.acceptedResource, - acceptedContent: null, - local: { added: {}, removed: [], updated: {} }, - remote: localUserData.storage, - localUserData, - skippedStorageKeys: [], - localChange: Change.None, - remoteChange: Change.Modified, - hasConflicts: false, - }; - } - async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { return [{ resource: joinPath(uri, 'globalState.json'), comparableResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI }]; } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 8062afd9963..3bd1ba3a209 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -7,10 +7,9 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platfo import { UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, ISyncResourceHandle, - IRemoteUserData, ISyncData, Change + IRemoteUserData, Change } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; -import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -19,7 +18,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { AbstractJsonFileSynchroniser, IFileResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; 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'; @@ -32,6 +31,10 @@ interface ISyncContent { all?: string; } +interface IKeybindingsResourcePreview extends IFileResourcePreview { + previewResult: IMergeResult; +} + export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { protected readonly version: number = 1; @@ -55,67 +58,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } - protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - const fileContent = await this.getLocalFileContent(); - const previewContent = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent: previewContent, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent: previewContent, - localChange: previewContent !== null ? Change.Modified : Change.None, - remoteChange: Change.None, - hasConflicts: false, - }]; - } - - protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - const fileContent = await this.getLocalFileContent(); - const previewContent: string | null = fileContent ? fileContent.value.toString() : null; - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent: previewContent, - localChange: Change.None, - remoteChange: previewContent !== null ? Change.Modified : Change.None, - hasConflicts: false, - }]; - } - - protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - const fileContent = await this.getLocalFileContent(); - const previewContent = this.getKeybindingsContentFromSyncContent(syncData.content); - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent: previewContent, - localChange: previewContent !== null ? Change.Modified : Change.None, - remoteChange: previewContent !== null ? Change.Modified : Change.None, - hasConflicts: false, - }]; - } - - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { + protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null; const lastSyncContent: string | null = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null; @@ -123,7 +66,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem const fileContent = await this.getLocalFileContent(); const formattingOptions = await this.getFormattingOptions(); - let previewContent: string | null = null; + let mergedContent: string | null = null; let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; @@ -142,7 +85,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); // Sync only if there are changes if (result.hasChanges) { - previewContent = result.mergeContent; + mergedContent = result.mergeContent; hasConflicts = result.hasConflicts; hasLocalChanged = hasConflicts || result.mergeContent !== localContent; hasRemoteChanged = hasConflicts || result.mergeContent !== remoteContent; @@ -153,41 +96,80 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem // First time syncing to remote else if (fileContent) { this.logService.trace(`${this.syncResourceLogLabel}: Remote keybindings does not exist. Synchronizing keybindings for the first time.`); - previewContent = fileContent.value.toString(); + mergedContent = fileContent.value.toString(); hasRemoteChanged = true; } - if (previewContent && !token.isCancellationRequested) { - await this.fileService.writeFile(this.previewResource, VSBuffer.fromString(previewContent)); - } - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent: previewContent, - hasConflicts, + const previewResult: IMergeResult = { + content: mergedContent, localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None, remoteChange: hasRemoteChanged ? Change.Modified : Change.None, - }]; - } - - protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise { - return { - ...resourcePreview, - acceptedContent, - localChange: isEqual(resource, this.localResource) ? Change.None : Change.Modified, - remoteChange: isEqual(resource, this.remoteResource) ? Change.None : Change.Modified, + hasConflicts }; + + return [{ + fileContent, + localResource: this.localResource, + localContent: fileContent ? fileContent.value.toString() : null, + localChange: previewResult.localChange, + + remoteResource: this.remoteResource, + remoteContent, + remoteChange: previewResult.remoteChange, + + previewResource: this.previewResource, + previewResult + }]; + } - protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; + protected async getMergeResult(resourcePreview: IKeybindingsResourcePreview, token: CancellationToken): Promise { + return resourcePreview.previewResult; + } + + protected async getAcceptResult(resourcePreview: IKeybindingsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise { + + /* Accept local resource */ + if (isEqual(resource, this.localResource)) { + return { + content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null, + localChange: Change.None, + remoteChange: Change.Modified, + }; + } + + /* Accept remote resource */ + if (isEqual(resource, this.remoteResource)) { + return { + content: resourcePreview.remoteContent !== null ? this.getKeybindingsContentFromSyncContent(resourcePreview.remoteContent) : null, + localChange: Change.Modified, + remoteChange: Change.None, + }; + } + + /* Accept preview resource */ + if (isEqual(resource, this.previewResource)) { + if (content === undefined) { + return { + content: resourcePreview.previewResult.content, + localChange: resourcePreview.previewResult.localChange, + remoteChange: resourcePreview.previewResult.remoteChange, + }; + } else { + return { + content, + localChange: Change.Modified, + remoteChange: Change.Modified, + }; + } + } + + throw new Error(`Invalid Resource: ${resource.toString()}`); + } + + protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IKeybindingsResourcePreview, IAcceptResult][], force: boolean): Promise { + const { fileContent } = resourcePreviews[0][0]; + let { content, localChange, remoteChange } = resourcePreviews[0][1]; if (localChange === Change.None && remoteChange === Change.None) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 778875415fc..4bc393cca11 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -17,7 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge'; import { edit } from 'vs/platform/userDataSync/common/content'; -import { AbstractJsonFileSynchroniser, IFileResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -26,6 +26,10 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { Edit } from 'vs/base/common/jsonFormatter'; import { setProperty, applyEdits } from 'vs/base/common/jsonEdit'; +interface ISettingsResourcePreview extends IFileResourcePreview { + previewResult: IMergeResult; +} + export interface ISettingsSyncContent { settings: string; } @@ -60,102 +64,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } - protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - - const fileContent = await this.getLocalFileContent(); - const formatUtils = await this.getFormattingOptions(); - const ignoredSettings = await this.getIgnoredSettings(); - const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - - let previewContent: string | null = null; - if (remoteSettingsSyncContent) { - // Update ignored settings from local file content - previewContent = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils); - } - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent: previewContent, - localChange: previewContent !== null ? Change.Modified : Change.None, - remoteChange: Change.None, - hasConflicts: false, - }]; - } - - protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - - const fileContent = await this.getLocalFileContent(); - const formatUtils = await this.getFormattingOptions(); - const ignoredSettings = await this.getIgnoredSettings(); - const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - - let previewContent: string | null = fileContent?.value.toString() || null; - if (previewContent) { - // Remove ignored settings - previewContent = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formatUtils); - } - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent: previewContent, - localChange: Change.None, - remoteChange: previewContent !== null ? Change.Modified : Change.None, - hasConflicts: false, - }]; - } - - protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - - const fileContent = await this.getLocalFileContent(); - const formatUtils = await this.getFormattingOptions(); - const ignoredSettings = await this.getIgnoredSettings(); - - let previewContent: string | null = null; - const settingsSyncContent = this.parseSettingsSyncContent(syncData.content); - const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - if (settingsSyncContent) { - previewContent = updateIgnoredSettings(settingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils); - } - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent: previewContent, - localChange: previewContent !== null ? Change.Modified : Change.None, - remoteChange: previewContent !== null ? Change.Modified : Change.None, - hasConflicts: false, - }]; - } - - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { + protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { const fileContent = await this.getLocalFileContent(); const formattingOptions = await this.getFormattingOptions(); const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); const lastSettingsSyncContent: ISettingsSyncContent | null = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null; const ignoredSettings = await this.getIgnoredSettings(); - let acceptedContent: string | null = null; - let previewContent: string | null = null; + let mergedContent: string | null = null; let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; @@ -165,7 +81,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.validateContent(localContent); this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, [], formattingOptions); - acceptedContent = result.localContent || result.remoteContent; + mergedContent = result.localContent || result.remoteContent; hasLocalChanged = result.localContent !== null; hasRemoteChanged = result.remoteContent !== null; hasConflicts = result.hasConflicts; @@ -174,48 +90,92 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement // First time syncing to remote else if (fileContent) { this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`); - acceptedContent = fileContent.value.toString(); + mergedContent = fileContent.value.toString(); hasRemoteChanged = true; } - if (acceptedContent && !token.isCancellationRequested) { - // Remove the ignored settings from the preview. - previewContent = updateIgnoredSettings(acceptedContent, '{}', ignoredSettings, formattingOptions); - } - - return [{ - localResource: this.localResource, - fileContent, - localContent: fileContent ? fileContent.value.toString() : null, - remoteResource: this.remoteResource, - remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, - previewResource: this.previewResource, - previewContent, - acceptedResource: this.acceptedResource, - acceptedContent, + const previewResult = { + content: mergedContent, localChange: hasLocalChanged ? Change.Modified : Change.None, remoteChange: hasRemoteChanged ? Change.Modified : Change.None, - hasConflicts, + hasConflicts + }; + + return [{ + fileContent, + localResource: this.localResource, + localContent: fileContent ? fileContent.value.toString() : null, + localChange: previewResult.localChange, + + remoteResource: this.remoteResource, + remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null, + remoteChange: previewResult.remoteChange, + + previewResource: this.previewResource, + previewResult }]; } - protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise { - if (acceptedContent && (isEqual(resource, this.previewResource) || isEqual(resource, this.remoteResource))) { - const formatUtils = await this.getFormattingOptions(); - // Add ignored settings from local file content - const ignoredSettings = await this.getIgnoredSettings(); - acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); - } + protected async getMergeResult(resourcePreview: ISettingsResourcePreview, token: CancellationToken): Promise { + const formatUtils = await this.getFormattingOptions(); + const ignoredSettings = await this.getIgnoredSettings(); return { - ...resourcePreview, - acceptedContent, - localChange: isEqual(resource, this.localResource) ? Change.None : Change.Modified, - remoteChange: isEqual(resource, this.remoteResource) ? Change.None : Change.Modified, + ...resourcePreview.previewResult, + + // remove ignored settings from the preview content + content: resourcePreview.previewResult.content ? updateIgnoredSettings(resourcePreview.previewResult.content, '{}', ignoredSettings, formatUtils) : null }; } - protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0]; + protected async getAcceptResult(resourcePreview: ISettingsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise { + + const formattingOptions = await this.getFormattingOptions(); + const ignoredSettings = await this.getIgnoredSettings(); + + /* Accept local resource */ + if (isEqual(resource, this.localResource)) { + return { + /* Remove ignored settings */ + content: resourcePreview.fileContent ? updateIgnoredSettings(resourcePreview.fileContent.value.toString(), '{}', ignoredSettings, formattingOptions) : null, + localChange: Change.None, + remoteChange: Change.Modified, + }; + } + + /* Accept remote resource */ + if (isEqual(resource, this.remoteResource)) { + return { + /* Update ignored settings from local file content */ + content: resourcePreview.remoteContent !== null ? updateIgnoredSettings(resourcePreview.remoteContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formattingOptions) : null, + localChange: Change.Modified, + remoteChange: Change.None, + }; + } + + /* Accept preview resource */ + if (isEqual(resource, this.previewResource)) { + if (content === undefined) { + return { + content: resourcePreview.previewResult.content, + localChange: resourcePreview.previewResult.localChange, + remoteChange: resourcePreview.previewResult.remoteChange, + }; + } else { + return { + /* Add ignored settings from local file content */ + content: content !== null ? updateIgnoredSettings(content, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formattingOptions) : null, + localChange: Change.Modified, + remoteChange: Change.Modified, + }; + } + } + + throw new Error(`Invalid Resource: ${resource.toString()}`); + } + + protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [ISettingsResourcePreview, IAcceptResult][], force: boolean): Promise { + const { fileContent } = resourcePreviews[0][0]; + let { content, localChange, remoteChange } = resourcePreviews[0][1]; if (localChange === Change.None && remoteChange === Change.None) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index b25c7d727bd..c2b97bbde26 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -10,17 +10,25 @@ import { 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'; -import { AbstractSynchroniser, IFileResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStringDictionary } from 'vs/base/common/collections'; import { URI } from 'vs/base/common/uri'; import { joinPath, extname, relativePath, isEqualOrParent, basename, dirname } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; -import { merge, IMergeResult, areSame } from 'vs/platform/userDataSync/common/snippetsMerge'; +import { merge, IMergeResult as ISnippetsMergeResult, areSame } from 'vs/platform/userDataSync/common/snippetsMerge'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { deepClone } from 'vs/base/common/objects'; +interface ISnippetsResourcePreview extends IFileResourcePreview { + previewResult: IMergeResult; +} + +interface ISnippetsAcceptedResourcePreview extends IFileResourcePreview { + acceptResult: IAcceptResult; +} + export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { protected readonly version: number = 1; @@ -51,36 +59,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD this.triggerLocalChange(); } - protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - const resourcePreviews: IFileResourcePreview[] = []; - if (remoteUserData.syncData !== null) { - const local = await this.getSnippetsFileContents(); - const localSnippets = this.toSnippetsContents(local); - const remoteSnippets = this.parseSnippets(remoteUserData.syncData); - const mergeResult = merge(localSnippets, remoteSnippets, localSnippets); - resourcePreviews.push(...this.getResourcePreviews(mergeResult, local, remoteSnippets)); - } - return resourcePreviews; - } - - protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - const local = await this.getSnippetsFileContents(); - const localSnippets = this.toSnippetsContents(local); - const mergeResult = merge(localSnippets, null, null); - const resourcePreviews = this.getResourcePreviews(mergeResult, local, {}); - return resourcePreviews; - } - - protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - const local = await this.getSnippetsFileContents(); - const localSnippets = this.toSnippetsContents(local); - const snippets = this.parseSnippets(syncData); - const mergeResult = merge(localSnippets, snippets, localSnippets); - const resourcePreviews = this.getResourcePreviews(mergeResult, local, snippets); - return resourcePreviews; - } - - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { + protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { const local = await this.getSnippetsFileContents(); const localSnippets = this.toSnippetsContents(local); const remoteSnippets: IStringDictionary | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null; @@ -96,102 +75,72 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD return this.getResourcePreviews(mergeResult, local, remoteSnippets || {}); } - protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise { - return { - ...resourcePreview, - acceptedContent, - localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent), - remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent), - }; + protected async getMergeResult(resourcePreview: ISnippetsResourcePreview, token: CancellationToken): Promise { + return resourcePreview.previewResult; } - private computeLocalChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change { - const isRemoteResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' })); - const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder); + protected async getAcceptResult(resourcePreview: ISnippetsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise { - const previewExists = acceptedContent !== null; - const remoteExists = resourcePreview.remoteContent !== null; - const localExists = resourcePreview.fileContent !== null; - - if (isRemoteResourceAccepted) { - if (remoteExists && localExists) { - return Change.Modified; - } - if (remoteExists && !localExists) { - return Change.Added; - } - if (!remoteExists && localExists) { - return Change.Deleted; - } - return Change.None; + /* Accept local resource */ + if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))) { + return { + content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null, + localChange: Change.None, + remoteChange: resourcePreview.fileContent + ? resourcePreview.remoteContent !== null ? Change.Modified : Change.Added + : Change.Deleted + }; } - if (isPreviewResourceAccepted) { - if (previewExists && localExists) { - return Change.Modified; - } - if (previewExists && !localExists) { - return Change.Added; - } - if (!previewExists && localExists) { - return Change.Deleted; - } - return Change.None; + /* Accept remote resource */ + if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))) { + return { + content: resourcePreview.remoteContent, + localChange: resourcePreview.remoteContent !== null + ? resourcePreview.fileContent ? Change.Modified : Change.Added + : Change.Deleted, + remoteChange: Change.None, + }; } - return Change.None; + /* Accept preview resource */ + if (isEqualOrParent(resource, this.syncPreviewFolder)) { + if (content === undefined) { + return { + content: resourcePreview.previewResult.content, + localChange: resourcePreview.previewResult.localChange, + remoteChange: resourcePreview.previewResult.remoteChange, + }; + } else { + return { + content, + localChange: content === null + ? resourcePreview.fileContent !== null ? Change.Deleted : Change.None + : Change.Modified, + remoteChange: content === null + ? resourcePreview.remoteContent !== null ? Change.Deleted : Change.None + : Change.Modified + }; + } + } + + throw new Error(`Invalid Resource: ${resource.toString()}`); } - private computeRemoteChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change { - const isLocalResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' })); - const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder); - - const previewExists = acceptedContent !== null; - const remoteExists = resourcePreview.remoteContent !== null; - const localExists = resourcePreview.fileContent !== null; - - if (isLocalResourceAccepted) { - if (remoteExists && localExists) { - return Change.Modified; - } - if (remoteExists && !localExists) { - return Change.Deleted; - } - if (!remoteExists && localExists) { - return Change.Added; - } - return Change.None; - } - - if (isPreviewResourceAccepted) { - if (previewExists && remoteExists) { - return Change.Modified; - } - if (previewExists && !remoteExists) { - return Change.Added; - } - if (!previewExists && remoteExists) { - return Change.Deleted; - } - return Change.None; - } - - return Change.None; - } - - protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - if (resourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) { + protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [ISnippetsResourcePreview, IAcceptResult][], force: boolean): Promise { + const accptedResourcePreviews: ISnippetsAcceptedResourcePreview[] = resourcePreviews.map(([resourcePreview, acceptResult]) => ({ ...resourcePreview, acceptResult })); + if (accptedResourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`); } - if (resourcePreviews.some(({ localChange }) => localChange !== Change.None)) { + if (accptedResourcePreviews.some(({ localChange }) => localChange !== Change.None)) { // back up all snippets - await this.updateLocalBackup(resourcePreviews); - await this.updateLocalSnippets(resourcePreviews, force); + await this.updateLocalBackup(accptedResourcePreviews); + await this.updateLocalSnippets(accptedResourcePreviews, force); } - if (resourcePreviews.some(({ remoteChange }) => remoteChange !== Change.None)) { - remoteUserData = await this.updateRemoteSnippets(resourcePreviews, remoteUserData, force); + if (accptedResourcePreviews.some(({ remoteChange }) => remoteChange !== Change.None)) { + remoteUserData = await this.updateRemoteSnippets(accptedResourcePreviews, remoteUserData, force); } if (lastSyncUserData?.ref !== remoteUserData.ref) { @@ -201,7 +150,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized snippets`); } - for (const { previewResource } of resourcePreviews) { + for (const { previewResource } of accptedResourcePreviews) { // Delete the preview try { await this.fileService.del(previewResource); @@ -210,29 +159,38 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD } - private getResourcePreviews(mergeResult: IMergeResult, localFileContent: IStringDictionary, remoteSnippets: IStringDictionary): IFileResourcePreview[] { - const resourcePreviews: Map = new Map(); + private getResourcePreviews(snippetsMergeResult: ISnippetsMergeResult, localFileContent: IStringDictionary, remoteSnippets: IStringDictionary): ISnippetsResourcePreview[] { + const resourcePreviews: Map = new Map(); /* Snippets added remotely -> add locally */ - for (const key of Object.keys(mergeResult.local.added)) { + for (const key of Object.keys(snippetsMergeResult.local.added)) { + const previewResult: IMergeResult = { + content: snippetsMergeResult.local.added[key], + hasConflicts: false, + localChange: Change.Added, + remoteChange: Change.None, + }; resourcePreviews.set(key, { - localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: null, + localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), localContent: null, remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: mergeResult.local.added[key], - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: mergeResult.local.added[key], - hasConflicts: false, - localChange: Change.Added, - remoteChange: Change.None + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } /* Snippets updated remotely -> update locally */ - for (const key of Object.keys(mergeResult.local.updated)) { + for (const key of Object.keys(snippetsMergeResult.local.updated)) { + const previewResult: IMergeResult = { + content: snippetsMergeResult.local.updated[key], + hasConflicts: false, + localChange: Change.Modified, + remoteChange: Change.None, + }; resourcePreviews.set(key, { localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], @@ -240,17 +198,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: mergeResult.local.updated[key], - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: mergeResult.local.updated[key], - hasConflicts: false, - localChange: Change.Modified, - remoteChange: Change.None + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } /* Snippets removed remotely -> remove locally */ - for (const key of mergeResult.local.removed) { + for (const key of snippetsMergeResult.local.removed) { + const previewResult: IMergeResult = { + content: null, + hasConflicts: false, + localChange: Change.Deleted, + remoteChange: Change.None, + }; resourcePreviews.set(key, { localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], @@ -258,17 +219,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: null, previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: null, - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: null, - hasConflicts: false, - localChange: Change.Deleted, - remoteChange: Change.None + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } /* Snippets added locally -> add remotely */ - for (const key of Object.keys(mergeResult.remote.added)) { + for (const key of Object.keys(snippetsMergeResult.remote.added)) { + const previewResult: IMergeResult = { + content: snippetsMergeResult.remote.added[key], + hasConflicts: false, + localChange: Change.None, + remoteChange: Change.Added, + }; resourcePreviews.set(key, { localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], @@ -276,17 +240,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: null, previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: mergeResult.remote.added[key], - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: mergeResult.remote.added[key], - hasConflicts: false, - localChange: Change.None, - remoteChange: Change.Added + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } /* Snippets updated locally -> update remotely */ - for (const key of Object.keys(mergeResult.remote.updated)) { + for (const key of Object.keys(snippetsMergeResult.remote.updated)) { + const previewResult: IMergeResult = { + content: snippetsMergeResult.remote.updated[key], + hasConflicts: false, + localChange: Change.None, + remoteChange: Change.Modified, + }; resourcePreviews.set(key, { localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key], @@ -294,17 +261,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: mergeResult.remote.updated[key], - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: mergeResult.remote.updated[key], - hasConflicts: false, - localChange: Change.None, - remoteChange: Change.Modified + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } /* Snippets removed locally -> remove remotely */ - for (const key of mergeResult.remote.removed) { + for (const key of snippetsMergeResult.remote.removed) { + const previewResult: IMergeResult = { + content: null, + hasConflicts: false, + localChange: Change.None, + remoteChange: Change.Deleted, + }; resourcePreviews.set(key, { localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: null, @@ -312,17 +282,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key], previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: null, - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: null, - hasConflicts: false, - localChange: Change.None, - remoteChange: Change.Deleted + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } /* Snippets with conflicts */ - for (const key of mergeResult.conflicts) { + for (const key of snippetsMergeResult.conflicts) { + const previewResult: IMergeResult = { + content: localFileContent[key] ? localFileContent[key].value.toString() : null, + hasConflicts: true, + localChange: localFileContent[key] ? Change.Modified : Change.Added, + remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added + }; resourcePreviews.set(key, { localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key] || null, @@ -330,18 +303,21 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key] || null, previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - hasConflicts: true, - localChange: localFileContent[key] ? Change.Modified : Change.Added, - remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } /* Unmodified Snippets */ for (const key of Object.keys(localFileContent)) { if (!resourcePreviews.has(key)) { + const previewResult: IMergeResult = { + content: localFileContent[key] ? localFileContent[key].value.toString() : null, + hasConflicts: false, + localChange: Change.None, + remoteChange: Change.None + }; resourcePreviews.set(key, { localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }), fileContent: localFileContent[key] || null, @@ -349,12 +325,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }), remoteContent: remoteSnippets[key] || null, previewResource: joinPath(this.syncPreviewFolder, key), - previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }), - acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null, - hasConflicts: false, - localChange: Change.None, - remoteChange: Change.None + previewResult, + localChange: previewResult.localChange, + remoteChange: previewResult.remoteChange }); } } @@ -427,8 +400,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD await this.backupLocal(JSON.stringify(this.toSnippetsContents(local))); } - private async updateLocalSnippets(resourcePreviews: IFileResourcePreview[], force: boolean): Promise { - for (const { fileContent, acceptedContent: content, localResource, remoteResource, localChange } of resourcePreviews) { + private async updateLocalSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], force: boolean): Promise { + for (const { fileContent, acceptResult, localResource, remoteResource, localChange } of resourcePreviews) { if (localChange !== Change.None) { const key = remoteResource ? basename(remoteResource) : basename(localResource!); const resource = joinPath(this.snippetsFolder, key); @@ -443,31 +416,31 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD // Added else if (localChange === Change.Added) { this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, basename(resource)); - await this.fileService.createFile(resource, VSBuffer.fromString(content!), { overwrite: force }); + await this.fileService.createFile(resource, VSBuffer.fromString(acceptResult.content!), { overwrite: force }); this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, basename(resource)); } // Updated else { this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, basename(resource)); - await this.fileService.writeFile(resource, VSBuffer.fromString(content!), force ? undefined : fileContent!); + await this.fileService.writeFile(resource, VSBuffer.fromString(acceptResult.content!), force ? undefined : fileContent!); this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, basename(resource)); } } } } - private async updateRemoteSnippets(resourcePreviews: IFileResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise { + private async updateRemoteSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise { const currentSnippets: IStringDictionary = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {}; const newSnippets: IStringDictionary = deepClone(currentSnippets); - for (const { acceptedContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) { + for (const { acceptResult, localResource, remoteResource, remoteChange } of resourcePreviews) { if (remoteChange !== Change.None) { const key = localResource ? basename(localResource) : basename(remoteResource!); if (remoteChange === Change.Deleted) { delete newSnippets[key]; } else { - newSnippets[key] = content!; + newSnippets[key] = acceptResult.content!; } } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 085d2dc3d0a..70874ae2b1d 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -356,14 +356,12 @@ export interface IUserDataSynchroniser { readonly onDidChangeLocal: Event; - pull(): Promise; - push(): Promise; sync(manifest: IUserDataManifest | null, headers: IHeaders): Promise; replace(uri: URI): Promise; stop(): Promise; preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise; - accept(resource: URI, content: string | null): Promise; + accept(resource: URI, content?: string | null): Promise; merge(resource: URI): Promise; discard(resource: URI): Promise; apply(force: boolean, headers: IHeaders): Promise; @@ -403,7 +401,7 @@ export interface IManualSyncTask extends IDisposable { readonly manifest: IUserDataManifest | null; readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>; preview(): Promise<[SyncResource, ISyncResourcePreview][]>; - accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]>; + accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]>; merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>; discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>; apply(): Promise<[SyncResource, ISyncResourcePreview][]>; @@ -431,7 +429,6 @@ export interface IUserDataSyncService { createSyncTask(): Promise; createManualSyncTask(): Promise; - pull(): Promise; replace(uri: URI): Promise; reset(): Promise; resetRemote(): Promise; @@ -440,7 +437,7 @@ export interface IUserDataSyncService { hasLocalData(): Promise; hasPreviouslySynced(): Promise; resolveContent(resource: URI): Promise; - accept(resource: SyncResource, conflictResource: URI, content: string | null, apply: boolean): Promise; + accept(resource: SyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean): Promise; getLocalSyncResourceHandles(resource: SyncResource): Promise; getRemoteSyncResourceHandles(resource: SyncResource): Promise; diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 015faac4543..8ef3bb5daeb 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -46,7 +46,6 @@ export class UserDataSyncChannel implements IServerChannel { case 'createManualSyncTask': return this.createManualSyncTask(); - case 'pull': return this.service.pull(); case 'replace': return this.service.replace(URI.revive(args[0])); case 'reset': return this.service.reset(); case 'resetRemote': return this.service.resetRemote(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 7d5dd64ce26..80f087edfff 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -98,36 +98,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.resource))); } - async pull(): Promise { - await this.checkEnablement(); - try { - for (const synchroniser of this.synchronisers) { - await synchroniser.pull(); - } - this.updateLastSyncTime(); - } catch (error) { - if (error instanceof UserDataSyncError) { - this.telemetryService.publicLog2<{ resource?: string, executionId?: string }, SyncErrorClassification>(`sync/error/${error.code}`, { resource: error.resource }); - } - throw error; - } - } - - async push(): Promise { - await this.checkEnablement(); - try { - for (const synchroniser of this.synchronisers) { - await synchroniser.push(); - } - this.updateLastSyncTime(); - } catch (error) { - if (error instanceof UserDataSyncError) { - this.telemetryService.publicLog2<{ resource?: string, executionId?: string }, SyncErrorClassification>(`sync/error/${error.code}`, { resource: error.resource }); - } - throw error; - } - } - async createSyncTask(): Promise { await this.checkEnablement(); @@ -256,7 +226,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - async accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise { + async accept(syncResource: SyncResource, resource: URI, content: string | null | undefined, apply: boolean): Promise { await this.checkEnablement(); const synchroniser = this.getSynchroniser(syncResource); await synchroniser.accept(resource, content); @@ -456,7 +426,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { return this.previews; } - async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { + async accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { return this.performAction(resource, sychronizer => sychronizer.accept(resource, content)); } @@ -555,8 +525,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { this._onSynchronizeResources.fire(this.synchronizingResources); const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; for (const resourcePreview of preview.resourcePreviews) { - const content = await synchroniser.resolveContent(resourcePreview.remoteResource); - await synchroniser.accept(resourcePreview.remoteResource, content); + await synchroniser.accept(resourcePreview.remoteResource); } await synchroniser.apply(true, this.syncHeaders); this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); @@ -577,8 +546,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask { this._onSynchronizeResources.fire(this.synchronizingResources); const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!; for (const resourcePreview of preview.resourcePreviews) { - const content = await synchroniser.resolveContent(resourcePreview.localResource); - await synchroniser.accept(resourcePreview.localResource, content); + await synchroniser.accept(resourcePreview.localResource); } await synchroniser.apply(true, this.syncHeaders); this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1); diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index a6c7b761025..2d5594c01d7 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -207,33 +207,6 @@ suite('GlobalStateSync', () => { assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } }); }); - test('first time sync - push', async () => { - updateStorage('a', 'value1', testClient); - updateStorage('b', 'value2', testClient); - - await testObject.push(); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); - - const { content } = await testClient.read(testObject.resource); - assert.ok(content !== null); - const actual = parseGlobalState(content!); - assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } }); - }); - - test('first time sync - pull', async () => { - updateStorage('a', 'value1', client2); - updateStorage('b', 'value2', client2); - await client2.sync(); - - await testObject.pull(); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); - - assert.equal(readStorage('a', testClient), 'value1'); - assert.equal(readStorage('b', testClient), 'value2'); - }); - function parseGlobalState(content: string): IGlobalState { const syncData: ISyncData = JSON.parse(content); return JSON.parse(syncData.content); diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 0115fe44f8d..262fcf95f63 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -613,35 +613,6 @@ suite('SnippetsSync', () => { assert.deepEqual(actual, { 'typescript.json': tsSnippet1 }); }); - test('first time sync - push', async () => { - await updateSnippet('html.json', htmlSnippet1, testClient); - await updateSnippet('typescript.json', tsSnippet1, testClient); - - await testObject.push(); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); - - const { content } = await testClient.read(testObject.resource); - assert.ok(content !== null); - const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 }); - }); - - test('first time sync - pull', async () => { - await updateSnippet('html.json', htmlSnippet1, client2); - await updateSnippet('typescript.json', tsSnippet1, client2); - await client2.sync(); - - await testObject.pull(); - assert.equal(testObject.status, SyncStatus.Idle); - assert.deepEqual(testObject.conflicts, []); - - const actual1 = await readSnippet('html.json', testClient); - assert.equal(actual1, htmlSnippet1); - const actual2 = await readSnippet('typescript.json', testClient); - assert.equal(actual2, tsSnippet1); - }); - test('sync global and language snippet', async () => { await updateSnippet('global.code-snippets', globalSnippet, client2); await updateSnippet('html.json', htmlSnippet1, client2); diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 0d5106c1945..af5045272dd 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest, MergeState } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest, MergeState } 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, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { Barrier } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -17,6 +17,10 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil const resource = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'testResource', path: `/current.json` }); +interface ITestResourcePreview extends IResourcePreview { + ref: string; +} + class TestSynchroniser extends AbstractSynchroniser { syncBarrier: Barrier = new Barrier(); @@ -48,28 +52,43 @@ class TestSynchroniser extends AbstractSynchroniser { return super.doSync(remoteUserData, lastSyncUserData, apply); } - protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; - } - - protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; - } - - protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise { - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; - } - - protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { + protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise { if (this.syncResult.hasError) { throw new Error('failed'); } - return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }]; + + return [{ + localResource: resource, + localContent: null, + remoteResource: resource, + remoteContent: null, + previewResource: resource, + localChange: Change.Modified, + remoteChange: Change.None, + ref: remoteUserData.ref, + }]; } - protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, preview: IResourcePreview[], forcePush: boolean): Promise { - if (preview[0]?.acceptedContent) { - await this.applyRef(preview[0].acceptedContent); + protected async getMergeResult(resourcePreview: ITestResourcePreview, token: CancellationToken): Promise { + return { + content: resourcePreview.ref, + localChange: Change.Modified, + remoteChange: Change.None, + hasConflicts: this.syncResult.hasConflicts, + }; + } + + protected async getAcceptResult(resourcePreview: ITestResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise { + return { + content: content === undefined ? resourcePreview.ref : null, + localChange: Change.Modified, + remoteChange: Change.None, + }; + } + + protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IResourcePreview, IAcceptResult][], force: boolean): Promise { + if (resourcePreviews[0][1].content) { + await this.applyRef(resourcePreviews[0][1].content); } } @@ -328,7 +347,7 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); assert.deepEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [resource]); @@ -341,7 +360,7 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource); preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); @@ -397,7 +416,8 @@ suite('TestSynchronizer', () => { let preview = await testObject.preview(await client.manifest()); await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); assert.deepEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [resource]); @@ -411,7 +431,8 @@ suite('TestSynchronizer', () => { let preview = await testObject.preview(await client.manifest()); await testObject.merge(preview!.resourcePreviews[0].previewResource); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); @@ -425,7 +446,8 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); assert.deepEqual(testObject.status, SyncStatus.Syncing); assertPreviews(preview!.resourcePreviews, [resource]); @@ -438,7 +460,8 @@ suite('TestSynchronizer', () => { testObject.syncBarrier.open(); let preview = await testObject.preview(await client.manifest()); - preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!); + const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource); + preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content); preview = await testObject.apply(false); assert.deepEqual(testObject.status, SyncStatus.Idle); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index def897833ec..768592a3ec5 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -76,65 +76,6 @@ suite('UserDataSyncService', () => { }); - test('test first time sync from the client with no changes - pull', async () => { - const target = new UserDataSyncTestServer(); - - // Setup and sync from the first client - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run(); - - // Setup the test client - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject = testClient.instantiationService.get(IUserDataSyncService); - - // Sync (pull) from the test client - target.reset(); - await testObject.pull(); - - assert.deepEqual(target.requests, [ - { 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: {} }, - ]); - - }); - - test('test first time sync from the client with changes - pull', async () => { - const target = new UserDataSyncTestServer(); - - // Setup and sync from the first client - const client = disposableStore.add(new UserDataSyncClient(target)); - await client.setUp(); - await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run(); - - // Setup the test client with changes - const testClient = disposableStore.add(new UserDataSyncClient(target)); - await testClient.setUp(); - const testObject = testClient.instantiationService.get(IUserDataSyncService); - const fileService = testClient.instantiationService.get(IFileService); - const environmentService = testClient.instantiationService.get(IEnvironmentService); - await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 }))); - await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }]))); - await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' }))); - - // Sync (pull) from the test client - target.reset(); - await testObject.pull(); - - assert.deepEqual(target.requests, [ - { 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: {} }, - ]); - - }); - test('test first time sync from the client with no changes - merge', async () => { const target = new UserDataSyncTestServer(); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 1058b353fe2..1e9139bbf3b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -111,7 +111,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IOutputService private readonly outputService: IOutputService, @IUserDataSyncAccountService readonly authTokenService: IUserDataSyncAccountService, @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, - @ITextModelService private readonly textModelResolverService: ITextModelService, + @ITextModelService textModelResolverService: ITextModelService, @IPreferencesService private readonly preferencesService: IPreferencesService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IProductService private readonly productService: IProductService, @@ -253,12 +253,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async acceptRemote(syncResource: SyncResource, conflicts: IResourcePreview[]) { try { for (const conflict of conflicts) { - const modelRef = await this.textModelResolverService.createModelReference(conflict.remoteResource); - try { - await this.userDataSyncService.accept(syncResource, conflict.remoteResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled()); - } finally { - modelRef.dispose(); - } + await this.userDataSyncService.accept(syncResource, conflict.remoteResource, undefined, this.userDataAutoSyncService.isEnabled()); } } catch (e) { this.notificationService.error(e); @@ -268,12 +263,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async acceptLocal(syncResource: SyncResource, conflicts: IResourcePreview[]): Promise { try { for (const conflict of conflicts) { - const modelRef = await this.textModelResolverService.createModelReference(conflict.previewResource); - try { - await this.userDataSyncService.accept(syncResource, conflict.previewResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled()); - } finally { - modelRef.dispose(); - } + await this.userDataSyncService.accept(syncResource, conflict.previewResource, undefined, this.userDataAutoSyncService.isEnabled()); } } catch (e) { this.notificationService.error(e); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts index 92156df07fa..f0e1e1a4d8c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts @@ -54,7 +54,6 @@ export class UserDataSyncMergesViewPane extends TreeViewPane { @IEditorService private readonly editorService: IEditorService, @IDialogService private readonly dialogService: IDialogService, @IProgressService private readonly progressService: IProgressService, - @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @IDecorationsService decorationsService: IDecorationsService, @IKeybindingService keybindingService: IKeybindingService, @@ -251,16 +250,14 @@ export class UserDataSyncMergesViewPane extends TreeViewPane { private async acceptLocal(userDataSyncResource: IUserDataSyncResource): Promise { await this.withProgress(async () => { - const content = await this.userDataSyncService.resolveContent(userDataSyncResource.local); - await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local, content); + await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local); }); await this.reopen(userDataSyncResource); } private async acceptRemote(userDataSyncResource: IUserDataSyncResource): Promise { await this.withProgress(async () => { - const content = await this.userDataSyncService.resolveContent(userDataSyncResource.remote); - await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote, content); + await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote); }); await this.reopen(userDataSyncResource); } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 525c39e291e..ba0d9db8460 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -594,7 +594,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { this.updateResources(); } - async accept(syncResource: SyncResource, resource: URI, content: string | null): Promise { + async accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise { if (this.manualSync) { const syncPreview = await this.manualSync.task.accept(resource, content); this.updatePreview(syncPreview); diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 73c59a33130..4f5a3219e58 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -20,7 +20,7 @@ export interface IUserDataSyncPreview { readonly onDidChangeResources: Event>; readonly resources: ReadonlyArray; - accept(syncResource: SyncResource, resource: URI, content: string | null): Promise; + accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise; merge(resource?: URI): Promise; discard(resource?: URI): Promise; pull(): Promise; diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 8e74f8b5f48..039a35d98d4 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -65,10 +65,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._register(this.channel.listen<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); } - pull(): Promise { - return this.channel.call('pull'); - } - createSyncTask(): Promise { throw new Error('not supported'); } @@ -186,7 +182,7 @@ class ManualSyncTask implements IManualSyncTask { return this.deserializePreviews(previews); } - async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { + async accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]> { const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('accept', [resource, content]); return this.deserializePreviews(previews); }