diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index 57d31ad2242..e94034cd94f 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -61,6 +61,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } } } else { + this.syncTriggerDelayer.cancel(); if (this.autoSync.value !== undefined) { this.logService.info('Auto Sync: Disabled because', reason); this.autoSync.clear(); @@ -68,6 +69,16 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } } + enable(): void { + this.userDataSyncEnablementService.setEnablement(true); + this.updateAutoSync(); + } + + disable(): void { + this.userDataSyncEnablementService.setEnablement(false); + this.updateAutoSync(); + } + // For tests purpose only protected startAutoSync(): boolean { return true; } @@ -93,11 +104,10 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff || userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) { this.logService.info('Auto Sync: Sync is turned off in the cloud.'); await this.userDataSyncService.resetLocal(); - this.logService.info('Auto Sync: Did reset the local sync state.'); - this.userDataSyncEnablementService.setEnablement(false); + this.disable(); this.logService.info('Auto Sync: Turned off sync because sync is turned off in the cloud'); } else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests) { - this.userDataSyncEnablementService.setEnablement(false); + this.disable(); this.logService.info('Auto Sync: Turned off sync because of making too many requests to server'); } else { this.logService.error(userDataSyncError); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 14ea209eb98..1c269cae5d2 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -360,6 +360,8 @@ export const IUserDataAutoSyncService = createDecorator; + enable(): void; + disable(): void; triggerAutoSync(sources: string[]): Promise; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 9608c10359d..7b6a2401fca 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -74,6 +74,8 @@ export class UserDataAutoSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { case 'triggerAutoSync': return this.service.triggerAutoSync(args[0]); + case 'enable': return Promise.resolve(this.service.enable()); + case 'disable': return Promise.resolve(this.service.disable()); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 957ad58f04c..fbd7c979c46 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -19,11 +19,12 @@ import { URI } from 'vs/base/common/uri'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { isEqual } from 'vs/base/common/resources'; import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync'; -import { Throttler } from 'vs/base/common/async'; +import { Throttler, createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { IProductService } from 'vs/platform/product/common/productService'; import { platform, PlatformToString } from 'vs/base/common/platform'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { CancellationToken } from 'vs/base/common/cancellation'; type SyncClassification = { resource?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -37,6 +38,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ _serviceBrand: any; private readonly syncThrottler: Throttler; + private syncPromise: CancelablePromise | undefined; private readonly synchronisers: IUserDataSynchroniser[]; private _status: SyncStatus = SyncStatus.Uninitialized; @@ -141,10 +143,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.recoveredSettings = true; } - await this.syncThrottler.queue(() => this.doSync()); + await this.syncThrottler.queue(() => { + this.syncPromise = createCancelablePromise(token => this.doSync(token)); + return this.syncPromise; + }); } - private async doSync(): Promise { + private async doSync(token: CancellationToken): Promise { const startTime = new Date().getTime(); this._syncErrors = []; try { @@ -171,6 +176,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ const machines = await this.userDataSyncMachinesService.getMachines(manifest || undefined); const currentMachine = machines.find(machine => machine.isCurrent); + // Return if cancellation is requested + if (token.isCancellationRequested) { + return; + } + // Check if sync was turned off from other machine if (currentMachine?.disabled) { // Unset the current machine @@ -180,6 +190,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } for (const synchroniser of this.synchronisers) { + // Return if cancellation is requested + if (token.isCancellationRequested) { + return; + } try { await synchroniser.sync(manifest); } catch (e) { @@ -193,16 +207,32 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ manifest = await this.userDataSyncStoreService.manifest(); } + // Return if cancellation is requested + if (token.isCancellationRequested) { + return; + } + // Update local session id if (manifest && manifest.session !== sessionId) { this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL); } + // Return if cancellation is requested + if (token.isCancellationRequested) { + return; + } + + // Add current machine if (!currentMachine) { const name = this.computeDefaultMachineName(machines); await this.userDataSyncMachinesService.addCurrentMachine(name, manifest || undefined); } + // Return if cancellation is requested + if (token.isCancellationRequested) { + return; + } + this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); this.updateLastSyncTime(); @@ -228,9 +258,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ async stop(): Promise { await this.checkEnablement(); + + if (this.syncPromise) { + this.syncPromise.cancel(); + this.logService.info('Canelled sync that is in progress'); + this.syncPromise = undefined; + } + if (this.status === SyncStatus.Idle) { return; } + for (const synchroniser of this.synchronisers) { try { if (synchroniser.status !== SyncStatus.Idle) { @@ -240,6 +278,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.error(e); } } + } async acceptConflict(conflict: URI, content: string): Promise { @@ -330,6 +369,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.error(e); } } + this.logService.info('Did reset the local sync state.'); } private async hasPreviouslySynced(): Promise { @@ -345,6 +385,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.checkEnablement(); try { await this.userDataSyncStoreService.clear(); + this.logService.info('Cleared data on server'); } catch (e) { this.logService.error(e); } diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataAutoSyncService.ts index c4836a46568..1f001705e8a 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -28,8 +28,15 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } triggerAutoSync(sources: string[]): Promise { - return this.channel.call('triggerAutoSync', [sources]); } + enable(): void { + this.channel.call('enable'); + } + + disable(): void { + this.channel.call('disable'); + } + } diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 343d988c062..0e0ce7faa6f 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IUserDataSyncEnablementService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncEnablementService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNCED_DATA_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync'; @@ -82,6 +82,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IQuickInputService private readonly quickInputService: IQuickInputService, @IStorageService private readonly storageService: IStorageService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, + @IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, @IProductService productService: IProductService, @@ -103,8 +104,8 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this.syncStatusContext.set(this.userDataSyncService.status); this._register(userDataSyncService.onDidChangeStatus(status => this.syncStatusContext.set(status))); - this.syncEnablementContext.set(this.userDataSyncEnablementService.isEnabled()); - this._register(this.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.syncEnablementContext.set(enabled))); + this.syncEnablementContext.set(userDataSyncEnablementService.isEnabled()); + this._register(userDataSyncEnablementService.onDidChangeEnablement(enabled => this.syncEnablementContext.set(enabled))); extensionService.whenInstalledExtensionsRegistered().then(() => { if (this.authenticationProviders.every(({ id }) => authenticationService.isAuthenticationProviderRegistered(id))) { @@ -222,18 +223,19 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } await this.handleFirstTimeSync(); - this.userDataSyncEnablementService.setEnablement(true); + this.userDataAutoSyncService.enable(); this.notificationService.info(localize('sync turned on', "Preferences sync is turned on")); } async turnoff(everywhere: boolean): Promise { + this.userDataAutoSyncService.disable(); + if (everywhere) { this.telemetryService.publicLog2('sync/turnOffEveryWhere'); await this.userDataSyncService.reset(); } else { await this.userDataSyncService.resetLocal(); } - this.userDataSyncEnablementService.setEnablement(false); } private async handleFirstTimeSync(): Promise {