mirror of
https://github.com/Microsoft/vscode
synced 2024-11-05 18:29:38 +00:00
refactor sync to reflect preview, merge, accept & apply.
This commit is contained in:
parent
e2de23c683
commit
8ca1cf6293
18 changed files with 657 additions and 991 deletions
|
@ -53,20 +53,38 @@ function isSyncData(thing: any): thing is ISyncData {
|
|||
return false;
|
||||
}
|
||||
|
||||
export interface IMergableResourcePreview extends IBaseResourcePreview {
|
||||
export interface IResourcePreview {
|
||||
|
||||
readonly remoteResource: URI;
|
||||
readonly remoteContent: string | null;
|
||||
readonly remoteChange: Change;
|
||||
|
||||
readonly localResource: URI;
|
||||
readonly localContent: string | null;
|
||||
readonly previewContent: string | null;
|
||||
readonly acceptedContent: string | null;
|
||||
readonly localChange: Change;
|
||||
|
||||
readonly previewResource: URI;
|
||||
}
|
||||
|
||||
export interface IAcceptResult {
|
||||
readonly content: string | null;
|
||||
readonly localChange: Change;
|
||||
readonly remoteChange: Change;
|
||||
}
|
||||
|
||||
export interface IMergeResult extends IAcceptResult {
|
||||
readonly hasConflicts: boolean;
|
||||
}
|
||||
|
||||
export type IResourcePreview = Omit<IMergableResourcePreview, 'mergeState'>;
|
||||
export interface IResourcePreviewResult extends IBaseResourcePreview, IResourcePreview {
|
||||
mergeResult?: IMergeResult;
|
||||
acceptResult?: IAcceptResult;
|
||||
}
|
||||
|
||||
export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
|
||||
readonly remoteUserData: IRemoteUserData;
|
||||
readonly lastSyncUserData: IRemoteUserData | null;
|
||||
readonly resourcePreviews: IMergableResourcePreview[];
|
||||
readonly resourcePreviews: IResourcePreviewResult[];
|
||||
}
|
||||
|
||||
export abstract class AbstractSynchroniser extends Disposable {
|
||||
|
@ -82,10 +100,10 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
|
||||
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
|
||||
|
||||
private _conflicts: IMergableResourcePreview[] = [];
|
||||
get conflicts(): IMergableResourcePreview[] { return this._conflicts; }
|
||||
private _onDidChangeConflicts: Emitter<IMergableResourcePreview[]> = this._register(new Emitter<IMergableResourcePreview[]>());
|
||||
readonly onDidChangeConflicts: Event<IMergableResourcePreview[]> = this._onDidChangeConflicts.event;
|
||||
private _conflicts: IResourcePreviewResult[] = [];
|
||||
get conflicts(): IResourcePreviewResult[] { return this._conflicts; }
|
||||
private _onDidChangeConflicts: Emitter<IResourcePreviewResult[]> = this._register(new Emitter<IResourcePreviewResult[]>());
|
||||
readonly onDidChangeConflicts: Event<IResourcePreviewResult[]> = this._onDidChangeConflicts.event;
|
||||
|
||||
private readonly localChangeTriggerScheduler = new RunOnceScheduler(() => this.doTriggerLocalChange(), 50);
|
||||
private readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
|
||||
|
@ -162,53 +180,6 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
async pull(): Promise<void> {
|
||||
if (!this.isEnabled()) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling ${this.syncResourceLogLabel.toLowerCase()} as it is disabled.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.stop();
|
||||
|
||||
try {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Started pulling ${this.syncResourceLogLabel.toLowerCase()}...`);
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const preview = await this.generatePullPreview(remoteUserData, lastSyncUserData, CancellationToken.None);
|
||||
|
||||
await this.applyPreview(remoteUserData, lastSyncUserData, preview, false);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Finished pulling ${this.syncResourceLogLabel.toLowerCase()}.`);
|
||||
} finally {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
}
|
||||
}
|
||||
|
||||
async push(): Promise<void> {
|
||||
if (!this.isEnabled()) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing ${this.syncResourceLogLabel.toLowerCase()} as it is disabled.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.stop();
|
||||
|
||||
try {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Started pushing ${this.syncResourceLogLabel.toLowerCase()}...`);
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
const preview = await this.generatePushPreview(remoteUserData, lastSyncUserData, CancellationToken.None);
|
||||
|
||||
await this.applyPreview(remoteUserData, lastSyncUserData, preview, true);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Finished pushing ${this.syncResourceLogLabel.toLowerCase()}.`);
|
||||
|
||||
} finally {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
}
|
||||
}
|
||||
|
||||
async sync(manifest: IUserDataManifest | null, headers: IHeaders = {}): Promise<void> {
|
||||
await this._sync(manifest, true, headers);
|
||||
}
|
||||
|
@ -292,8 +263,17 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
this.setStatus(SyncStatus.Syncing);
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getLatestRemoteUserData(null, lastSyncUserData);
|
||||
const preview = await this.generateReplacePreview(syncData, remoteUserData, lastSyncUserData);
|
||||
await this.applyPreview(remoteUserData, lastSyncUserData, preview, false);
|
||||
|
||||
const resourcePreviewResults = await this.generateSyncPreview(remoteUserData, lastSyncUserData, CancellationToken.None);
|
||||
|
||||
const resourcePreviews: [IResourcePreview, IAcceptResult][] = [];
|
||||
for (const resourcePreviewResult of resourcePreviewResults) {
|
||||
/* Accept remote resource */
|
||||
const acceptResult: IAcceptResult = await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.remoteResource, resourcePreviewResult.remoteContent, CancellationToken.None);
|
||||
resourcePreviews.push([resourcePreviewResult, acceptResult]);
|
||||
}
|
||||
|
||||
await this.applyResult(remoteUserData, lastSyncUserData, resourcePreviews, false);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Finished resetting ${this.resource.toLowerCase()}.`);
|
||||
} finally {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
|
@ -384,23 +364,35 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
async accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null> {
|
||||
async merge(resource: URI): Promise<ISyncResourcePreview | null> {
|
||||
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
|
||||
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content);
|
||||
const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None);
|
||||
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(mergeResult?.content || ''));
|
||||
const acceptResult: IAcceptResult | undefined = mergeResult && !mergeResult.hasConflicts
|
||||
? await this.getAcceptResult(resourcePreview, resourcePreview.previewResource, undefined, CancellationToken.None)
|
||||
: undefined;
|
||||
return {
|
||||
...updatedResourcePreview,
|
||||
mergeState: MergeState.Accepted
|
||||
...resourcePreview,
|
||||
mergeResult,
|
||||
acceptResult,
|
||||
mergeState: mergeResult.hasConflicts ? MergeState.Conflict : acceptResult ? MergeState.Accepted : MergeState.Preview,
|
||||
localChange: acceptResult ? acceptResult.localChange : mergeResult.localChange,
|
||||
remoteChange: acceptResult ? acceptResult.remoteChange : mergeResult.remoteChange
|
||||
};
|
||||
});
|
||||
return this.syncPreviewPromise;
|
||||
}
|
||||
|
||||
async merge(resource: URI): Promise<ISyncResourcePreview | null> {
|
||||
async accept(resource: URI, content?: string | null): Promise<ISyncResourcePreview | null> {
|
||||
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
|
||||
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
|
||||
const acceptResult = await this.getAcceptResult(resourcePreview, resource, content, CancellationToken.None);
|
||||
return {
|
||||
...updatedResourcePreview,
|
||||
mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted
|
||||
...resourcePreview,
|
||||
acceptResult,
|
||||
mergeResult: undefined,
|
||||
mergeState: MergeState.Accepted,
|
||||
localChange: acceptResult.localChange,
|
||||
remoteChange: acceptResult.remoteChange
|
||||
};
|
||||
});
|
||||
return this.syncPreviewPromise;
|
||||
|
@ -408,17 +400,21 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
|
||||
async discard(resource: URI): Promise<ISyncResourcePreview | null> {
|
||||
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
|
||||
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
|
||||
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
|
||||
const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None);
|
||||
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(mergeResult.content || ''));
|
||||
return {
|
||||
...updatedResourcePreview,
|
||||
mergeState: MergeState.Preview
|
||||
...resourcePreview,
|
||||
mergeResult: undefined,
|
||||
acceptResult: undefined,
|
||||
mergeState: MergeState.Preview,
|
||||
localChange: mergeResult.localChange,
|
||||
remoteChange: mergeResult.remoteChange
|
||||
};
|
||||
});
|
||||
return this.syncPreviewPromise;
|
||||
}
|
||||
|
||||
private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise<IMergableResourcePreview>): Promise<void> {
|
||||
private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IResourcePreviewResult) => Promise<IResourcePreviewResult>): Promise<void> {
|
||||
if (!this.syncPreviewPromise) {
|
||||
return;
|
||||
}
|
||||
|
@ -448,13 +444,6 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise<IResourcePreview> {
|
||||
return {
|
||||
...resourcePreview,
|
||||
acceptedContent
|
||||
};
|
||||
}
|
||||
|
||||
private async doApply(force: boolean): Promise<SyncStatus> {
|
||||
if (!this.syncPreviewPromise) {
|
||||
return SyncStatus.Idle;
|
||||
|
@ -463,17 +452,17 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
const preview = await this.syncPreviewPromise;
|
||||
|
||||
// check for conflicts
|
||||
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
|
||||
if (preview.resourcePreviews.some(({ mergeResult }) => mergeResult?.hasConflicts)) {
|
||||
return SyncStatus.HasConflicts;
|
||||
}
|
||||
|
||||
// check if all are accepted
|
||||
if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) {
|
||||
if (preview.resourcePreviews.some(({ localChange, remoteChange, acceptResult }) => !acceptResult)) {
|
||||
return SyncStatus.Syncing;
|
||||
}
|
||||
|
||||
// apply preview
|
||||
await this.applyPreview(preview.remoteUserData, preview.lastSyncUserData, preview.resourcePreviews, force);
|
||||
await this.applyResult(preview.remoteUserData, preview.lastSyncUserData, preview.resourcePreviews.map(resourcePreview => ([resourcePreview, resourcePreview.acceptResult!])), force);
|
||||
|
||||
// reset preview
|
||||
this.syncPreviewPromise = null;
|
||||
|
@ -490,8 +479,8 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
} catch (error) { /* Ignore */ }
|
||||
}
|
||||
|
||||
private updateConflicts(previews: IMergableResourcePreview[]): void {
|
||||
const conflicts = previews.filter(p => p.mergeState === MergeState.Conflict);
|
||||
private updateConflicts(resourcePreviews: IResourcePreviewResult[]): void {
|
||||
const conflicts = resourcePreviews.filter(r => r.mergeResult?.hasConflicts);
|
||||
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
|
||||
this._conflicts = conflicts;
|
||||
this._onDidChangeConflicts.fire(conflicts);
|
||||
|
@ -550,7 +539,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
if (syncPreview) {
|
||||
for (const resourcePreview of syncPreview.resourcePreviews) {
|
||||
if (isEqual(resourcePreview.acceptedResource, uri)) {
|
||||
return resourcePreview.acceptedContent;
|
||||
return resourcePreview.acceptResult ? resourcePreview.acceptResult.content : null;
|
||||
}
|
||||
if (isEqual(resourcePreview.remoteResource, uri)) {
|
||||
return resourcePreview.remoteContent;
|
||||
|
@ -575,22 +564,47 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
|
||||
// For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine
|
||||
const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData;
|
||||
const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
|
||||
const resourcePreviewResults = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
|
||||
|
||||
const resourcePreviews: IMergableResourcePreview[] = [];
|
||||
for (const resourcePreview of result) {
|
||||
if (token.isCancellationRequested) {
|
||||
break;
|
||||
const resourcePreviews: IResourcePreviewResult[] = [];
|
||||
for (const resourcePreviewResult of resourcePreviewResults) {
|
||||
const acceptedResource = resourcePreviewResult.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
|
||||
|
||||
/* No change -> Accept */
|
||||
if (resourcePreviewResult.localChange === Change.None && resourcePreviewResult.remoteChange === Change.None) {
|
||||
resourcePreviews.push({
|
||||
...resourcePreviewResult,
|
||||
acceptedResource,
|
||||
acceptResult: { content: null, localChange: Change.None, remoteChange: Change.None },
|
||||
mergeState: MergeState.Accepted
|
||||
});
|
||||
}
|
||||
if (!apply) {
|
||||
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
|
||||
|
||||
/* Changed -> Apply ? (Merge ? Conflict | Accept) : Preview */
|
||||
else {
|
||||
/* Merge */
|
||||
const mergeResult = apply ? await this.getMergeResult(resourcePreviewResult, token) : undefined;
|
||||
if (token.isCancellationRequested) {
|
||||
break;
|
||||
}
|
||||
await this.fileService.writeFile(resourcePreviewResult.previewResource, VSBuffer.fromString(mergeResult?.content || ''));
|
||||
|
||||
/* Conflict | Accept */
|
||||
const acceptResult = mergeResult && !mergeResult.hasConflicts
|
||||
/* Accept if merged and there are no conflicts */
|
||||
? await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.previewResource, undefined, token)
|
||||
: undefined;
|
||||
|
||||
resourcePreviews.push({
|
||||
...resourcePreviewResult,
|
||||
acceptedResource: resourcePreviewResult.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
mergeResult,
|
||||
acceptResult,
|
||||
mergeState: mergeResult?.hasConflicts ? MergeState.Conflict : acceptResult ? MergeState.Accepted : MergeState.Preview,
|
||||
localChange: acceptResult ? acceptResult.localChange : mergeResult ? mergeResult.localChange : resourcePreviewResult.localChange,
|
||||
remoteChange: acceptResult ? acceptResult.remoteChange : mergeResult ? mergeResult.remoteChange : resourcePreviewResult.remoteChange
|
||||
});
|
||||
}
|
||||
resourcePreviews.push({
|
||||
...resourcePreview,
|
||||
mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */
|
||||
: apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted)
|
||||
: MergeState.Preview
|
||||
});
|
||||
}
|
||||
|
||||
return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine };
|
||||
|
@ -687,11 +701,10 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
}
|
||||
|
||||
protected abstract readonly version: number;
|
||||
protected abstract generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]>;
|
||||
protected abstract generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]>;
|
||||
protected abstract generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IResourcePreview[]>;
|
||||
protected abstract generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]>;
|
||||
protected abstract applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IResourcePreview[], forcePush: boolean): Promise<void>;
|
||||
protected abstract getMergeResult(resourcePreview: IResourcePreview, token: CancellationToken): Promise<IMergeResult>;
|
||||
protected abstract getAcceptResult(resourcePreview: IResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult>;
|
||||
protected abstract applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, result: [IResourcePreview, IAcceptResult][], force: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IFileResourcePreview extends IResourcePreview {
|
||||
|
|
|
@ -15,7 +15,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
|
|||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
import { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources';
|
||||
|
@ -25,13 +25,17 @@ import { compare } from 'vs/base/common/strings';
|
|||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export interface IExtensionResourcePreview extends IResourcePreview {
|
||||
readonly localExtensions: ISyncExtension[];
|
||||
interface IExtensionResourceMergeResult extends IAcceptResult {
|
||||
readonly added: ISyncExtension[];
|
||||
readonly removed: IExtensionIdentifier[];
|
||||
readonly updated: ISyncExtension[];
|
||||
readonly remote: ISyncExtension[] | null;
|
||||
}
|
||||
|
||||
interface IExtensionResourcePreview extends IResourcePreview {
|
||||
readonly localExtensions: ISyncExtension[];
|
||||
readonly skippedExtensions: ISyncExtension[];
|
||||
readonly previewResult: IExtensionResourceMergeResult;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IRemoteUserData {
|
||||
|
@ -75,47 +79,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
() => undefined, 500)(() => this.triggerLocalChange()));
|
||||
}
|
||||
|
||||
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IExtensionResourcePreview[]> {
|
||||
const remoteExtensions = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
|
||||
const pullPreview = await this.getPullPreview(remoteExtensions);
|
||||
return [pullPreview];
|
||||
}
|
||||
|
||||
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IExtensionResourcePreview[]> {
|
||||
const remoteExtensions = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
|
||||
const pushPreview = await this.getPushPreview(remoteExtensions);
|
||||
return [pushPreview];
|
||||
}
|
||||
|
||||
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const remoteExtensions = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
|
||||
const syncExtensions = await this.parseAndMigrateExtensions(syncData);
|
||||
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
|
||||
const mergeResult = merge(localExtensions, syncExtensions, localExtensions, [], ignoredExtensions);
|
||||
const { added, removed, updated } = mergeResult;
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localExtensions),
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote: syncExtensions,
|
||||
localExtensions,
|
||||
skippedExtensions: [],
|
||||
localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: Change.Modified,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
|
||||
|
@ -131,32 +94,100 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
this.logService.trace(`${this.syncResourceLogLabel}: Remote extensions does not exist. Synchronizing extensions for the first time.`);
|
||||
}
|
||||
|
||||
const mergeResult = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions);
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localExtensions),
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions);
|
||||
const previewResult: IExtensionResourceMergeResult = {
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote,
|
||||
localExtensions,
|
||||
skippedExtensions,
|
||||
content: null,
|
||||
localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
};
|
||||
|
||||
return [{
|
||||
skippedExtensions,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localExtensions),
|
||||
localExtensions,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
}];
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: IExtensionResourcePreview[], force: boolean): Promise<void> {
|
||||
let { added, removed, updated, remote, skippedExtensions, localExtensions, localChange, remoteChange } = resourcePreviews[0];
|
||||
protected async getMergeResult(resourcePreview: IExtensionResourcePreview, token: CancellationToken): Promise<IMergeResult> {
|
||||
return { ...resourcePreview.previewResult, hasConflicts: false };
|
||||
}
|
||||
|
||||
protected async getAcceptResult(resourcePreview: IExtensionResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IExtensionResourceMergeResult> {
|
||||
|
||||
/* Accept local resource */
|
||||
if (isEqual(resource, this.localResource)) {
|
||||
return this.acceptLocal(resourcePreview);
|
||||
}
|
||||
|
||||
/* Accept remote resource */
|
||||
if (isEqual(resource, this.remoteResource)) {
|
||||
return this.acceptRemote(resourcePreview);
|
||||
}
|
||||
|
||||
/* Accept preview resource */
|
||||
if (isEqual(resource, this.previewResource)) {
|
||||
return resourcePreview.previewResult;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Resource: ${resource.toString()}`);
|
||||
}
|
||||
|
||||
private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
|
||||
const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions);
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
return {
|
||||
content: resourcePreview.localContent,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote,
|
||||
localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
};
|
||||
}
|
||||
|
||||
private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
|
||||
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
|
||||
if (remoteExtensions !== null) {
|
||||
const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, resourcePreview.localExtensions, [], ignoredExtensions);
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
return {
|
||||
content: resourcePreview.remoteContent,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote,
|
||||
localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: resourcePreview.remoteContent,
|
||||
added: [], removed: [], updated: [], remote: null,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IExtensionResourcePreview, IExtensionResourceMergeResult][], force: boolean): Promise<void> {
|
||||
let { skippedExtensions, localExtensions } = resourcePreviews[0][0];
|
||||
let { added, removed, updated, remote, localChange, remoteChange } = resourcePreviews[0][1];
|
||||
|
||||
if (localChange === Change.None && remoteChange === Change.None) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing extensions.`);
|
||||
|
@ -183,97 +214,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
}
|
||||
}
|
||||
|
||||
protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string | null): Promise<IExtensionResourcePreview> {
|
||||
if (isEqual(resource, this.localResource)) {
|
||||
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
|
||||
return this.getPushPreview(remoteExtensions);
|
||||
}
|
||||
return {
|
||||
...resourcePreview,
|
||||
acceptedContent,
|
||||
hasConflicts: false,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
}
|
||||
|
||||
private async getPullPreview(remoteExtensions: ISyncExtension[] | null): Promise<IExtensionResourcePreview> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
|
||||
const localResource = this.localResource;
|
||||
const localContent = this.format(localExtensions);
|
||||
const remoteResource = this.remoteResource;
|
||||
const previewResource = this.previewResource;
|
||||
const acceptedResource = this.acceptedResource;
|
||||
const previewContent = null;
|
||||
if (remoteExtensions !== null) {
|
||||
const mergeResult = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions);
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
return {
|
||||
localResource,
|
||||
localContent,
|
||||
remoteResource,
|
||||
remoteContent: this.format(remoteExtensions),
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote,
|
||||
localExtensions,
|
||||
skippedExtensions: [],
|
||||
localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
localResource,
|
||||
localContent,
|
||||
remoteResource,
|
||||
remoteContent: null,
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
added: [], removed: [], updated: [], remote: null, localExtensions, skippedExtensions: [],
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None,
|
||||
hasConflicts: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async getPushPreview(remoteExtensions: ISyncExtension[] | null): Promise<IExtensionResourcePreview> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
|
||||
const mergeResult = merge(localExtensions, null, null, [], ignoredExtensions);
|
||||
const { added, removed, updated, remote } = mergeResult;
|
||||
return {
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localExtensions),
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote,
|
||||
localExtensions,
|
||||
skippedExtensions: [],
|
||||
localChange: added.length > 0 || removed.length > 0 || updated.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
};
|
||||
}
|
||||
|
||||
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
return [{ resource: joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }];
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import {
|
||||
IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncResourceEnablementService,
|
||||
IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, USER_DATA_SYNC_SCHEME, IRemoteUserData, ISyncData, Change
|
||||
IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue, USER_DATA_SYNC_SCHEME, IRemoteUserData, Change
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
@ -16,7 +16,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
|
|||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
@ -30,11 +30,15 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
|||
const argvStoragePrefx = 'globalState.argv.';
|
||||
const argvProperties: string[] = ['locale'];
|
||||
|
||||
export interface IGlobalStateResourcePreview extends IResourcePreview {
|
||||
interface IGlobalStateResourceMergeResult extends IAcceptResult {
|
||||
readonly local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
|
||||
readonly remote: IStringDictionary<IStorageValue> | null;
|
||||
}
|
||||
|
||||
export interface IGlobalStateResourcePreview extends IResourcePreview {
|
||||
readonly skippedStorageKeys: string[];
|
||||
readonly localUserData: IGlobalState;
|
||||
readonly previewResult: IGlobalStateResourceMergeResult;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IRemoteUserData {
|
||||
|
@ -76,43 +80,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
|||
);
|
||||
}
|
||||
|
||||
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IGlobalStateResourcePreview[]> {
|
||||
const remoteContent = remoteUserData.syncData !== null ? remoteUserData.syncData.content : null;
|
||||
const pullPreview = await this.getPullPreview(remoteContent, lastSyncUserData?.skippedStorageKeys || []);
|
||||
return [pullPreview];
|
||||
}
|
||||
|
||||
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IGlobalStateResourcePreview[]> {
|
||||
const remoteContent = remoteUserData.syncData !== null ? remoteUserData.syncData.content : null;
|
||||
const pushPreview = await this.getPushPreview(remoteContent);
|
||||
return [pushPreview];
|
||||
}
|
||||
|
||||
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IGlobalStateResourcePreview[]> {
|
||||
const localUserData = await this.getLocalGlobalState();
|
||||
const syncGlobalState: IGlobalState = JSON.parse(syncData.content);
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
const mergeResult = merge(localUserData.storage, syncGlobalState.storage, localUserData.storage, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
|
||||
const { local, skipped } = mergeResult;
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localUserData),
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
local,
|
||||
remote: syncGlobalState.storage,
|
||||
localUserData,
|
||||
skippedStorageKeys: skipped,
|
||||
localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: Change.Modified,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IGlobalStateResourcePreview[]> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
const lastSyncGlobalState: IGlobalState | null = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
|
||||
|
@ -125,30 +92,88 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
|||
this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`);
|
||||
}
|
||||
|
||||
const mergeResult = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
|
||||
const { local, remote, skipped } = mergeResult;
|
||||
const { local, remote, skipped } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
|
||||
const previewResult: IGlobalStateResourceMergeResult = {
|
||||
content: null,
|
||||
local,
|
||||
remote,
|
||||
localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
};
|
||||
|
||||
return [{
|
||||
skippedStorageKeys: skipped,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localGloablState),
|
||||
localUserData: localGloablState,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
local,
|
||||
remote,
|
||||
localUserData: localGloablState,
|
||||
skippedStorageKeys: skipped,
|
||||
localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
}];
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: IGlobalStateResourcePreview[], force: boolean): Promise<void> {
|
||||
let { local, remote, localUserData, localChange, remoteChange, skippedStorageKeys } = resourcePreviews[0];
|
||||
protected async getMergeResult(resourcePreview: IGlobalStateResourcePreview, token: CancellationToken): Promise<IMergeResult> {
|
||||
return { ...resourcePreview.previewResult, hasConflicts: false };
|
||||
}
|
||||
|
||||
protected async getAcceptResult(resourcePreview: IGlobalStateResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IGlobalStateResourceMergeResult> {
|
||||
|
||||
/* Accept local resource */
|
||||
if (isEqual(resource, this.localResource)) {
|
||||
return this.acceptLocal(resourcePreview);
|
||||
}
|
||||
|
||||
/* Accept remote resource */
|
||||
if (isEqual(resource, this.remoteResource)) {
|
||||
return this.acceptRemote(resourcePreview);
|
||||
}
|
||||
|
||||
/* Accept preview resource */
|
||||
if (isEqual(resource, this.previewResource)) {
|
||||
return resourcePreview.previewResult;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Resource: ${resource.toString()}`);
|
||||
}
|
||||
|
||||
private async acceptLocal(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
|
||||
return {
|
||||
content: resourcePreview.localContent,
|
||||
local: { added: {}, removed: [], updated: {} },
|
||||
remote: resourcePreview.localUserData.storage,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
}
|
||||
|
||||
private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
|
||||
if (resourcePreview.remoteContent !== null) {
|
||||
const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent);
|
||||
const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), resourcePreview.skippedStorageKeys, this.logService);
|
||||
return {
|
||||
content: resourcePreview.remoteContent,
|
||||
local,
|
||||
remote,
|
||||
localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: resourcePreview.remoteContent,
|
||||
local: { added: {}, removed: [], updated: {} },
|
||||
remote: null,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise<void> {
|
||||
let { localUserData, skippedStorageKeys } = resourcePreviews[0][0];
|
||||
let { local, remote, localChange, remoteChange } = resourcePreviews[0][1];
|
||||
|
||||
if (localChange === Change.None && remoteChange === Change.None) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`);
|
||||
|
@ -178,88 +203,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
|||
}
|
||||
}
|
||||
|
||||
protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string | null): Promise<IGlobalStateResourcePreview> {
|
||||
if (isEqual(this.localResource, resource)) {
|
||||
return this.getPushPreview(resourcePreview.remoteContent);
|
||||
}
|
||||
if (isEqual(this.remoteResource, resource)) {
|
||||
return this.getPullPreview(resourcePreview.remoteContent, resourcePreview.skippedStorageKeys);
|
||||
}
|
||||
return resourcePreview;
|
||||
}
|
||||
|
||||
private async getPullPreview(remoteContent: string | null, skippedStorageKeys: string[]): Promise<IGlobalStateResourcePreview> {
|
||||
const localGlobalState = await this.getLocalGlobalState();
|
||||
const localResource = this.localResource;
|
||||
const localContent = this.format(localGlobalState);
|
||||
const remoteResource = this.remoteResource;
|
||||
const previewResource = this.previewResource;
|
||||
const acceptedResource = this.acceptedResource;
|
||||
const previewContent = null;
|
||||
if (remoteContent !== null) {
|
||||
const remoteGlobalState: IGlobalState = JSON.parse(remoteContent);
|
||||
const mergeResult = merge(localGlobalState.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), skippedStorageKeys, this.logService);
|
||||
const { local, remote, skipped } = mergeResult;
|
||||
return {
|
||||
localResource,
|
||||
localContent,
|
||||
remoteResource,
|
||||
remoteContent: this.format(remoteGlobalState),
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
local,
|
||||
remote,
|
||||
localUserData: localGlobalState,
|
||||
skippedStorageKeys: skipped,
|
||||
localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None,
|
||||
remoteChange: remote !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
localResource,
|
||||
localContent,
|
||||
remoteResource,
|
||||
remoteContent: null,
|
||||
previewResource,
|
||||
previewContent,
|
||||
acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
local: { added: {}, removed: [], updated: {} },
|
||||
remote: null,
|
||||
localUserData: localGlobalState,
|
||||
skippedStorageKeys: [],
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None,
|
||||
hasConflicts: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async getPushPreview(remoteContent: string | null): Promise<IGlobalStateResourcePreview> {
|
||||
const localUserData = await this.getLocalGlobalState();
|
||||
const remoteGlobalState: IGlobalState = remoteContent ? JSON.parse(remoteContent) : null;
|
||||
return {
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localUserData),
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent: null,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: null,
|
||||
local: { added: {}, removed: [], updated: {} },
|
||||
remote: localUserData.storage,
|
||||
localUserData,
|
||||
skippedStorageKeys: [],
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Modified,
|
||||
hasConflicts: false,
|
||||
};
|
||||
}
|
||||
|
||||
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
|
||||
return [{ resource: joinPath(uri, 'globalState.json'), comparableResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI }];
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platfo
|
|||
import {
|
||||
UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource,
|
||||
IUserDataSynchroniser, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, ISyncResourceHandle,
|
||||
IRemoteUserData, ISyncData, Change
|
||||
IRemoteUserData, Change
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
@ -19,7 +18,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
|||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { AbstractJsonFileSynchroniser, IFileResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
|
||||
|
@ -32,6 +31,10 @@ interface ISyncContent {
|
|||
all?: string;
|
||||
}
|
||||
|
||||
interface IKeybindingsResourcePreview extends IFileResourcePreview {
|
||||
previewResult: IMergeResult;
|
||||
}
|
||||
|
||||
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
protected readonly version: number = 1;
|
||||
|
@ -55,67 +58,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||
super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
|
||||
}
|
||||
|
||||
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const previewContent = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: previewContent,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: Change.None,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const previewContent: string | null = fileContent ? fileContent.value.toString() : null;
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IFileResourcePreview[]> {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const previewContent = this.getKeybindingsContentFromSyncContent(syncData.content);
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IKeybindingsResourcePreview[]> {
|
||||
const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
const lastSyncContent: string | null = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null;
|
||||
|
||||
|
@ -123,7 +66,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||
const fileContent = await this.getLocalFileContent();
|
||||
const formattingOptions = await this.getFormattingOptions();
|
||||
|
||||
let previewContent: string | null = null;
|
||||
let mergedContent: string | null = null;
|
||||
let hasLocalChanged: boolean = false;
|
||||
let hasRemoteChanged: boolean = false;
|
||||
let hasConflicts: boolean = false;
|
||||
|
@ -142,7 +85,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||
const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService);
|
||||
// Sync only if there are changes
|
||||
if (result.hasChanges) {
|
||||
previewContent = result.mergeContent;
|
||||
mergedContent = result.mergeContent;
|
||||
hasConflicts = result.hasConflicts;
|
||||
hasLocalChanged = hasConflicts || result.mergeContent !== localContent;
|
||||
hasRemoteChanged = hasConflicts || result.mergeContent !== remoteContent;
|
||||
|
@ -153,41 +96,80 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||
// First time syncing to remote
|
||||
else if (fileContent) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Remote keybindings does not exist. Synchronizing keybindings for the first time.`);
|
||||
previewContent = fileContent.value.toString();
|
||||
mergedContent = fileContent.value.toString();
|
||||
hasRemoteChanged = true;
|
||||
}
|
||||
|
||||
if (previewContent && !token.isCancellationRequested) {
|
||||
await this.fileService.writeFile(this.previewResource, VSBuffer.fromString(previewContent));
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
hasConflicts,
|
||||
const previewResult: IMergeResult = {
|
||||
content: mergedContent,
|
||||
localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None,
|
||||
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
|
||||
return {
|
||||
...resourcePreview,
|
||||
acceptedContent,
|
||||
localChange: isEqual(resource, this.localResource) ? Change.None : Change.Modified,
|
||||
remoteChange: isEqual(resource, this.remoteResource) ? Change.None : Change.Modified,
|
||||
hasConflicts
|
||||
};
|
||||
|
||||
return [{
|
||||
fileContent,
|
||||
localResource: this.localResource,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
localChange: previewResult.localChange,
|
||||
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent,
|
||||
remoteChange: previewResult.remoteChange,
|
||||
|
||||
previewResource: this.previewResource,
|
||||
previewResult
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
|
||||
protected async getMergeResult(resourcePreview: IKeybindingsResourcePreview, token: CancellationToken): Promise<IMergeResult> {
|
||||
return resourcePreview.previewResult;
|
||||
}
|
||||
|
||||
protected async getAcceptResult(resourcePreview: IKeybindingsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
|
||||
|
||||
/* Accept local resource */
|
||||
if (isEqual(resource, this.localResource)) {
|
||||
return {
|
||||
content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
}
|
||||
|
||||
/* Accept remote resource */
|
||||
if (isEqual(resource, this.remoteResource)) {
|
||||
return {
|
||||
content: resourcePreview.remoteContent !== null ? this.getKeybindingsContentFromSyncContent(resourcePreview.remoteContent) : null,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
}
|
||||
|
||||
/* Accept preview resource */
|
||||
if (isEqual(resource, this.previewResource)) {
|
||||
if (content === undefined) {
|
||||
return {
|
||||
content: resourcePreview.previewResult.content,
|
||||
localChange: resourcePreview.previewResult.localChange,
|
||||
remoteChange: resourcePreview.previewResult.remoteChange,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Resource: ${resource.toString()}`);
|
||||
}
|
||||
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IKeybindingsResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
|
||||
const { fileContent } = resourcePreviews[0][0];
|
||||
let { content, localChange, remoteChange } = resourcePreviews[0][1];
|
||||
|
||||
if (localChange === Change.None && remoteChange === Change.None) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`);
|
||||
|
|
|
@ -17,7 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
|||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { AbstractJsonFileSynchroniser, IFileResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
@ -26,6 +26,10 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
|
|||
import { Edit } from 'vs/base/common/jsonFormatter';
|
||||
import { setProperty, applyEdits } from 'vs/base/common/jsonEdit';
|
||||
|
||||
interface ISettingsResourcePreview extends IFileResourcePreview {
|
||||
previewResult: IMergeResult;
|
||||
}
|
||||
|
||||
export interface ISettingsSyncContent {
|
||||
settings: string;
|
||||
}
|
||||
|
@ -60,102 +64,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
|||
super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
|
||||
}
|
||||
|
||||
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
|
||||
let previewContent: string | null = null;
|
||||
if (remoteSettingsSyncContent) {
|
||||
// Update ignored settings from local file content
|
||||
previewContent = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: Change.None,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
|
||||
let previewContent: string | null = fileContent?.value.toString() || null;
|
||||
if (previewContent) {
|
||||
// Remove ignored settings
|
||||
previewContent = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IFileResourcePreview[]> {
|
||||
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
|
||||
let previewContent: string | null = null;
|
||||
const settingsSyncContent = this.parseSettingsSyncContent(syncData.content);
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
if (settingsSyncContent) {
|
||||
previewContent = updateIgnoredSettings(settingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent: previewContent,
|
||||
localChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
remoteChange: previewContent !== null ? Change.Modified : Change.None,
|
||||
hasConflicts: false,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<ISettingsResourcePreview[]> {
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
const formattingOptions = await this.getFormattingOptions();
|
||||
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
|
||||
const lastSettingsSyncContent: ISettingsSyncContent | null = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null;
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
|
||||
let acceptedContent: string | null = null;
|
||||
let previewContent: string | null = null;
|
||||
let mergedContent: string | null = null;
|
||||
let hasLocalChanged: boolean = false;
|
||||
let hasRemoteChanged: boolean = false;
|
||||
let hasConflicts: boolean = false;
|
||||
|
@ -165,7 +81,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
|||
this.validateContent(localContent);
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`);
|
||||
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, [], formattingOptions);
|
||||
acceptedContent = result.localContent || result.remoteContent;
|
||||
mergedContent = result.localContent || result.remoteContent;
|
||||
hasLocalChanged = result.localContent !== null;
|
||||
hasRemoteChanged = result.remoteContent !== null;
|
||||
hasConflicts = result.hasConflicts;
|
||||
|
@ -174,48 +90,92 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
|||
// First time syncing to remote
|
||||
else if (fileContent) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`);
|
||||
acceptedContent = fileContent.value.toString();
|
||||
mergedContent = fileContent.value.toString();
|
||||
hasRemoteChanged = true;
|
||||
}
|
||||
|
||||
if (acceptedContent && !token.isCancellationRequested) {
|
||||
// Remove the ignored settings from the preview.
|
||||
previewContent = updateIgnoredSettings(acceptedContent, '{}', ignoredSettings, formattingOptions);
|
||||
}
|
||||
|
||||
return [{
|
||||
localResource: this.localResource,
|
||||
fileContent,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
previewResource: this.previewResource,
|
||||
previewContent,
|
||||
acceptedResource: this.acceptedResource,
|
||||
acceptedContent,
|
||||
const previewResult = {
|
||||
content: mergedContent,
|
||||
localChange: hasLocalChanged ? Change.Modified : Change.None,
|
||||
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
|
||||
hasConflicts,
|
||||
hasConflicts
|
||||
};
|
||||
|
||||
return [{
|
||||
fileContent,
|
||||
localResource: this.localResource,
|
||||
localContent: fileContent ? fileContent.value.toString() : null,
|
||||
localChange: previewResult.localChange,
|
||||
|
||||
remoteResource: this.remoteResource,
|
||||
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
|
||||
remoteChange: previewResult.remoteChange,
|
||||
|
||||
previewResource: this.previewResource,
|
||||
previewResult
|
||||
}];
|
||||
}
|
||||
|
||||
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
|
||||
if (acceptedContent && (isEqual(resource, this.previewResource) || isEqual(resource, this.remoteResource))) {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
// Add ignored settings from local file content
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
|
||||
}
|
||||
protected async getMergeResult(resourcePreview: ISettingsResourcePreview, token: CancellationToken): Promise<IMergeResult> {
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
return {
|
||||
...resourcePreview,
|
||||
acceptedContent,
|
||||
localChange: isEqual(resource, this.localResource) ? Change.None : Change.Modified,
|
||||
remoteChange: isEqual(resource, this.remoteResource) ? Change.None : Change.Modified,
|
||||
...resourcePreview.previewResult,
|
||||
|
||||
// remove ignored settings from the preview content
|
||||
content: resourcePreview.previewResult.content ? updateIgnoredSettings(resourcePreview.previewResult.content, '{}', ignoredSettings, formatUtils) : null
|
||||
};
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
|
||||
protected async getAcceptResult(resourcePreview: ISettingsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
|
||||
|
||||
const formattingOptions = await this.getFormattingOptions();
|
||||
const ignoredSettings = await this.getIgnoredSettings();
|
||||
|
||||
/* Accept local resource */
|
||||
if (isEqual(resource, this.localResource)) {
|
||||
return {
|
||||
/* Remove ignored settings */
|
||||
content: resourcePreview.fileContent ? updateIgnoredSettings(resourcePreview.fileContent.value.toString(), '{}', ignoredSettings, formattingOptions) : null,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
}
|
||||
|
||||
/* Accept remote resource */
|
||||
if (isEqual(resource, this.remoteResource)) {
|
||||
return {
|
||||
/* Update ignored settings from local file content */
|
||||
content: resourcePreview.remoteContent !== null ? updateIgnoredSettings(resourcePreview.remoteContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formattingOptions) : null,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
}
|
||||
|
||||
/* Accept preview resource */
|
||||
if (isEqual(resource, this.previewResource)) {
|
||||
if (content === undefined) {
|
||||
return {
|
||||
content: resourcePreview.previewResult.content,
|
||||
localChange: resourcePreview.previewResult.localChange,
|
||||
remoteChange: resourcePreview.previewResult.remoteChange,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
/* Add ignored settings from local file content */
|
||||
content: content !== null ? updateIgnoredSettings(content, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formattingOptions) : null,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Resource: ${resource.toString()}`);
|
||||
}
|
||||
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [ISettingsResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
|
||||
const { fileContent } = resourcePreviews[0][0];
|
||||
let { content, localChange, remoteChange } = resourcePreviews[0][1];
|
||||
|
||||
if (localChange === Change.None && remoteChange === Change.None) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`);
|
||||
|
|
|
@ -10,17 +10,25 @@ import {
|
|||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { AbstractSynchroniser, IFileResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, extname, relativePath, isEqualOrParent, basename, dirname } from 'vs/base/common/resources';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { merge, IMergeResult, areSame } from 'vs/platform/userDataSync/common/snippetsMerge';
|
||||
import { merge, IMergeResult as ISnippetsMergeResult, areSame } from 'vs/platform/userDataSync/common/snippetsMerge';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
interface ISnippetsResourcePreview extends IFileResourcePreview {
|
||||
previewResult: IMergeResult;
|
||||
}
|
||||
|
||||
interface ISnippetsAcceptedResourcePreview extends IFileResourcePreview {
|
||||
acceptResult: IAcceptResult;
|
||||
}
|
||||
|
||||
export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
protected readonly version: number = 1;
|
||||
|
@ -51,36 +59,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
this.triggerLocalChange();
|
||||
}
|
||||
|
||||
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
const resourcePreviews: IFileResourcePreview[] = [];
|
||||
if (remoteUserData.syncData !== null) {
|
||||
const local = await this.getSnippetsFileContents();
|
||||
const localSnippets = this.toSnippetsContents(local);
|
||||
const remoteSnippets = this.parseSnippets(remoteUserData.syncData);
|
||||
const mergeResult = merge(localSnippets, remoteSnippets, localSnippets);
|
||||
resourcePreviews.push(...this.getResourcePreviews(mergeResult, local, remoteSnippets));
|
||||
}
|
||||
return resourcePreviews;
|
||||
}
|
||||
|
||||
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
const local = await this.getSnippetsFileContents();
|
||||
const localSnippets = this.toSnippetsContents(local);
|
||||
const mergeResult = merge(localSnippets, null, null);
|
||||
const resourcePreviews = this.getResourcePreviews(mergeResult, local, {});
|
||||
return resourcePreviews;
|
||||
}
|
||||
|
||||
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IFileResourcePreview[]> {
|
||||
const local = await this.getSnippetsFileContents();
|
||||
const localSnippets = this.toSnippetsContents(local);
|
||||
const snippets = this.parseSnippets(syncData);
|
||||
const mergeResult = merge(localSnippets, snippets, localSnippets);
|
||||
const resourcePreviews = this.getResourcePreviews(mergeResult, local, snippets);
|
||||
return resourcePreviews;
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<ISnippetsResourcePreview[]> {
|
||||
const local = await this.getSnippetsFileContents();
|
||||
const localSnippets = this.toSnippetsContents(local);
|
||||
const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : null;
|
||||
|
@ -96,102 +75,72 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
return this.getResourcePreviews(mergeResult, local, remoteSnippets || {});
|
||||
}
|
||||
|
||||
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
|
||||
return {
|
||||
...resourcePreview,
|
||||
acceptedContent,
|
||||
localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent),
|
||||
remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent),
|
||||
};
|
||||
protected async getMergeResult(resourcePreview: ISnippetsResourcePreview, token: CancellationToken): Promise<IMergeResult> {
|
||||
return resourcePreview.previewResult;
|
||||
}
|
||||
|
||||
private computeLocalChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
|
||||
const isRemoteResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }));
|
||||
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
|
||||
protected async getAcceptResult(resourcePreview: ISnippetsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
|
||||
|
||||
const previewExists = acceptedContent !== null;
|
||||
const remoteExists = resourcePreview.remoteContent !== null;
|
||||
const localExists = resourcePreview.fileContent !== null;
|
||||
|
||||
if (isRemoteResourceAccepted) {
|
||||
if (remoteExists && localExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (remoteExists && !localExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
if (!remoteExists && localExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
return Change.None;
|
||||
/* Accept local resource */
|
||||
if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))) {
|
||||
return {
|
||||
content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null,
|
||||
localChange: Change.None,
|
||||
remoteChange: resourcePreview.fileContent
|
||||
? resourcePreview.remoteContent !== null ? Change.Modified : Change.Added
|
||||
: Change.Deleted
|
||||
};
|
||||
}
|
||||
|
||||
if (isPreviewResourceAccepted) {
|
||||
if (previewExists && localExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (previewExists && !localExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
if (!previewExists && localExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
return Change.None;
|
||||
/* Accept remote resource */
|
||||
if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))) {
|
||||
return {
|
||||
content: resourcePreview.remoteContent,
|
||||
localChange: resourcePreview.remoteContent !== null
|
||||
? resourcePreview.fileContent ? Change.Modified : Change.Added
|
||||
: Change.Deleted,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
}
|
||||
|
||||
return Change.None;
|
||||
/* Accept preview resource */
|
||||
if (isEqualOrParent(resource, this.syncPreviewFolder)) {
|
||||
if (content === undefined) {
|
||||
return {
|
||||
content: resourcePreview.previewResult.content,
|
||||
localChange: resourcePreview.previewResult.localChange,
|
||||
remoteChange: resourcePreview.previewResult.remoteChange,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content,
|
||||
localChange: content === null
|
||||
? resourcePreview.fileContent !== null ? Change.Deleted : Change.None
|
||||
: Change.Modified,
|
||||
remoteChange: content === null
|
||||
? resourcePreview.remoteContent !== null ? Change.Deleted : Change.None
|
||||
: Change.Modified
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Invalid Resource: ${resource.toString()}`);
|
||||
}
|
||||
|
||||
private computeRemoteChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
|
||||
const isLocalResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }));
|
||||
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
|
||||
|
||||
const previewExists = acceptedContent !== null;
|
||||
const remoteExists = resourcePreview.remoteContent !== null;
|
||||
const localExists = resourcePreview.fileContent !== null;
|
||||
|
||||
if (isLocalResourceAccepted) {
|
||||
if (remoteExists && localExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (remoteExists && !localExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
if (!remoteExists && localExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
if (isPreviewResourceAccepted) {
|
||||
if (previewExists && remoteExists) {
|
||||
return Change.Modified;
|
||||
}
|
||||
if (previewExists && !remoteExists) {
|
||||
return Change.Added;
|
||||
}
|
||||
if (!previewExists && remoteExists) {
|
||||
return Change.Deleted;
|
||||
}
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
return Change.None;
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
if (resourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [ISnippetsResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
|
||||
const accptedResourcePreviews: ISnippetsAcceptedResourcePreview[] = resourcePreviews.map(([resourcePreview, acceptResult]) => ({ ...resourcePreview, acceptResult }));
|
||||
if (accptedResourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`);
|
||||
}
|
||||
|
||||
if (resourcePreviews.some(({ localChange }) => localChange !== Change.None)) {
|
||||
if (accptedResourcePreviews.some(({ localChange }) => localChange !== Change.None)) {
|
||||
// back up all snippets
|
||||
await this.updateLocalBackup(resourcePreviews);
|
||||
await this.updateLocalSnippets(resourcePreviews, force);
|
||||
await this.updateLocalBackup(accptedResourcePreviews);
|
||||
await this.updateLocalSnippets(accptedResourcePreviews, force);
|
||||
}
|
||||
|
||||
if (resourcePreviews.some(({ remoteChange }) => remoteChange !== Change.None)) {
|
||||
remoteUserData = await this.updateRemoteSnippets(resourcePreviews, remoteUserData, force);
|
||||
if (accptedResourcePreviews.some(({ remoteChange }) => remoteChange !== Change.None)) {
|
||||
remoteUserData = await this.updateRemoteSnippets(accptedResourcePreviews, remoteUserData, force);
|
||||
}
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
|
@ -201,7 +150,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized snippets`);
|
||||
}
|
||||
|
||||
for (const { previewResource } of resourcePreviews) {
|
||||
for (const { previewResource } of accptedResourcePreviews) {
|
||||
// Delete the preview
|
||||
try {
|
||||
await this.fileService.del(previewResource);
|
||||
|
@ -210,29 +159,38 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
|
||||
}
|
||||
|
||||
private getResourcePreviews(mergeResult: IMergeResult, localFileContent: IStringDictionary<IFileContent>, remoteSnippets: IStringDictionary<string>): IFileResourcePreview[] {
|
||||
const resourcePreviews: Map<string, IFileResourcePreview> = new Map<string, IFileResourcePreview>();
|
||||
private getResourcePreviews(snippetsMergeResult: ISnippetsMergeResult, localFileContent: IStringDictionary<IFileContent>, remoteSnippets: IStringDictionary<string>): ISnippetsResourcePreview[] {
|
||||
const resourcePreviews: Map<string, ISnippetsResourcePreview> = new Map<string, ISnippetsResourcePreview>();
|
||||
|
||||
/* Snippets added remotely -> add locally */
|
||||
for (const key of Object.keys(mergeResult.local.added)) {
|
||||
for (const key of Object.keys(snippetsMergeResult.local.added)) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: snippetsMergeResult.local.added[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.Added,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: null,
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
localContent: null,
|
||||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.local.added[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.local.added[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.Added,
|
||||
remoteChange: Change.None
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
|
||||
/* Snippets updated remotely -> update locally */
|
||||
for (const key of Object.keys(mergeResult.local.updated)) {
|
||||
for (const key of Object.keys(snippetsMergeResult.local.updated)) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: snippetsMergeResult.local.updated[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
|
@ -240,17 +198,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.local.updated[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.local.updated[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
|
||||
/* Snippets removed remotely -> remove locally */
|
||||
for (const key of mergeResult.local.removed) {
|
||||
for (const key of snippetsMergeResult.local.removed) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.Deleted,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
|
@ -258,17 +219,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.Deleted,
|
||||
remoteChange: Change.None
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
|
||||
/* Snippets added locally -> add remotely */
|
||||
for (const key of Object.keys(mergeResult.remote.added)) {
|
||||
for (const key of Object.keys(snippetsMergeResult.remote.added)) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: snippetsMergeResult.remote.added[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Added,
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
|
@ -276,17 +240,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.remote.added[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.remote.added[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Added
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
|
||||
/* Snippets updated locally -> update remotely */
|
||||
for (const key of Object.keys(mergeResult.remote.updated)) {
|
||||
for (const key of Object.keys(snippetsMergeResult.remote.updated)) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: snippetsMergeResult.remote.updated[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Modified,
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key],
|
||||
|
@ -294,17 +261,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: mergeResult.remote.updated[key],
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: mergeResult.remote.updated[key],
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Modified
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
|
||||
/* Snippets removed locally -> remove remotely */
|
||||
for (const key of mergeResult.remote.removed) {
|
||||
for (const key of snippetsMergeResult.remote.removed) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Deleted,
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: null,
|
||||
|
@ -312,17 +282,20 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key],
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.Deleted
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
|
||||
/* Snippets with conflicts */
|
||||
for (const key of mergeResult.conflicts) {
|
||||
for (const key of snippetsMergeResult.conflicts) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
hasConflicts: true,
|
||||
localChange: localFileContent[key] ? Change.Modified : Change.Added,
|
||||
remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key] || null,
|
||||
|
@ -330,18 +303,21 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key] || null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
hasConflicts: true,
|
||||
localChange: localFileContent[key] ? Change.Modified : Change.Added,
|
||||
remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
|
||||
/* Unmodified Snippets */
|
||||
for (const key of Object.keys(localFileContent)) {
|
||||
if (!resourcePreviews.has(key)) {
|
||||
const previewResult: IMergeResult = {
|
||||
content: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None
|
||||
};
|
||||
resourcePreviews.set(key, {
|
||||
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
|
||||
fileContent: localFileContent[key] || null,
|
||||
|
@ -349,12 +325,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
|
||||
remoteContent: remoteSnippets[key] || null,
|
||||
previewResource: joinPath(this.syncPreviewFolder, key),
|
||||
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
|
||||
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
|
||||
hasConflicts: false,
|
||||
localChange: Change.None,
|
||||
remoteChange: Change.None
|
||||
previewResult,
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -427,8 +400,8 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
await this.backupLocal(JSON.stringify(this.toSnippetsContents(local)));
|
||||
}
|
||||
|
||||
private async updateLocalSnippets(resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
|
||||
for (const { fileContent, acceptedContent: content, localResource, remoteResource, localChange } of resourcePreviews) {
|
||||
private async updateLocalSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], force: boolean): Promise<void> {
|
||||
for (const { fileContent, acceptResult, localResource, remoteResource, localChange } of resourcePreviews) {
|
||||
if (localChange !== Change.None) {
|
||||
const key = remoteResource ? basename(remoteResource) : basename(localResource!);
|
||||
const resource = joinPath(this.snippetsFolder, key);
|
||||
|
@ -443,31 +416,31 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
// Added
|
||||
else if (localChange === Change.Added) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, basename(resource));
|
||||
await this.fileService.createFile(resource, VSBuffer.fromString(content!), { overwrite: force });
|
||||
await this.fileService.createFile(resource, VSBuffer.fromString(acceptResult.content!), { overwrite: force });
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, basename(resource));
|
||||
}
|
||||
|
||||
// Updated
|
||||
else {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, basename(resource));
|
||||
await this.fileService.writeFile(resource, VSBuffer.fromString(content!), force ? undefined : fileContent!);
|
||||
await this.fileService.writeFile(resource, VSBuffer.fromString(acceptResult.content!), force ? undefined : fileContent!);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, basename(resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async updateRemoteSnippets(resourcePreviews: IFileResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise<IRemoteUserData> {
|
||||
private async updateRemoteSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise<IRemoteUserData> {
|
||||
const currentSnippets: IStringDictionary<string> = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {};
|
||||
const newSnippets: IStringDictionary<string> = deepClone(currentSnippets);
|
||||
|
||||
for (const { acceptedContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) {
|
||||
for (const { acceptResult, localResource, remoteResource, remoteChange } of resourcePreviews) {
|
||||
if (remoteChange !== Change.None) {
|
||||
const key = localResource ? basename(localResource) : basename(remoteResource!);
|
||||
if (remoteChange === Change.Deleted) {
|
||||
delete newSnippets[key];
|
||||
} else {
|
||||
newSnippets[key] = content!;
|
||||
newSnippets[key] = acceptResult.content!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -356,14 +356,12 @@ export interface IUserDataSynchroniser {
|
|||
|
||||
readonly onDidChangeLocal: Event<void>;
|
||||
|
||||
pull(): Promise<void>;
|
||||
push(): Promise<void>;
|
||||
sync(manifest: IUserDataManifest | null, headers: IHeaders): Promise<void>;
|
||||
replace(uri: URI): Promise<boolean>;
|
||||
stop(): Promise<void>;
|
||||
|
||||
preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise<ISyncResourcePreview | null>;
|
||||
accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null>;
|
||||
accept(resource: URI, content?: string | null): Promise<ISyncResourcePreview | null>;
|
||||
merge(resource: URI): Promise<ISyncResourcePreview | null>;
|
||||
discard(resource: URI): Promise<ISyncResourcePreview | null>;
|
||||
apply(force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
|
||||
|
@ -403,7 +401,7 @@ export interface IManualSyncTask extends IDisposable {
|
|||
readonly manifest: IUserDataManifest | null;
|
||||
readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>;
|
||||
preview(): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
apply(): Promise<[SyncResource, ISyncResourcePreview][]>;
|
||||
|
@ -431,7 +429,6 @@ export interface IUserDataSyncService {
|
|||
createSyncTask(): Promise<ISyncTask>;
|
||||
createManualSyncTask(): Promise<IManualSyncTask>;
|
||||
|
||||
pull(): Promise<void>;
|
||||
replace(uri: URI): Promise<void>;
|
||||
reset(): Promise<void>;
|
||||
resetRemote(): Promise<void>;
|
||||
|
@ -440,7 +437,7 @@ export interface IUserDataSyncService {
|
|||
hasLocalData(): Promise<boolean>;
|
||||
hasPreviouslySynced(): Promise<boolean>;
|
||||
resolveContent(resource: URI): Promise<string | null>;
|
||||
accept(resource: SyncResource, conflictResource: URI, content: string | null, apply: boolean): Promise<void>;
|
||||
accept(resource: SyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean): Promise<void>;
|
||||
|
||||
getLocalSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
|
||||
getRemoteSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
|
||||
|
|
|
@ -46,7 +46,6 @@ export class UserDataSyncChannel implements IServerChannel {
|
|||
|
||||
case 'createManualSyncTask': return this.createManualSyncTask();
|
||||
|
||||
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();
|
||||
|
|
|
@ -98,36 +98,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||
this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeLocal, () => s.resource)));
|
||||
}
|
||||
|
||||
async pull(): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
try {
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
await synchroniser.pull();
|
||||
}
|
||||
this.updateLastSyncTime();
|
||||
} catch (error) {
|
||||
if (error instanceof UserDataSyncError) {
|
||||
this.telemetryService.publicLog2<{ resource?: string, executionId?: string }, SyncErrorClassification>(`sync/error/${error.code}`, { resource: error.resource });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async push(): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
try {
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
await synchroniser.push();
|
||||
}
|
||||
this.updateLastSyncTime();
|
||||
} catch (error) {
|
||||
if (error instanceof UserDataSyncError) {
|
||||
this.telemetryService.publicLog2<{ resource?: string, executionId?: string }, SyncErrorClassification>(`sync/error/${error.code}`, { resource: error.resource });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createSyncTask(): Promise<ISyncTask> {
|
||||
await this.checkEnablement();
|
||||
|
||||
|
@ -256,7 +226,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||
}
|
||||
}
|
||||
|
||||
async accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise<void> {
|
||||
async accept(syncResource: SyncResource, resource: URI, content: string | null | undefined, apply: boolean): Promise<void> {
|
||||
await this.checkEnablement();
|
||||
const synchroniser = this.getSynchroniser(syncResource);
|
||||
await synchroniser.accept(resource, content);
|
||||
|
@ -456,7 +426,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
|||
return this.previews;
|
||||
}
|
||||
|
||||
async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
async accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
return this.performAction(resource, sychronizer => sychronizer.accept(resource, content));
|
||||
}
|
||||
|
||||
|
@ -555,8 +525,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
|||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
|
||||
for (const resourcePreview of preview.resourcePreviews) {
|
||||
const content = await synchroniser.resolveContent(resourcePreview.remoteResource);
|
||||
await synchroniser.accept(resourcePreview.remoteResource, content);
|
||||
await synchroniser.accept(resourcePreview.remoteResource);
|
||||
}
|
||||
await synchroniser.apply(true, this.syncHeaders);
|
||||
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
|
||||
|
@ -577,8 +546,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
|
|||
this._onSynchronizeResources.fire(this.synchronizingResources);
|
||||
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
|
||||
for (const resourcePreview of preview.resourcePreviews) {
|
||||
const content = await synchroniser.resolveContent(resourcePreview.localResource);
|
||||
await synchroniser.accept(resourcePreview.localResource, content);
|
||||
await synchroniser.accept(resourcePreview.localResource);
|
||||
}
|
||||
await synchroniser.apply(true, this.syncHeaders);
|
||||
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
|
||||
|
|
|
@ -207,33 +207,6 @@ suite('GlobalStateSync', () => {
|
|||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } });
|
||||
});
|
||||
|
||||
test('first time sync - push', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateStorage('b', 'value2', testClient);
|
||||
|
||||
await testObject.push();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } });
|
||||
});
|
||||
|
||||
test('first time sync - pull', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateStorage('b', 'value2', client2);
|
||||
await client2.sync();
|
||||
|
||||
await testObject.pull();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value1');
|
||||
assert.equal(readStorage('b', testClient), 'value2');
|
||||
});
|
||||
|
||||
function parseGlobalState(content: string): IGlobalState {
|
||||
const syncData: ISyncData = JSON.parse(content);
|
||||
return JSON.parse(syncData.content);
|
||||
|
|
|
@ -613,35 +613,6 @@ suite('SnippetsSync', () => {
|
|||
assert.deepEqual(actual, { 'typescript.json': tsSnippet1 });
|
||||
});
|
||||
|
||||
test('first time sync - push', async () => {
|
||||
await updateSnippet('html.json', htmlSnippet1, testClient);
|
||||
await updateSnippet('typescript.json', tsSnippet1, testClient);
|
||||
|
||||
await testObject.push();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseSnippets(content!);
|
||||
assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'typescript.json': tsSnippet1 });
|
||||
});
|
||||
|
||||
test('first time sync - pull', async () => {
|
||||
await updateSnippet('html.json', htmlSnippet1, client2);
|
||||
await updateSnippet('typescript.json', tsSnippet1, client2);
|
||||
await client2.sync();
|
||||
|
||||
await testObject.pull();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
const actual1 = await readSnippet('html.json', testClient);
|
||||
assert.equal(actual1, htmlSnippet1);
|
||||
const actual2 = await readSnippet('typescript.json', testClient);
|
||||
assert.equal(actual2, tsSnippet1);
|
||||
});
|
||||
|
||||
test('sync global and language snippet', async () => {
|
||||
await updateSnippet('global.code-snippets', globalSnippet, client2);
|
||||
await updateSnippet('html.json', htmlSnippet1, client2);
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest, MergeState } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest, MergeState } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
@ -17,6 +17,10 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil
|
|||
|
||||
const resource = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'testResource', path: `/current.json` });
|
||||
|
||||
interface ITestResourcePreview extends IResourcePreview {
|
||||
ref: string;
|
||||
}
|
||||
|
||||
class TestSynchroniser extends AbstractSynchroniser {
|
||||
|
||||
syncBarrier: Barrier = new Barrier();
|
||||
|
@ -48,28 +52,43 @@ class TestSynchroniser extends AbstractSynchroniser {
|
|||
return super.doSync(remoteUserData, lastSyncUserData, apply);
|
||||
}
|
||||
|
||||
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
|
||||
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
|
||||
}
|
||||
|
||||
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
|
||||
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
|
||||
}
|
||||
|
||||
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IResourcePreview[]> {
|
||||
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<ITestResourcePreview[]> {
|
||||
if (this.syncResult.hasError) {
|
||||
throw new Error('failed');
|
||||
}
|
||||
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
|
||||
|
||||
return [{
|
||||
localResource: resource,
|
||||
localContent: null,
|
||||
remoteResource: resource,
|
||||
remoteContent: null,
|
||||
previewResource: resource,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None,
|
||||
ref: remoteUserData.ref,
|
||||
}];
|
||||
}
|
||||
|
||||
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, preview: IResourcePreview[], forcePush: boolean): Promise<void> {
|
||||
if (preview[0]?.acceptedContent) {
|
||||
await this.applyRef(preview[0].acceptedContent);
|
||||
protected async getMergeResult(resourcePreview: ITestResourcePreview, token: CancellationToken): Promise<IMergeResult> {
|
||||
return {
|
||||
content: resourcePreview.ref,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None,
|
||||
hasConflicts: this.syncResult.hasConflicts,
|
||||
};
|
||||
}
|
||||
|
||||
protected async getAcceptResult(resourcePreview: ITestResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
|
||||
return {
|
||||
content: content === undefined ? resourcePreview.ref : null,
|
||||
localChange: Change.Modified,
|
||||
remoteChange: Change.None,
|
||||
};
|
||||
}
|
||||
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IResourcePreview, IAcceptResult][], force: boolean): Promise<void> {
|
||||
if (resourcePreviews[0][1].content) {
|
||||
await this.applyRef(resourcePreviews[0][1].content);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,7 +347,7 @@ suite('TestSynchronizer', () => {
|
|||
testObject.syncBarrier.open();
|
||||
|
||||
let preview = await testObject.preview(await client.manifest());
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource);
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.Syncing);
|
||||
assertPreviews(preview!.resourcePreviews, [resource]);
|
||||
|
@ -341,7 +360,7 @@ suite('TestSynchronizer', () => {
|
|||
testObject.syncBarrier.open();
|
||||
|
||||
let preview = await testObject.preview(await client.manifest());
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource);
|
||||
preview = await testObject.apply(false);
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.Idle);
|
||||
|
@ -397,7 +416,8 @@ suite('TestSynchronizer', () => {
|
|||
|
||||
let preview = await testObject.preview(await client.manifest());
|
||||
await testObject.merge(preview!.resourcePreviews[0].previewResource);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
|
||||
const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content);
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.Syncing);
|
||||
assertPreviews(preview!.resourcePreviews, [resource]);
|
||||
|
@ -411,7 +431,8 @@ suite('TestSynchronizer', () => {
|
|||
|
||||
let preview = await testObject.preview(await client.manifest());
|
||||
await testObject.merge(preview!.resourcePreviews[0].previewResource);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
|
||||
const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content);
|
||||
preview = await testObject.apply(false);
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.Idle);
|
||||
|
@ -425,7 +446,8 @@ suite('TestSynchronizer', () => {
|
|||
testObject.syncBarrier.open();
|
||||
|
||||
let preview = await testObject.preview(await client.manifest());
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
|
||||
const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content);
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.Syncing);
|
||||
assertPreviews(preview!.resourcePreviews, [resource]);
|
||||
|
@ -438,7 +460,8 @@ suite('TestSynchronizer', () => {
|
|||
testObject.syncBarrier.open();
|
||||
|
||||
let preview = await testObject.preview(await client.manifest());
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
|
||||
const content = await testObject.resolveContent(preview!.resourcePreviews[0].previewResource);
|
||||
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, content);
|
||||
preview = await testObject.apply(false);
|
||||
|
||||
assert.deepEqual(testObject.status, SyncStatus.Idle);
|
||||
|
|
|
@ -76,65 +76,6 @@ suite('UserDataSyncService', () => {
|
|||
|
||||
});
|
||||
|
||||
test('test first time sync from the client with no changes - pull', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run();
|
||||
|
||||
// Setup the test client
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
|
||||
// Sync (pull) from the test client
|
||||
target.reset();
|
||||
await testObject.pull();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test first time sync from the client with changes - pull', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
// Setup and sync from the first client
|
||||
const client = disposableStore.add(new UserDataSyncClient(target));
|
||||
await client.setUp();
|
||||
await (await client.instantiationService.get(IUserDataSyncService).createSyncTask()).run();
|
||||
|
||||
// Setup the test client with changes
|
||||
const testClient = disposableStore.add(new UserDataSyncClient(target));
|
||||
await testClient.setUp();
|
||||
const testObject = testClient.instantiationService.get(IUserDataSyncService);
|
||||
const fileService = testClient.instantiationService.get(IFileService);
|
||||
const environmentService = testClient.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
|
||||
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
|
||||
|
||||
// Sync (pull) from the test client
|
||||
target.reset();
|
||||
await testObject.pull();
|
||||
|
||||
assert.deepEqual(target.requests, [
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
test('test first time sync from the client with no changes - merge', async () => {
|
||||
const target = new UserDataSyncTestServer();
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
|||
@IOutputService private readonly outputService: IOutputService,
|
||||
@IUserDataSyncAccountService readonly authTokenService: IUserDataSyncAccountService,
|
||||
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
|
@ -253,12 +253,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
|||
private async acceptRemote(syncResource: SyncResource, conflicts: IResourcePreview[]) {
|
||||
try {
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.remoteResource);
|
||||
try {
|
||||
await this.userDataSyncService.accept(syncResource, conflict.remoteResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
await this.userDataSyncService.accept(syncResource, conflict.remoteResource, undefined, this.userDataAutoSyncService.isEnabled());
|
||||
}
|
||||
} catch (e) {
|
||||
this.notificationService.error(e);
|
||||
|
@ -268,12 +263,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
|||
private async acceptLocal(syncResource: SyncResource, conflicts: IResourcePreview[]): Promise<void> {
|
||||
try {
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.previewResource);
|
||||
try {
|
||||
await this.userDataSyncService.accept(syncResource, conflict.previewResource, modelRef.object.textEditorModel.getValue(), this.userDataAutoSyncService.isEnabled());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
await this.userDataSyncService.accept(syncResource, conflict.previewResource, undefined, this.userDataAutoSyncService.isEnabled());
|
||||
}
|
||||
} catch (e) {
|
||||
this.notificationService.error(e);
|
||||
|
|
|
@ -54,7 +54,6 @@ export class UserDataSyncMergesViewPane extends TreeViewPane {
|
|||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
@IDecorationsService decorationsService: IDecorationsService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
|
@ -251,16 +250,14 @@ export class UserDataSyncMergesViewPane extends TreeViewPane {
|
|||
|
||||
private async acceptLocal(userDataSyncResource: IUserDataSyncResource): Promise<void> {
|
||||
await this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(userDataSyncResource.local);
|
||||
await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local, content);
|
||||
await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.local);
|
||||
});
|
||||
await this.reopen(userDataSyncResource);
|
||||
}
|
||||
|
||||
private async acceptRemote(userDataSyncResource: IUserDataSyncResource): Promise<void> {
|
||||
await this.withProgress(async () => {
|
||||
const content = await this.userDataSyncService.resolveContent(userDataSyncResource.remote);
|
||||
await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote, content);
|
||||
await this.userDataSyncPreview.accept(userDataSyncResource.syncResource, userDataSyncResource.remote);
|
||||
});
|
||||
await this.reopen(userDataSyncResource);
|
||||
}
|
||||
|
|
|
@ -594,7 +594,7 @@ class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
|
|||
this.updateResources();
|
||||
}
|
||||
|
||||
async accept(syncResource: SyncResource, resource: URI, content: string | null): Promise<void> {
|
||||
async accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise<void> {
|
||||
if (this.manualSync) {
|
||||
const syncPreview = await this.manualSync.task.accept(resource, content);
|
||||
this.updatePreview(syncPreview);
|
||||
|
|
|
@ -20,7 +20,7 @@ export interface IUserDataSyncPreview {
|
|||
readonly onDidChangeResources: Event<ReadonlyArray<IUserDataSyncResource>>;
|
||||
readonly resources: ReadonlyArray<IUserDataSyncResource>;
|
||||
|
||||
accept(syncResource: SyncResource, resource: URI, content: string | null): Promise<void>;
|
||||
accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise<void>;
|
||||
merge(resource?: URI): Promise<void>;
|
||||
discard(resource?: URI): Promise<void>;
|
||||
pull(): Promise<void>;
|
||||
|
|
|
@ -65,10 +65,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||
this._register(this.channel.listen<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)])))));
|
||||
}
|
||||
|
||||
pull(): Promise<void> {
|
||||
return this.channel.call('pull');
|
||||
}
|
||||
|
||||
createSyncTask(): Promise<ISyncTask> {
|
||||
throw new Error('not supported');
|
||||
}
|
||||
|
@ -186,7 +182,7 @@ class ManualSyncTask implements IManualSyncTask {
|
|||
return this.deserializePreviews(previews);
|
||||
}
|
||||
|
||||
async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
async accept(resource: URI, content?: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
|
||||
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('accept', [resource, content]);
|
||||
return this.deserializePreviews(previews);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue