diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d02dd14a69a6e701ace929e49e0a2c42e301ab23..82eb46f185a463be18e132c5c03b97e4b6303873 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -154,7 +154,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse await this.apply(previewResult); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -345,12 +345,12 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null, this.source); } private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { const content = JSON.stringify(extensions); - ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref); + ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref, this.source); return { content, ref }; } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index f3275f87b1bb9d5132e415e3c2d3b9d6f0f0375f..58b60ff6f6ee2aa7a06e37372532ba8fb5a3ab13 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -132,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.logService.trace('UI State: Finished synchronizing ui state.'); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -259,12 +259,12 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null, this.source); } private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise { const content = JSON.stringify(globalState); - ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref); + ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref, this.source); return { content, ref }; } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index d4841150c248ea8ee18b179d9b62b5909111956b..4d20c895d03310a7b1128b3dfe9d1ee763cc45a9 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; @@ -266,7 +266,7 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -433,11 +433,11 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } private async getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null, this.source); } private async updateRemoteUserData(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); + return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref, this.source); } private getKeybindingsContentFromSyncContent(syncContent: string): string | null { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index fc707cbc9b53d421de08439fc20dd14cbaa5e32c..f8dd7b0ba634a5dde54bb82219a3e110906b903d 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -302,7 +302,7 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -454,11 +454,11 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null, this.source); } private async writeToRemote(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref); + return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref, this.source); } private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts index 6f53447afb382211b35f21fa9cbd16bbde194543..d3a5a7d52ecf2ea7ea1c554dfa84ff6f9ed658a3 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSync.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -4,16 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { timeout } from 'vs/base/common/async'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService, UserDataSyncError, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncService { _serviceBrand: any; private enabled: boolean = false; + private successiveFailures: number = 0; + + 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; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -41,6 +45,7 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer this.sync(true, auto); return; } else { + this.successiveFailures = 0; if (stopIfDisabled) { this.userDataSyncService.stop(); this.logService.info('Auto sync stopped.'); @@ -65,11 +70,17 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer } } await this.userDataSyncService.sync(); + this.successiveFailures = 0; } catch (e) { + this.successiveFailures++; this.logService.error(e); + this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); + } + if (this.successiveFailures > 5) { + this._onError.fire({ code: UserDataSyncErrorCode.TooManyFailures }); } if (loop) { - await timeout(1000 * 60 * 5); // Loop sync for every 5 min. + await timeout(1000 * 60 * 5 * (this.successiveFailures + 1)); // Loop sync for every (successive failures count + 1) times 5 mins interval. this.sync(loop, true); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index aeb1bd8425b2215cc638d04aa7c7be313ad9552e..6580f517dfb64d34c974f509bb7db5424ad5af52 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -123,15 +123,18 @@ export interface IUserData { content: string | null; } -export enum UserDataSyncStoreErrorCode { +export enum UserDataSyncErrorCode { + TooLarge = 'TooLarge', Unauthroized = 'Unauthroized', Rejected = 'Rejected', - Unknown = 'Unknown' + Unknown = 'Unknown', + TooManyFailures = 'TooManyFailures', + ConnectionRefused = 'ConnectionRefused' } -export class UserDataSyncStoreError extends Error { +export class UserDataSyncError extends Error { - constructor(message: string, public readonly code: UserDataSyncStoreErrorCode) { + constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) { super(message); } @@ -151,8 +154,8 @@ export const IUserDataSyncStoreService = createDecorator; - write(key: string, content: string, ref: string | null): Promise; + read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise; + write(key: string, content: string, ref: string | null, source?: SyncSource): Promise; clear(): Promise; } @@ -217,6 +220,7 @@ export interface IUserDataSyncService extends ISynchroniser { export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); export interface IUserDataAutoSyncService { _serviceBrand: any; + onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>; triggerAutoSync(): Promise; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index c6340f89049f9ebab8a02c1985e9d6bcc5649c9d..caf9856c728672747554e439a2fc1f352d41f6f5 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -84,6 +84,9 @@ export class UserDataAutoSyncChannel implements IServerChannel { constructor(private readonly service: IUserDataAutoSyncService) { } listen(_: unknown, event: string): Event { + switch (event) { + case 'onError': return this.service.onError; + } throw new Error(`Event not found: ${event}`); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index f33c18806002895fd3b0edec1f142587491e9c4f..a5e77ab721eb6f4a869908e7d65317931801eb04 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; @@ -27,7 +27,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn this.userDataSyncStore = getUserDataSyncStore(configurationService); } - async read(key: string, oldValue: IUserData | null): Promise { + async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } @@ -40,7 +40,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-None-Match'] = oldValue.ref; } - const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None); + const context = await this.request({ type: 'GET', url, headers }, source, CancellationToken.None); if (context.res.statusCode === 304) { // There is no new value. Hence return the old value. @@ -59,7 +59,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return { ref, content }; } - async write(key: string, data: string, ref: string | null): Promise { + async write(key: string, data: string, ref: string | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } @@ -70,12 +70,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-Match'] = ref; } - const context = await this.request({ type: 'POST', url, data, headers }, CancellationToken.None); - - if (context.res.statusCode === 412) { - // There is a new value. Throw Rejected Error - throw new UserDataSyncStoreError('New data exists', UserDataSyncStoreErrorCode.Rejected); - } + const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None); if (!isSuccess(context)) { throw new Error('Server returned ' + context.res.statusCode); @@ -96,14 +91,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource').toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; - const context = await this.request({ type: 'DELETE', url, headers }, CancellationToken.None); + const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); if (!isSuccess(context)) { throw new Error('Server returned ' + context.res.statusCode); } } - private async request(options: IRequestOptions, token: CancellationToken): Promise { + private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise { const authToken = await this.authTokenService.getToken(); if (!authToken) { throw new Error('No Auth Token Available.'); @@ -111,15 +106,30 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn options.headers = options.headers || {}; options.headers['authorization'] = `Bearer ${authToken}`; - const context = await this.requestService.request(options, token); + let context; + + try { + context = await this.requestService.request(options, token); + } catch (e) { + throw new UserDataSyncError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, source); + } if (context.res.statusCode === 401) { // Throw Unauthorized Error - throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized); + throw new UserDataSyncError(`Request '${options.url?.toString()}' is not authorized.`, UserDataSyncErrorCode.Unauthroized, source); } - return context; + if (context.res.statusCode === 412) { + // There is a new value. Throw Rejected Error + throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed with precondition. There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Rejected, source); + } + if (context.res.statusCode === 413) { + // Throw Too Large Payload Error + throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed because data is too large.`, UserDataSyncErrorCode.TooLarge, source); + } + + return context; } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0612f07a58b9ca48993397a3454638a515f68399..c6f4d0e93525b41105b4f077a222675442c6331e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -44,6 +44,8 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { areSame } from 'vs/platform/userDataSync/common/settingsMerge'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsSync'; import type { IEditorInput } from 'vs/workbench/common/editor'; +import { Action } from 'vs/base/common/actions'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; const enum AuthStatus { Initializing = 'Initializing', @@ -91,6 +93,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, @ITextModelService private readonly textModelResolverService: ITextModelService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -104,6 +107,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.registerActions(); this.initializeActiveAccount().then(_ => { if (isWeb) { @@ -246,6 +250,35 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void { + switch (code) { + case UserDataSyncErrorCode.TooManyFailures: + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('too many errors', "Turned off sync because of too many failure attempts. Please open Sync log to check the failures and turn on sync."), + actions: { + primary: [new Action('open sync log', localize('open log', "Show logs"), undefined, true, () => this.showSyncLog())] + } + }); + return; + case UserDataSyncErrorCode.TooLarge: + if (source === SyncSource.Keybindings || source === SyncSource.Settings) { + const sourceArea = getSyncAreaLabel(source); + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('too large', "Turned off sync because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", sourceArea, '1MB'), + actions: { + primary: [new Action('open sync log', localize('open file', "Show {0} file", sourceArea), undefined, true, + () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + } + return; + } + } + private async updateBadge(): Promise { this.badgeDisposable.clear(); @@ -406,13 +439,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } }); if (result.confirmed) { - await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER); + await this.disableSync(); if (result.checkboxChecked) { await this.userDataSyncService.reset(); } } } + private disableSync(): Promise { + return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER); + } + private async signIn(): Promise { try { this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index f773ebf8be1ed0d9da6165ffbb8879f9a67e5812..7976bb0fbdb2404e5a48c17c4fdfbcaaea23de0a 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataAutoSyncService, UserDataSyncErrorCode, SyncSource } 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'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event } from 'vs/base/common/event'; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { _serviceBrand: undefined; private readonly channel: IChannel; + get onError(): Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> { return this.channel.listen('onError'); } constructor( @ISharedProcessService sharedProcessService: ISharedProcessService