diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 4b29c0a61a9..aca3d82798d 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -311,7 +311,7 @@ export abstract class AbstractSynchroniser extends Disposable { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { // current version is not compatible with cloud version this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.resource }); - throw new UserDataSyncError(localize({ key: 'incompatible', comment: ['This is an error while syncing a resource that its local version is not compatible with its remote version.'] }, "Cannot sync {0} as its local version {1} is not compatible with its remote version {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.resource); + throw new UserDataSyncError(localize({ key: 'incompatible', comment: ['This is an error while syncing a resource that its local version is not compatible with its remote version.'] }, "Cannot sync {0} as its local version {1} is not compatible with its remote version {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.IncompatibleLocalContent, this.resource); } try { @@ -626,7 +626,7 @@ export abstract class AbstractSynchroniser extends Disposable { } catch (error) { this.logService.error(error); } - throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with current version."), UserDataSyncErrorCode.Incompatible, this.resource); + throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with current version."), UserDataSyncErrorCode.IncompatibleRemoteContent, this.resource); } private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 96500ad38c4..511df050312 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -218,6 +218,26 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i this.logService.info('Auto Sync: Turned off sync because of making too many requests to server'); } + // Upgrade Required or Gone + else if (userDataSyncError.code === UserDataSyncErrorCode.UpgradeRequired || userDataSyncError.code === UserDataSyncErrorCode.Gone) { + await this.turnOff(false, true /* force soft turnoff on error */, + true /* do not disable machine because disabling a machine makes request to server and can fail with upgrade required or gone */); + this.disableMachineEventually(); + this.logService.info('Auto Sync: Turned off sync because current client is not compatible with server. Requires client upgrade.'); + } + + // Incompatible Local Content + else if (userDataSyncError.code === UserDataSyncErrorCode.IncompatibleLocalContent) { + await this.turnOff(false, true /* force soft turnoff on error */); + this.logService.info('Auto Sync: Turned off sync because server has {0} content with newer version than of client. Requires client upgrade.', userDataSyncError.resource); + } + + // Incompatible Remote Content + else if (userDataSyncError.code === UserDataSyncErrorCode.IncompatibleRemoteContent) { + await this.turnOff(false, true /* force soft turnoff on error */); + this.logService.info('Auto Sync: Turned off sync because server has {0} content with older version than of client. Requires server reset.', userDataSyncError.resource); + } + else { this.logService.error(userDataSyncError); this.successiveFailures++; diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 1c94f50dfcd..511962f6222 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -217,7 +217,8 @@ export enum UserDataSyncErrorCode { LocalPreconditionFailed = 'LocalPreconditionFailed', LocalInvalidContent = 'LocalInvalidContent', LocalError = 'LocalError', - Incompatible = 'Incompatible', + IncompatibleLocalContent = 'IncompatibleLocalContent', + IncompatibleRemoteContent = 'IncompatibleRemoteContent', UnresolvedConflicts = 'UnresolvedConflicts', Unknown = 'Unknown', @@ -415,6 +416,7 @@ export interface IUserDataSyncService { pull(): Promise; replace(uri: URI): Promise; reset(): Promise; + resetRemote(): Promise; resetLocal(): Promise; hasLocalData(): Promise; diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 4edcf255f35..a46fe509fc3 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -49,6 +49,7 @@ export class UserDataSyncChannel implements IServerChannel { 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(); case 'resetLocal': return this.service.resetLocal(); case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); case 'hasLocalData': return this.service.hasLocalData(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index cf8a3019b1b..b364d7cae54 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -313,6 +313,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.resetLocal(); } + async resetRemote(): Promise { + await this.checkEnablement(); + try { + await this.userDataSyncStoreService.clear(); + this.logService.info('Cleared data on server'); + } catch (e) { + this.logService.error(e); + } + } + async resetLocal(): Promise { await this.checkEnablement(); this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL); @@ -336,16 +346,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return false; } - private async resetRemote(): Promise { - await this.checkEnablement(); - try { - await this.userDataSyncStoreService.clear(); - this.logService.info('Cleared data on server'); - } catch (e) { - this.logService.error(e); - } - } - private setStatus(status: SyncStatus): void { const oldStatus = this._status; if (this._status !== status) { @@ -402,7 +402,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ case UserDataSyncErrorCode.LocalTooManyRequests: case UserDataSyncErrorCode.Gone: case UserDataSyncErrorCode.UpgradeRequired: - case UserDataSyncErrorCode.Incompatible: + case UserDataSyncErrorCode.IncompatibleRemoteContent: + case UserDataSyncErrorCode.IncompatibleLocalContent: throw e; } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 24ad409a5a9..e868b599487 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -119,6 +119,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IStorageService private readonly storageService: IStorageService, @IOpenerService private readonly openerService: IOpenerService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @ICommandService private readonly commandService: ICommandService, ) { super(); @@ -294,10 +295,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.handleTooLargeError(error.resource, localize('too large', "Disabled syncing {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.toLowerCase(), sourceArea.toLowerCase(), '100kb'), error); } break; - case UserDataSyncErrorCode.Incompatible: + case UserDataSyncErrorCode.IncompatibleLocalContent: case UserDataSyncErrorCode.Gone: case UserDataSyncErrorCode.UpgradeRequired: - this.userDataSyncWorkbenchService.turnoff(false); const message = localize('error upgrade required', "Preferences sync is disabled because the current version ({0}, {1}) is not compatible with the sync service. Please update before turning on sync.", this.productService.version, this.productService.commit); const operationId = error.operationId ? localize('operationId', "Operation Id: {0}", error.operationId) : undefined; this.notificationService.notify({ @@ -305,6 +305,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo message: operationId ? `${message} ${operationId}` : message, }); break; + case UserDataSyncErrorCode.IncompatibleRemoteContent: + this.notificationService.notify({ + severity: Severity.Error, + message: localize('error reset required', "Preferences sync is disabled because your data in the cloud is older than that of in the client. Please reset your data in the cloud before turning on sync."), + actions: { + primary: [ + new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), + new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.commandService.executeCommand(SHOW_SYNCED_DATA_COMMAND_ID)) + ] + } + }); + return; } } @@ -439,7 +451,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return; } break; - case UserDataSyncErrorCode.Incompatible: + case UserDataSyncErrorCode.IncompatibleLocalContent: case UserDataSyncErrorCode.Gone: case UserDataSyncErrorCode.UpgradeRequired: const message = localize('error upgrade required while starting sync', "Preferences sync cannot be turned on because the current version ({0}, {1}) is not compatible with the sync service. Please update before turning on sync.", this.productService.version, this.productService.commit); @@ -449,6 +461,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo message: operationId ? `${message} ${operationId}` : message, }); return; + case UserDataSyncErrorCode.IncompatibleRemoteContent: + this.notificationService.notify({ + severity: Severity.Error, + message: localize('error reset required while starting sync', "Preferences sync cannot be turned on because your data in the cloud is older than that of in the client. Please reset your data in the cloud before turning on sync."), + actions: { + primary: [ + new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), + new Action('show synced data', localize('show synced data action', "Show Synced Data"), undefined, true, () => this.commandService.executeCommand(SHOW_SYNCED_DATA_COMMAND_ID)) + ] + } + }); + return; } } this.notificationService.error(localize('turn on failed', "Error while starting Sync: {0}", toErrorMessage(e))); @@ -946,7 +970,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo that.viewsEnablementContext.set(true); const viewDescriptorService = accessor.get(IViewDescriptorService); const viewsService = accessor.get(IViewsService); - const viewContainer = viewDescriptorService.getViewContainerByViewId(viewContainerId); + const viewContainer = viewDescriptorService.getViewContainerById(viewContainerId); if (viewContainer) { const model = viewDescriptorService.getViewContainerModel(viewContainer); if (model.activeViewDescriptors.length) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index ec1f3d825f2..14127228011 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncResourceEnablementService, IUserDataAutoSyncService, Change } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncResourceEnablementService, IUserDataAutoSyncService, Change, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; @@ -33,7 +33,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, CONTEXT_SHOW_MANUAL_SYNC_VIEW, IUserDataSyncPreview, IUserDataSyncResourceGroup } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { TreeView } from 'vs/workbench/contrib/views/browser/treeView'; import { flatten } from 'vs/base/common/arrays'; import { isEqual, basename } from 'vs/base/common/resources'; @@ -42,7 +42,6 @@ export class UserDataSyncViewPaneContainer extends ViewPaneContainer { constructor( containerId: string, - @IDialogService private readonly dialogService: IDialogService, @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @ICommandService private readonly commandService: ICommandService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -68,22 +67,10 @@ export class UserDataSyncViewPaneContainer extends ViewPaneContainer { getSecondaryActions(): IAction[] { return [ - new Action('workbench.actions.syncData.reset', localize('workbench.actions.syncData.reset', "Reset Synced Data"), undefined, true, () => this.reset()), + new Action('workbench.actions.syncData.reset', localize('workbench.actions.syncData.reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), ]; } - private async reset(): Promise { - const result = await this.dialogService.confirm({ - message: localize('reset', "This will clear your synced data from the cloud and stop sync on all your devices."), - title: localize('reset title', "Reset Synced Data"), - type: 'info', - primaryButton: localize('reset button', "Reset"), - }); - if (result.confirmed) { - await this.userDataSyncWorkbenchService.turnoff(true); - } - } - } export class UserDataSyncDataViews extends Disposable { @@ -626,6 +613,7 @@ abstract class UserDataSyncActivityViewDataProvider implements ITreeViewDataProv constructor( @IUserDataSyncService protected readonly userDataSyncService: IUserDataSyncService, @IUserDataAutoSyncService protected readonly userDataAutoSyncService: IUserDataAutoSyncService, + @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @INotificationService private readonly notificationService: INotificationService, ) { } @@ -639,7 +627,22 @@ abstract class UserDataSyncActivityViewDataProvider implements ITreeViewDataProv } return []; } catch (error) { - this.notificationService.error(error); + if (!(error instanceof UserDataSyncError)) { + error = UserDataSyncError.toUserDataSyncError(error); + } + if (error instanceof UserDataSyncError && error.code === UserDataSyncErrorCode.IncompatibleRemoteContent) { + this.notificationService.notify({ + severity: Severity.Error, + message: error.message, + actions: { + primary: [ + new Action('reset', localize('reset', "Reset Synced Data"), undefined, true, () => this.userDataSyncWorkbenchService.resetSyncedData()), + ] + } + }); + } else { + this.notificationService.error(error); + } throw error; } } @@ -705,9 +708,10 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie @IUserDataSyncService userDataSyncService: IUserDataSyncService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, @IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService, + @IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @INotificationService notificationService: INotificationService, ) { - super(userDataSyncService, userDataAutoSyncService, notificationService); + super(userDataSyncService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService); } async getChildren(element?: ITreeItem): Promise { diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 932c48e840c..8fd6f93a8ba 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -357,6 +357,18 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } + async resetSyncedData(): Promise { + const result = await this.dialogService.confirm({ + message: localize('reset', "This will clear your synced data from the cloud and stop sync on all your devices."), + title: localize('reset title', "Reset Synced Data"), + type: 'info', + primaryButton: localize('reset button', "Reset"), + }); + if (result.confirmed) { + await this.userDataSyncService.resetRemote(); + } + } + private isSupportedAuthenticationProviderId(authenticationProviderId: string): boolean { return this.authenticationProviders.some(({ id }) => id === authenticationProviderId); } diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 8dcf799ad6d..1adb9771010 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -54,6 +54,7 @@ export interface IUserDataSyncWorkbenchService { turnOn(): Promise; turnoff(everyWhere: boolean): Promise; signIn(): Promise; + resetSyncedData(): Promise; } export function getSyncAreaLabel(source: SyncResource): string { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 85b71618c86..bd7b7fa90f8 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -86,6 +86,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('reset'); } + resetRemote(): Promise { + return this.channel.call('resetRemote'); + } + resetLocal(): Promise { return this.channel.call('resetLocal'); } @@ -165,7 +169,16 @@ class ManualSyncTask implements IManualSyncTask { readonly manifest: IUserDataManifest | null, sharedProcessService: ISharedProcessService, ) { - this.channel = sharedProcessService.getChannel(`manualSyncTask-${id}`); + const manualSyncTaskChannel = sharedProcessService.getChannel(`manualSyncTask-${id}`); + this.channel = { + call(command: string, arg?: any, cancellationToken?: CancellationToken): Promise { + return manualSyncTaskChannel.call(command, arg, cancellationToken) + .then(null, error => { throw UserDataSyncError.toUserDataSyncError(error); }); + }, + listen(event: string, arg?: any): Event { + return manualSyncTaskChannel.listen(event, arg); + } + }; } async preview(): Promise<[SyncResource, ISyncResourcePreview][]> {