diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 3c6191259ee..abb2996f999 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -260,9 +260,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (content !== null) { - if (this.hasErrors(content)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); - } + this.validateContent(content); if (hasLocalChanged) { this.logService.trace('Settings: Updating local settings...'); @@ -317,21 +315,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (remoteSettingsSyncContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; - - // No action when there are errors - if (this.hasErrors(localContent)) { - throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); - } - - else { - this.logService.trace('Settings: Merging remote settings with local settings...'); - const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); - content = result.localContent || result.remoteContent; - hasLocalChanged = result.localContent !== null; - hasRemoteChanged = result.remoteContent !== null; - hasConflicts = result.hasConflicts; - conflictSettings = result.conflictsSettings; - } + this.validateContent(localContent); + this.logService.trace('Settings: Merging remote settings with local settings...'); + const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); + content = result.localContent || result.remoteContent; + hasLocalChanged = result.localContent !== null; + hasRemoteChanged = result.remoteContent !== null; + hasConflicts = result.hasConflicts; + conflictSettings = result.conflictsSettings; } // First time syncing to remote @@ -364,4 +355,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement } return null; } + + private validateContent(content: string): void { + if (this.hasErrors(content)) { + throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); + } + } } diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 4cd08868110..c6e57074e97 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -6,7 +6,7 @@ import { timeout, Delayer } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncSource, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { @@ -17,8 +17,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto private successiveFailures: number = 0; private readonly syncDelayer: Delayer; - private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>()); - readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event; + private readonly _onError: Emitter = this._register(new Emitter()); + readonly onError: Event = this._onError.event; constructor( @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -62,7 +62,8 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto await this.userDataSyncService.sync(); this.resetFailures(); } catch (e) { - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.TurnedOff) { + const error = UserDataSyncError.toUserDataSyncError(e); + if (error.code === UserDataSyncErrorCode.TurnedOff) { this.logService.info('Auto Sync: Sync is turned off in the cloud.'); this.logService.info('Auto Sync: Resetting the local sync state.'); await this.userDataSyncService.resetLocal(); @@ -73,16 +74,16 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto return this.sync(loop, auto); } } - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.SessionExpired) { + if (error.code === UserDataSyncErrorCode.SessionExpired) { this.logService.info('Auto Sync: Cloud has new session'); this.logService.info('Auto Sync: Resetting the local sync state.'); await this.userDataSyncService.resetLocal(); this.logService.info('Auto Sync: Completed resetting the local sync state.'); return this.sync(loop, auto); } - this.logService.error(e); + this.logService.error(error); this.successiveFailures++; - this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); + this._onError.fire(error); } if (loop) { await timeout(1000 * 60 * 5); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 13114960e87..8dfdb35b465 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -302,7 +302,7 @@ export interface IUserDataSyncService { export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); export interface IUserDataAutoSyncService { _serviceBrand: any; - readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>; + readonly onError: Event; triggerAutoSync(): Promise; } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 21702db8313..5d64c2a6bc1 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -46,6 +46,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { fromNow } from 'vs/base/common/date'; +import { IProductService } from 'vs/platform/product/common/productService'; const enum AuthStatus { Initializing = 'Initializing', @@ -119,6 +120,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IPreferencesService private readonly preferencesService: IPreferencesService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IFileService private readonly fileService: IFileService, + @IProductService private readonly productService: IProductService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -138,7 +140,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e))); - this._register(userDataAutoSyncService.onError(({ code, source }) => this.onAutoSyncError(code, source))); + this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error))); this.registerActions(); this.initializeActiveAccount().then(_ => { if (!isWeb) { @@ -369,21 +371,41 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void { - switch (code) { + private onAutoSyncError(error: UserDataSyncError): void { + switch (error.code) { case UserDataSyncErrorCode.TooLarge: - if (source === SyncSource.Keybindings || source === SyncSource.Settings) { - const sourceArea = getSyncAreaLabel(source); + if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { + const sourceArea = getSyncAreaLabel(error.source); this.notificationService.notify({ severity: Severity.Error, message: localize('too large', "Disabled synchronizing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'), actions: { primary: [new Action('open sync file', localize('open file', "Show {0} file", sourceArea), undefined, true, - () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); } return; + case UserDataSyncErrorCode.LocalInvalidContent: + if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { + const sourceArea = getSyncAreaLabel(error.source); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('error invalid content', "Unable to sync {0} as there are errors/warnings in the file. Please open the file and fix them to continue syncing.", sourceArea), + actions: { + primary: [new Action('open sync file', localize('open file', "Show {0} file", sourceArea), undefined, true, + () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + } + return; + case UserDataSyncErrorCode.Incompatible: + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('error incompatible', "Disabled synchronizing because local data is incompatible with the data in the cloud. Please update {0} to continue syncing.", this.productService.nameLong), + }); + return; } } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index 7976bb0fbdb..4dc5de8bc46 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAutoSyncService, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataAutoSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -15,7 +15,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto _serviceBrand: undefined; private readonly channel: IChannel; - get onError(): Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> { return this.channel.listen('onError'); } + get onError(): Event { return Event.map(this.channel.listen('onError'), e => UserDataSyncError.toUserDataSyncError(e)); } constructor( @ISharedProcessService sharedProcessService: ISharedProcessService