diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 835e4d242f5..09dd0a4647e 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -19,6 +19,8 @@ import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; import { computeRemoteContent, merge } from 'vs/platform/userDataSync/common/settingsMerge'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import * as arrays from 'vs/base/common/arrays'; +import * as objects from 'vs/base/common/objects'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -41,6 +43,11 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + private _conflicts: IConflictSetting[] = []; + get conflicts(): IConflictSetting[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -65,6 +72,18 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer this._status = status; this._onDidChangStatus.fire(status); } + if (this._status !== SyncStatus.HasConflicts) { + this.setConflicts([]); + } + } + + private setConflicts(conflicts: IConflictSetting[]): void { + if (!arrays.equals(this.conflicts, conflicts, + (a, b) => a.key === b.key && objects.equals(a.localValue, b.localValue) && objects.equals(a.remoteValue, b.remoteValue)) + ) { + this._conflicts = conflicts; + this._onDidChangeConflicts.fire(conflicts); + } } async sync(_continue?: boolean): Promise { @@ -83,6 +102,8 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer return false; } + this.logService.trace('Settings: Started synchronizing settings...'); + this.setStatus(SyncStatus.Syncing); return this.doSync([]); } @@ -96,28 +117,15 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer this.setStatus(SyncStatus.Idle); } - async getConflicts(): Promise { - if (!this.syncPreviewResultPromise) { - return []; - } - if (this.status !== SyncStatus.HasConflicts) { - return []; - } - const preview = await this.syncPreviewResultPromise; - return preview.conflicts; - } - async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { if (this.status === SyncStatus.HasConflicts) { - this.stop(); + this.syncPreviewResultPromise!.cancel(); + this.syncPreviewResultPromise = null; await this.doSync(resolvedConflicts); } } private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { - this.logService.trace('Settings: Started synchronizing settings...'); - this.setStatus(SyncStatus.Syncing); - try { const result = await this.getPreview(resolvedConflicts); if (result.conflicts.length) { @@ -222,12 +230,13 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; + + // No action when there are errors if (this.hasErrors(localContent)) { this.logService.error('Settings: Unable to sync settings as there are errors/warning in settings file.'); - return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts }; } - if (!lastSyncData // First time sync + else if (!lastSyncData // First time sync || lastSyncData.content !== localContent // Local has forwarded || lastSyncData.content !== remoteContent // Remote has forwarded ) { @@ -255,6 +264,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); } + this.setConflicts(conflicts); return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts }; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 93dcbdce2bc..ca76015a885 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -200,7 +200,8 @@ export interface IConflictSetting { export const ISettingsSyncService = createDecorator('ISettingsSyncService'); export interface ISettingsSyncService extends ISynchroniser { _serviceBrand: any; - getConflicts(): Promise; + readonly onDidChangeConflicts: Event; + readonly conflicts: IConflictSetting[]; resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 3841e850cb4..bcd53cae1b7 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -42,6 +42,7 @@ export class SettingsSyncChannel implements IServerChannel { switch (event) { case 'onDidChangeStatus': return this.service.onDidChangeStatus; case 'onDidChangeLocal': return this.service.onDidChangeLocal; + case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; } throw new Error(`Event not found: ${event}`); } @@ -50,8 +51,8 @@ export class SettingsSyncChannel implements IServerChannel { switch (command) { case 'sync': return this.service.sync(args[0]); case '_getInitialStatus': return Promise.resolve(this.service.status); + case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); case 'stop': this.service.stop(); return Promise.resolve(); - case 'getConflicts': return this.service.getConflicts(); case 'resolveConflicts': return this.service.resolveConflicts(args[0]); } throw new Error('Invalid call'); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 58f65eee3f4..0a2adc53f3a 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -21,6 +21,11 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + private _conflicts: IConflictSetting[] = []; + get conflicts(): IConflictSetting[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } constructor( @@ -32,6 +37,12 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ this.updateStatus(status); this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); }); + this.channel.call('_getInitialConflicts').then(conflicts => { + if (conflicts.length) { + this.updateConflicts(conflicts); + } + this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + }); } sync(_continue?: boolean): Promise { @@ -42,10 +53,6 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ this.channel.call('stop'); } - getConflicts(): Promise { - return this.channel.call('getConflicts'); - } - resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { return this.channel.call('resolveConflicts', [conflicts]); } @@ -55,6 +62,11 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ this._onDidChangeStatus.fire(status); } + private async updateConflicts(conflicts: IConflictSetting[]): Promise { + this._conflicts = conflicts; + this._onDidChangeConflicts.fire(conflicts); + } + } registerSingleton(ISettingsSyncService, SettingsSyncService);