From d022bb76a9b715477f042362f8bfa17378cd5890 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 9 Feb 2020 15:13:51 +0100 Subject: [PATCH] move common logic to abstract synchronizer --- .../common/abstractSynchronizer.ts | 110 ++++++++++++++++-- .../userDataSync/common/extensionsSync.ts | 55 ++++----- .../userDataSync/common/globalStateSync.ts | 55 ++++----- .../userDataSync/common/keybindingsSync.ts | 100 +++------------- .../userDataSync/common/settingsSync.ts | 110 ++++-------------- 5 files changed, 186 insertions(+), 244 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index a14c3d62297..7c730a3afc7 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -7,13 +7,15 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { joinPath, dirname } from 'vs/base/common/resources'; import { toLocalISOString } from 'vs/base/common/date'; -import { ThrottledDelayer } from 'vs/base/common/async'; +import { ThrottledDelayer, CancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ParseError, parse } from 'vs/base/common/json'; +import { FormattingOptions } from 'vs/base/common/jsonFormatter'; type SyncConflictsClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -40,6 +42,7 @@ export abstract class AbstractSynchroniser extends Disposable { @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, ) { super(); this.syncFolder = joinPath(environmentService.userDataSyncHome, source); @@ -63,6 +66,21 @@ export abstract class AbstractSynchroniser extends Disposable { } } + async sync(): Promise { + if (!this.enabled) { + this.logService.info(`${this.source}: Skipping synchronizing ${this.source.toLowerCase()} as it is disabled.`); + return; + } + if (this.status !== SyncStatus.Idle) { + this.logService.info(`${this.source}: Skipping synchronizing ${this.source.toLowerCase()} as it is running already.`); + return; + } + + this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`); + this.setStatus(SyncStatus.Syncing); + return this.doSync(); + } + async hasPreviouslySynced(): Promise { const lastSyncData = await this.getLastSyncUserData(); return !!lastSyncData; @@ -74,6 +92,12 @@ export abstract class AbstractSynchroniser extends Disposable { return remoteUserData.content !== null; } + async getRemoteContent(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + const remoteUserData = await this.getRemoteUserData(lastSyncData); + return remoteUserData.content; + } + async resetLocal(): Promise { try { await this.fileService.del(this.lastSyncResource); @@ -94,11 +118,11 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async getRemoteUserData(lastSyncData: IUserData | null): Promise { - return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData, this.source); + return this.userDataSyncStoreService.read(this.remoteDataResourceKey, lastSyncData, this.source); } protected async updateRemoteUserData(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(this.getRemoteDataResourceKey(), content, ref, this.source); + return this.userDataSyncStoreService.write(this.remoteDataResourceKey, content, ref, this.source); } protected async backupLocal(content: VSBuffer): Promise { @@ -117,24 +141,55 @@ export abstract class AbstractSynchroniser extends Disposable { } protected abstract readonly enabled: boolean; - protected abstract getRemoteDataResourceKey(): string; + protected abstract readonly remoteDataResourceKey: string; + protected abstract doSync(): Promise; +} + +export interface IFileSyncPreviewResult { + readonly fileContent: IFileContent | null; + readonly remoteUserData: IUserData; + readonly lastSyncUserData: IUserData | null; + readonly content: string | null; + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; + readonly hasConflicts: boolean; } export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { + protected syncPreviewResultPromise: CancelablePromise | null = null; + constructor( protected readonly file: URI, - readonly source: SyncSource, + source: SyncSource, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @ITelemetryService telemetryService: ITelemetryService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, ) { - super(source, fileService, environmentService, userDataSyncStoreService, telemetryService); + super(source, fileService, environmentService, userDataSyncStoreService, telemetryService, logService); this._register(this.fileService.watch(dirname(file))); this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); } + async stop(): Promise { + this.cancel(); + this.logService.trace(`${this.source}: Stopped synchronizing ${this.source.toLowerCase()}.`); + await this.fileService.del(this.conflictsPreviewResource); + this.setStatus(SyncStatus.Idle); + } + + async getRemoteContent(preview?: boolean): Promise { + if (preview) { + if (this.syncPreviewResultPromise) { + const result = await this.syncPreviewResultPromise; + return result.remoteUserData ? result.remoteUserData.content : null; + } + } + return super.getRemoteContent(); + } + protected async getLocalFileContent(): Promise { try { return await this.fileService.readFile(this.file); @@ -182,6 +237,43 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { } - protected abstract cancel(): void; - protected abstract doSync(): Promise; + protected cancel(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + } + } + + protected abstract readonly conflictsPreviewResource: URI; +} + +export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser { + + constructor( + file: URI, + source: SyncSource, + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @ITelemetryService telemetryService: ITelemetryService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, + ) { + super(file, source, fileService, environmentService, userDataSyncStoreService, telemetryService, logService); + } + + protected hasErrors(content: string): boolean { + const parseErrors: ParseError[] = []; + parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); + return parseErrors.length > 0; + } + + private _formattingOptions: Promise | undefined = undefined; + protected getFormattingOptions(): Promise { + if (!this._formattingOptions) { + this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.file); + } + return this._formattingOptions; + } + } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 5fcafebec70..5534b6effe5 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -33,7 +33,8 @@ interface ILastSyncUserData extends IUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - protected get enabled(): boolean { return this.configurationService.getValue('sync.enableExtensions') === true; } + protected get remoteDataResourceKey(): string { return 'extensions'; } + protected get enabled(): boolean { return this.configurationService.getValue('sync.enableExtensions') === true && this.extensionGalleryService.isEnabled(); } constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -41,12 +42,12 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, telemetryService); + super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, telemetryService, logService); this._register( Event.debounce( Event.any( @@ -56,8 +57,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse () => undefined, 500)(() => this._onDidChangeLocal.fire())); } - protected getRemoteDataResourceKey(): string { return 'extensions'; } - async pull(): Promise { if (!this.enabled) { this.logService.info('Extensions: Skipped pulling extensions as it is disabled.'); @@ -117,37 +116,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } async sync(): Promise { - if (!this.enabled) { - this.logService.info('Extensions: Skipping synchronizing extensions as it is disabled.'); - return; - } if (!this.extensionGalleryService.isEnabled()) { this.logService.info('Extensions: Skipping synchronizing extensions as gallery is disabled.'); return; } - if (this.status !== SyncStatus.Idle) { - this.logService.info('Extensions: Skipping synchronizing extensions as it is running already.'); - return; - } - - this.logService.trace('Extensions: Started synchronizing extensions...'); - this.setStatus(SyncStatus.Syncing); - - try { - const previewResult = await this.getPreview(); - await this.apply(previewResult); - } catch (e) { - this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - throw e; - } - - this.logService.trace('Extensions: Finished synchronizing extensions.'); - this.setStatus(SyncStatus.Idle); + return super.sync(); } async stop(): Promise { } @@ -172,6 +145,24 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return null; } + protected async doSync(): Promise { + try { + const previewResult = await this.getPreview(); + await this.apply(previewResult); + } catch (e) { + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + throw e; + } + + this.logService.trace('Extensions: Finished synchronizing extensions.'); + this.setStatus(SyncStatus.Idle); + } + private async getPreview(): Promise { const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.content!) : null; diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index ec3bf3580e1..c11dba6fd0f 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -28,23 +28,22 @@ interface ISyncPreviewResult { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { + protected get remoteDataResourceKey(): string { return 'globalState'; } protected get enabled(): boolean { return this.configurationService.getValue('sync.enableUIState') === true; } constructor( @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, telemetryService); + super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, telemetryService, logService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } - protected getRemoteDataResourceKey(): string { return 'globalState'; } - async pull(): Promise { if (!this.enabled) { this.logService.info('UI State: Skipped pulling ui state as it is disabled.'); @@ -100,36 +99,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } - async sync(): Promise { - if (!this.enabled) { - this.logService.info('UI State: Skipping synchronizing UI state as it is disabled.'); - return; - } - if (this.status !== SyncStatus.Idle) { - this.logService.info('UI State: Skipping synchronizing ui state as it is running already.'); - return; - } - - this.logService.trace('UI State: Started synchronizing ui state...'); - this.setStatus(SyncStatus.Syncing); - - try { - const result = await this.getPreview(); - await this.apply(result); - this.logService.trace('UI State: Finished synchronizing ui state.'); - } catch (e) { - this.setStatus(SyncStatus.Idle); - 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 synchronize ui state as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - throw e; - } finally { - this.setStatus(SyncStatus.Idle); - } - } - async stop(): Promise { } accept(content: string): Promise { @@ -152,6 +121,24 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return null; } + protected async doSync(): Promise { + try { + const result = await this.getPreview(); + await this.apply(result); + this.logService.trace('UI State: Finished synchronizing ui state.'); + } catch (e) { + this.setStatus(SyncStatus.Idle); + 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 synchronize ui state as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + throw e; + } finally { + this.setStatus(SyncStatus.Idle); + } + } + private async getPreview(): Promise { const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null; diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index e4e542beebd..ae0e9dc35dd 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -3,22 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileService, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { 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'; +import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; -import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { URI } from 'vs/base/common/uri'; interface ISyncContent { mac?: string; @@ -27,40 +27,22 @@ interface ISyncContent { all?: string; } -interface ISyncPreviewResult { - readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData; - readonly lastSyncUserData: IUserData | null; - readonly content: string | null; - readonly hasLocalChanged: boolean; - readonly hasRemoteChanged: boolean; - readonly hasConflicts: boolean; -} - -export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements IUserDataSynchroniser { +export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser { - private syncPreviewResultPromise: CancelablePromise | null = null; + protected get remoteDataResourceKey(): string { return 'keybindings'; } + protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; } protected get enabled(): boolean { return this.configurationService.getValue('sync.enableKeybindings') === true; } constructor( @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, telemetryService); - } - - protected getRemoteDataResourceKey(): string { return 'keybindings'; } - - protected cancel(): void { - if (this.syncPreviewResultPromise) { - this.syncPreviewResultPromise.cancel(); - this.syncPreviewResultPromise = null; - } + super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, telemetryService, logService, userDataSyncUtilService); } async pull(): Promise { @@ -81,7 +63,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements if (content !== null) { const fileContent = await this.getLocalFileContent(); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, lastSyncUserData, @@ -122,7 +104,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements if (fileContent !== null) { const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, lastSyncUserData, @@ -146,28 +128,6 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements } - async sync(): Promise { - if (!this.enabled) { - this.logService.info('Keybindings: Skipping synchronizing keybindings as it is disabled.'); - return; - } - if (this.status !== SyncStatus.Idle) { - this.logService.info('Keybindings: Skipping synchronizing keybindings as it is running already.'); - return; - } - - this.logService.trace('Keybindings: Started synchronizing keybindings...'); - this.setStatus(SyncStatus.Syncing); - return this.doSync(); - } - - async stop(): Promise { - this.cancel(); - this.logService.trace('Keybindings: Stopped synchronizing keybindings.'); - await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); - this.setStatus(SyncStatus.Idle); - } - async accept(content: string): Promise { if (this.status === SyncStatus.HasConflicts) { const preview = await this.syncPreviewResultPromise!; @@ -195,17 +155,9 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements return false; } - async getRemoteContent(): Promise { - let content: string | null | undefined = null; - if (this.syncPreviewResultPromise) { - const preview = await this.syncPreviewResultPromise; - content = preview.remoteUserData?.content; - } else { - const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncData); - content = remoteUserData.content; - } - return content ? this.getKeybindingsContentFromSyncContent(content) : null; + async getRemoteContent(preview?: boolean): Promise { + const content = await super.getRemoteContent(preview); + return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null; } protected async doSync(): Promise { @@ -285,20 +237,14 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements this.syncPreviewResultPromise = null; } - private hasErrors(content: string): boolean { - const parseErrors: ParseError[] = []; - parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); - return parseErrors.length > 0; - } - - private getPreview(): Promise { + private getPreview(): Promise { if (!this.syncPreviewResultPromise) { this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token)); } return this.syncPreviewResultPromise; } - private async generatePreview(token: CancellationToken): Promise { + private async generatePreview(token: CancellationToken): Promise { const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncContent = lastSyncUserData && lastSyncUserData.content ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.content) : null; const remoteUserData = await this.getRemoteUserData(lastSyncUserData); @@ -349,14 +295,6 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; } - private _formattingOptions: Promise | undefined = undefined; - private getFormattingOptions(): Promise { - if (!this._formattingOptions) { - this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource); - } - return this._formattingOptions; - } - private getKeybindingsContentFromSyncContent(syncContent: string): string | null { try { const parsed = JSON.parse(syncContent); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 56735f2016a..98249ea52fd 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -3,63 +3,50 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileService, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, 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 { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; -import { isEmptyObject, isUndefinedOrNull } from 'vs/base/common/types'; +import { isEmptyObject } from 'vs/base/common/types'; import { edit } from 'vs/platform/userDataSync/common/content'; -import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { URI } from 'vs/base/common/uri'; -interface ISyncPreviewResult { - readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData; - readonly lastSyncUserData: IUserData | null; - readonly content: string | null; - readonly hasLocalChanged: boolean; - readonly hasRemoteChanged: boolean; - readonly hasConflicts: boolean; - readonly conflictSettings: IConflictSetting[]; -} - -export class SettingsSynchroniser extends AbstractFileSynchroniser implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService { _serviceBrand: any; - private syncPreviewResultPromise: CancelablePromise | null = null; + protected get remoteDataResourceKey(): string { return 'settings'; } + protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; } + protected get enabled(): boolean { return this.configurationService.getValue('sync.enableSettings') === true; } private _conflicts: IConflictSetting[] = []; get conflicts(): IConflictSetting[] { return this._conflicts; } private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - protected get enabled(): boolean { return this.configurationService.getValue('sync.enableSettings') === true; } - constructor( @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, telemetryService); + super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, telemetryService, logService, userDataSyncUtilService); } - protected getRemoteDataResourceKey(): string { return 'settings'; } - protected setStatus(status: SyncStatus): void { super.setStatus(status); if (this.status !== SyncStatus.HasConflicts) { @@ -67,13 +54,6 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } } - protected cancel(): void { - if (this.syncPreviewResultPromise) { - this.syncPreviewResultPromise.cancel(); - this.syncPreviewResultPromise = null; - } - } - private setConflicts(conflicts: IConflictSetting[]): void { if (!arrays.equals(this.conflicts, conflicts, (a, b) => a.key === b.key && objects.equals(a.localValue, b.localValue) && objects.equals(a.remoteValue, b.remoteValue)) @@ -103,7 +83,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS const formatUtils = await this.getFormattingOptions(); // Update ignored settings from local file content const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, lastSyncUserData, @@ -111,7 +91,6 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS hasLocalChanged: true, hasRemoteChanged: false, hasConflicts: false, - conflictSettings: [], })); await this.apply(); @@ -149,7 +128,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); - this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, lastSyncUserData, @@ -157,7 +136,6 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS hasRemoteChanged: true, hasLocalChanged: false, hasConflicts: false, - conflictSettings: [], })); await this.apply(true); @@ -174,28 +152,6 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } } - async sync(): Promise { - if (!this.enabled) { - this.logService.info('Settings: Skipping synchronizing settings as it is disabled.'); - return; - } - if (this.status !== SyncStatus.Idle) { - this.logService.info('Settings: Skipping synchronizing settings as it is running already.'); - return; - } - - this.logService.trace('Settings: Started synchronizing settings...'); - this.setStatus(SyncStatus.Syncing); - return this.doSync([]); - } - - async stop(): Promise { - this.cancel(); - this.logService.trace('Settings: Stopped synchronizing settings.'); - await this.fileService.del(this.environmentService.settingsSyncPreviewResource); - this.setStatus(SyncStatus.Idle); - } - async hasLocalData(): Promise { try { const localFileContent = await this.getLocalFileContent(); @@ -216,21 +172,13 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } async getRemoteContent(preview?: boolean): Promise { - let content: string | null | undefined = null; - if (this.syncPreviewResultPromise) { - const preview = await this.syncPreviewResultPromise; - content = preview.remoteUserData?.content; - } else { - const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.getRemoteUserData(lastSyncData); - content = remoteUserData.content; - } - if (preview && !isUndefinedOrNull(content)) { + let content = await super.getRemoteContent(preview); + if (preview && content !== null) { const formatUtils = await this.getFormattingOptions(); // remove ignored settings from the remote content for preview content = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils); } - return content !== undefined ? content : null; + return content; } async accept(content: string): Promise { @@ -331,20 +279,14 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS this.syncPreviewResultPromise = null; } - private hasErrors(content: string): boolean { - const parseErrors: ParseError[] = []; - parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); - return parseErrors.length > 0; - } - - private getPreview(resolvedConflicts: { key: string, value: any }[]): Promise { + private getPreview(resolvedConflicts: { key: string, value: any }[]): Promise { if (!this.syncPreviewResultPromise) { this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(resolvedConflicts, token)); } return this.syncPreviewResultPromise; } - private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise { + protected async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise { const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); // Get file content last to get the latest @@ -390,15 +332,7 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS } this.setConflicts(conflictSettings); - return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, conflictSettings, hasConflicts }; - } - - private _formattingOptions: Promise | undefined = undefined; - private getFormattingOptions(): Promise { - if (!this._formattingOptions) { - this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource); - } - return this._formattingOptions; + return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; } } -- GitLab