提交 d022bb76 编写于 作者: S Sandeep Somavarapu

move common logic to abstract synchronizer

上级 dcef03a8
......@@ -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<void> {
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<boolean> {
const lastSyncData = await this.getLastSyncUserData();
return !!lastSyncData;
......@@ -74,6 +92,12 @@ export abstract class AbstractSynchroniser extends Disposable {
return remoteUserData.content !== null;
}
async getRemoteContent(): Promise<string | null> {
const lastSyncData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncData);
return remoteUserData.content;
}
async resetLocal(): Promise<void> {
try {
await this.fileService.del(this.lastSyncResource);
......@@ -94,11 +118,11 @@ export abstract class AbstractSynchroniser extends Disposable {
}
protected async getRemoteUserData(lastSyncData: IUserData | null): Promise<IUserData> {
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<string> {
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<void> {
......@@ -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<void>;
}
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<IFileSyncPreviewResult> | 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<void> {
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<string | null> {
if (preview) {
if (this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise;
return result.remoteUserData ? result.remoteUserData.content : null;
}
}
return super.getRemoteContent();
}
protected async getLocalFileContent(): Promise<IFileContent | null> {
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<void>;
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<FormattingOptions> | undefined = undefined;
protected getFormattingOptions(): Promise<FormattingOptions> {
if (!this._formattingOptions) {
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.file);
}
return this._formattingOptions;
}
}
......@@ -33,7 +33,8 @@ interface ILastSyncUserData extends IUserData {
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
protected get enabled(): boolean { return this.configurationService.getValue<boolean>('sync.enableExtensions') === true; }
protected get remoteDataResourceKey(): string { return 'extensions'; }
protected get enabled(): boolean { return this.configurationService.getValue<boolean>('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<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<void> {
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<void> {
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<void> { }
......@@ -172,6 +145,24 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return null;
}
protected async doSync(): Promise<void> {
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<ISyncPreviewResult> {
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.content!) : null;
......
......@@ -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<boolean>('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<void> {
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<void> {
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<void> { }
accept(content: string): Promise<void> {
......@@ -152,6 +121,24 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return null;
}
protected async doSync(): Promise<void> {
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<ISyncPreviewResult> {
const lastSyncUserData = await this.getLastSyncUserData();
const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null;
......
......@@ -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<ISyncPreviewResult> | null = null;
protected get remoteDataResourceKey(): string { return 'keybindings'; }
protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; }
protected get enabled(): boolean { return this.configurationService.getValue<boolean>('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<void> {
......@@ -81,7 +63,7 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
if (content !== null) {
const fileContent = await this.getLocalFileContent();
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<ISyncPreviewResult>({
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({
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<ISyncPreviewResult>({
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({
fileContent,
remoteUserData,
lastSyncUserData,
......@@ -146,28 +128,6 @@ export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements
}
async sync(): Promise<void> {
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<void> {
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<void> {
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<string | null> {
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<string | null> {
const content = await super.getRemoteContent(preview);
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
}
protected async doSync(): Promise<void> {
......@@ -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<ISyncPreviewResult> {
private getPreview(): Promise<IFileSyncPreviewResult> {
if (!this.syncPreviewResultPromise) {
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token));
}
return this.syncPreviewResultPromise;
}
private async generatePreview(token: CancellationToken): Promise<ISyncPreviewResult> {
private async generatePreview(token: CancellationToken): Promise<IFileSyncPreviewResult> {
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<FormattingOptions> | undefined = undefined;
private getFormattingOptions(): Promise<FormattingOptions> {
if (!this._formattingOptions) {
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource);
}
return this._formattingOptions;
}
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
try {
const parsed = <ISyncContent>JSON.parse(syncContent);
......
......@@ -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<ISyncPreviewResult> | null = null;
protected get remoteDataResourceKey(): string { return 'settings'; }
protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; }
protected get enabled(): boolean { return this.configurationService.getValue<boolean>('sync.enableSettings') === true; }
private _conflicts: IConflictSetting[] = [];
get conflicts(): IConflictSetting[] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<IConflictSetting[]> = this._register(new Emitter<IConflictSetting[]>());
readonly onDidChangeConflicts: Event<IConflictSetting[]> = this._onDidChangeConflicts.event;
protected get enabled(): boolean { return this.configurationService.getValue<boolean>('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<ISyncPreviewResult>({
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({
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<ISyncPreviewResult>({
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({
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<void> {
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<void> {
this.cancel();
this.logService.trace('Settings: Stopped synchronizing settings.');
await this.fileService.del(this.environmentService.settingsSyncPreviewResource);
this.setStatus(SyncStatus.Idle);
}
async hasLocalData(): Promise<boolean> {
try {
const localFileContent = await this.getLocalFileContent();
......@@ -216,21 +172,13 @@ export class SettingsSynchroniser extends AbstractFileSynchroniser implements IS
}
async getRemoteContent(preview?: boolean): Promise<string | null> {
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<void> {
......@@ -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<ISyncPreviewResult> {
private getPreview(resolvedConflicts: { key: string, value: any }[]): Promise<IFileSyncPreviewResult> {
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<ISyncPreviewResult> {
protected async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<IFileSyncPreviewResult> {
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<FormattingOptions> | undefined = undefined;
private getFormattingOptions(): Promise<FormattingOptions> {
if (!this._formattingOptions) {
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource);
}
return this._formattingOptions;
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册