diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 0d5dbdd3ef5..69585e9d79f 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -187,6 +187,7 @@ export enum UserDataSyncErrorCode { // Local Errors LocalPreconditionFailed = 'LocalPreconditionFailed', LocalInvalidContent = 'LocalInvalidContent', + LocalError = 'LocalError', Incompatible = 'Incompatible', Unknown = 'Unknown', @@ -293,6 +294,7 @@ export interface IUserDataSyncService { readonly onDidChangeConflicts: Event; readonly onDidChangeLocal: Event; + readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>; readonly lastSyncTime: number | undefined; readonly onDidChangeLastSyncTime: Event; diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 08ac1ae6acf..04c8649a703 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -20,6 +20,7 @@ export class UserDataSyncChannel implements IServerChannel { case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; case 'onDidChangeLocal': return this.service.onDidChangeLocal; case 'onDidChangeLastSyncTime': return this.service.onDidChangeLastSyncTime; + case 'onSyncErrors': return this.service.onSyncErrors; } throw new Error(`Event not found: ${event}`); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 94e476f67f2..07203267160 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -41,6 +41,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _syncErrors: [SyncSource, UserDataSyncError][] = []; + private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); @@ -101,6 +105,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.checkEnablement(); const startTime = new Date().getTime(); + this._syncErrors = []; try { this.logService.trace('Sync started.'); if (this.status !== SyncStatus.HasConflicts) { @@ -126,6 +131,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined); } catch (e) { this.handleSyncError(e, synchroniser.source); + this._syncErrors.push([synchroniser.source, UserDataSyncError.toUserDataSyncError(e)]); } } @@ -144,6 +150,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } finally { this.updateStatus(); + this._onSyncErrors.fire(this._syncErrors); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 5bd817b28e6..36c2f28c7a1 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -37,11 +37,11 @@ class UserDataSyncSettingsMigrationContribution implements IWorkbenchContributio } private async removeFromConfiguration(): Promise { - await this.configurationService.updateValue('sync.enable', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableSettings', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableKeybindings', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableUIState', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableExtensions', undefined, ConfigurationTarget.USER); + await this.configurationService.updateValue('sync.enable', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableSettings', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableKeybindings', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableUIState', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableExtensions', undefined, {}, ConfigurationTarget.USER, true); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index ba05a309407..a2c70433f6c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -109,7 +109,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private readonly syncStatusContext: IContextKey; private readonly authenticationState: IContextKey; private readonly conflictsSources: IContextKey; - private readonly conflictsDisposables = new Map(); + private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); private _activeAccount: AuthenticationSession | undefined; @@ -149,6 +149,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.onDidChangeEnablement(this.userDataSyncEnablementService.isEnabled()); this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflictsSources))); + this._register(userDataSyncService.onSyncErrors(errors => this.onSyncErrors(errors))); this._register(this.authTokenService.onTokenFailed(_ => this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId))); this._register(this.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.onDidChangeEnablement(enabled))); this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); @@ -268,6 +269,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); } + private readonly conflictsDisposables = new Map(); private onDidChangeConflicts(conflicts: SyncSource[]) { this.updateBadge(); if (conflicts.length) { @@ -405,7 +407,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo severity: Severity.Error, message: localize('too large', "Disabled sync {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, + primary: [new Action('open sync file', localize('open file', "Open {0} file", sourceArea), undefined, true, () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); @@ -421,6 +423,47 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private readonly invalidContentErrorDisposables = new Map(); + private onSyncErrors(errors: [SyncSource, UserDataSyncError][]): void { + if (errors.length) { + for (const [source, error] of errors) { + switch (error.code) { + case UserDataSyncErrorCode.LocalInvalidContent: + this.handleInvalidContentError(source); + break; + default: + const disposable = this.invalidContentErrorDisposables.get(source); + if (disposable) { + disposable.dispose(); + this.invalidContentErrorDisposables.delete(source); + } + } + } + } else { + this.invalidContentErrorDisposables.forEach(disposable => disposable.dispose()); + this.invalidContentErrorDisposables.clear(); + } + } + + private handleInvalidContentError(source: SyncSource): void { + if (!this.invalidContentErrorDisposables.has(source)) { + const errorArea = getSyncAreaLabel(source); + const handle = this.notificationService.notify({ + severity: Severity.Error, + message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea), + actions: { + primary: [new Action('open sync file', localize('open file', "Open {0} file", errorArea), undefined, true, + () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + this.invalidContentErrorDisposables.set(source, toDisposable(() => { + // close the error warning notification + handle.close(); + this.invalidContentErrorDisposables.delete(source); + })); + } + } + private async updateBadge(): Promise { this.badgeDisposable.clear(); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 46537301337..2b495a43dc9 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -34,6 +34,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; + private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + constructor( @ISharedProcessService sharedProcessService: ISharedProcessService ) { @@ -58,6 +61,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._register(this.channel.listen('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime))); }); this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + this._register(this.channel.listen<[SyncSource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); } pull(): Promise {