Merge from vscode 1b314ab317fbff7d799b21754326b7d849889ceb

This commit is contained in:
ADS Merger
2020-07-15 23:51:18 +00:00
parent aae013d498
commit 9d3f12d0b7
554 changed files with 15159 additions and 8223 deletions

View File

@@ -9,7 +9,8 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import {
SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService,
IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, Conflict, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncResourcePreview as IBaseSyncResourcePreview, IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME
IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncResourcePreview as IBaseSyncResourcePreview,
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
@@ -52,9 +53,20 @@ function isSyncData(thing: any): thing is ISyncData {
return false;
}
export interface IMergableResourcePreview extends IBaseResourcePreview {
readonly remoteContent: string | null;
readonly localContent: string | null;
readonly previewContent: string | null;
readonly hasConflicts: boolean;
merged: boolean;
}
export type IResourcePreview = Omit<IMergableResourcePreview, 'merged'>;
export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IRemoteUserData | null;
readonly resourcePreviews: IMergableResourcePreview[];
}
export abstract class AbstractSynchroniser extends Disposable {
@@ -70,10 +82,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: Conflict[] = [];
get conflicts(): Conflict[] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<Conflict[]> = this._register(new Emitter<Conflict[]>());
readonly onDidChangeConflicts: Event<Conflict[]> = this._onDidChangeConflicts.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 readonly localChangeTriggerScheduler = new RunOnceScheduler(() => this.doTriggerLocalChange(), 50);
private readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
@@ -119,7 +131,7 @@ export abstract class AbstractSynchroniser extends Disposable {
this.logService.info(`${this.syncResourceLogLabel}: In conflicts state and local change detected. Syncing again...`);
const preview = await this.syncPreviewPromise!;
this.syncPreviewPromise = null;
const status = await this.performSync(preview.remoteUserData, preview.lastSyncUserData);
const status = await this.performSync(preview.remoteUserData, preview.lastSyncUserData, true);
this.setStatus(status);
}
@@ -127,7 +139,7 @@ export abstract class AbstractSynchroniser extends Disposable {
else {
this.logService.trace(`${this.syncResourceLogLabel}: Checking for local changes...`);
const lastSyncUserData = await this.getLastSyncUserData();
const hasRemoteChanged = lastSyncUserData ? (await this.generatePreview(lastSyncUserData, lastSyncUserData, CancellationToken.None)).hasRemoteChanged : true;
const hasRemoteChanged = lastSyncUserData ? (await this.doGenerateSyncResourcePreview(lastSyncUserData, lastSyncUserData, true, CancellationToken.None)).resourcePreviews.some(({ remoteChange }) => remoteChange !== Change.None) : true;
if (hasRemoteChanged) {
this._onDidChangeLocal.fire();
}
@@ -137,8 +149,6 @@ export abstract class AbstractSynchroniser extends Disposable {
protected setStatus(status: SyncStatus): void {
if (this._status !== status) {
const oldStatus = this._status;
this._status = status;
this._onDidChangStatus.fire(status);
if (status === SyncStatus.HasConflicts) {
// Log to telemetry when there is a sync conflict
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.resource });
@@ -147,16 +157,8 @@ export abstract class AbstractSynchroniser extends Disposable {
// Log to telemetry when conflicts are resolved
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource });
}
if (this.status !== SyncStatus.HasConflicts) {
this.setConflicts([]);
}
}
}
protected setConflicts(conflicts: Conflict[]) {
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(this._conflicts);
this._status = status;
this._onDidChangStatus.fire(status);
}
}
@@ -176,7 +178,7 @@ export abstract class AbstractSynchroniser extends Disposable {
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
const preview = await this.generatePullPreview(remoteUserData, lastSyncUserData, CancellationToken.None);
await this.applyPreview(preview, false);
await this.applyPreview(remoteUserData, lastSyncUserData, preview, false);
this.logService.info(`${this.syncResourceLogLabel}: Finished pulling ${this.syncResourceLogLabel.toLowerCase()}.`);
} finally {
this.setStatus(SyncStatus.Idle);
@@ -199,7 +201,7 @@ export abstract class AbstractSynchroniser extends Disposable {
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
const preview = await this.generatePushPreview(remoteUserData, lastSyncUserData, CancellationToken.None);
await this.applyPreview(preview, true);
await this.applyPreview(remoteUserData, lastSyncUserData, preview, true);
this.logService.info(`${this.syncResourceLogLabel}: Finished pushing ${this.syncResourceLogLabel.toLowerCase()}.`);
} finally {
@@ -208,38 +210,49 @@ export abstract class AbstractSynchroniser extends Disposable {
}
async sync(manifest: IUserDataManifest | null, headers: IHeaders = {}): Promise<void> {
await this._sync(manifest, true, headers);
}
async preview(manifest: IUserDataManifest | null, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
return this._sync(manifest, false, headers);
}
private async _sync(manifest: IUserDataManifest | null, apply: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null> {
try {
this.syncHeaders = { ...headers };
if (!this.isEnabled()) {
if (this.status !== SyncStatus.Idle) {
await this.stop();
}
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`);
return;
return null;
}
if (this.status === SyncStatus.HasConflicts) {
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`);
return;
return this.syncPreviewPromise;
}
if (this.status === SyncStatus.Syncing) {
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`);
return;
return this.syncPreviewPromise;
}
this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`);
this.setStatus(SyncStatus.Syncing);
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData);
let status: SyncStatus = SyncStatus.Idle;
try {
status = await this.performSync(remoteUserData, lastSyncUserData);
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData);
status = await this.performSync(remoteUserData, lastSyncUserData, apply);
if (status === SyncStatus.HasConflicts) {
this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`);
} else if (status === SyncStatus.Idle) {
this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`);
}
return this.syncPreviewPromise || null;
} finally {
this.setStatus(status);
}
@@ -267,7 +280,7 @@ export abstract class AbstractSynchroniser extends Disposable {
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getLatestRemoteUserData(null, lastSyncUserData);
const preview = await this.generateReplacePreview(syncData, remoteUserData, lastSyncUserData);
await this.applyPreview(preview, false);
await this.applyPreview(remoteUserData, lastSyncUserData, preview, false);
this.logService.info(`${this.syncResourceLogLabel}: Finished resetting ${this.resource.toLowerCase()}.`);
} finally {
this.setStatus(SyncStatus.Idle);
@@ -276,7 +289,7 @@ export abstract class AbstractSynchroniser extends Disposable {
return true;
}
private async getLatestRemoteUserData(manifest: IUserDataManifest | null, lastSyncUserData: IRemoteUserData | null): Promise<IRemoteUserData> {
protected async getLatestRemoteUserData(manifest: IUserDataManifest | null, lastSyncUserData: IRemoteUserData | null): Promise<IRemoteUserData> {
if (lastSyncUserData) {
const latestRef = manifest && manifest.latest ? manifest.latest[this.resource] : undefined;
@@ -294,24 +307,15 @@ export abstract class AbstractSynchroniser extends Disposable {
return this.getRemoteUserData(lastSyncUserData);
}
async generateSyncPreview(): Promise<ISyncResourcePreview | null> {
if (this.isEnabled()) {
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
return this.generatePreview(remoteUserData, lastSyncUserData, CancellationToken.None);
}
return null;
}
private async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
private async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean): Promise<SyncStatus> {
if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) {
// current version is not compatible with cloud version
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.resource });
throw new UserDataSyncError(localize({ key: 'incompatible', comment: ['This is an error while syncing a resource that its local version is not compatible with its remote version.'] }, "Cannot sync {0} as its local version {1} is not compatible with its remote version {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.resource);
throw new UserDataSyncError(localize({ key: 'incompatible', comment: ['This is an error while syncing a resource that its local version is not compatible with its remote version.'] }, "Cannot sync {0} as its local version {1} is not compatible with its remote version {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.IncompatibleLocalContent, this.resource);
}
try {
return await this.doSync(remoteUserData, lastSyncUserData);
return await this.doSync(remoteUserData, lastSyncUserData, apply);
} catch (e) {
if (e instanceof UserDataSyncError) {
switch (e.code) {
@@ -319,7 +323,7 @@ export abstract class AbstractSynchroniser extends Disposable {
case UserDataSyncErrorCode.LocalPreconditionFailed:
// Rejected as there is a new local version. Syncing again...
this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize ${this.syncResourceLogLabel} as there is a new local version available. Synchronizing again...`);
return this.performSync(remoteUserData, lastSyncUserData);
return this.performSync(remoteUserData, lastSyncUserData, apply);
case UserDataSyncErrorCode.PreconditionFailed:
// Rejected as there is a new remote version. Syncing again...
@@ -332,32 +336,28 @@ export abstract class AbstractSynchroniser extends Disposable {
// and one of them successfully updated remote and last sync state.
lastSyncUserData = await this.getLastSyncUserData();
return this.performSync(remoteUserData, lastSyncUserData);
return this.performSync(remoteUserData, lastSyncUserData, apply);
}
}
throw e;
}
}
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean): Promise<SyncStatus> {
try {
// generate or use existing preview
if (!this.syncPreviewPromise) {
this.syncPreviewPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token));
}
const preview = await this.syncPreviewPromise;
if (preview.hasConflicts) {
return SyncStatus.HasConflicts;
this.syncPreviewPromise = createCancelablePromise(token => this.doGenerateSyncResourcePreview(remoteUserData, lastSyncUserData, apply, token));
}
// apply preview
await this.applyPreview(preview, false);
if (apply) {
const preview = await this.syncPreviewPromise;
const newConflicts = preview.resourcePreviews.filter(({ hasConflicts }) => hasConflicts);
return await this.updateConflictsAndApply(newConflicts, false);
} else {
return SyncStatus.Syncing;
}
// reset preview
this.syncPreviewPromise = null;
return SyncStatus.Idle;
} catch (error) {
// reset preview on error
@@ -367,32 +367,128 @@ export abstract class AbstractSynchroniser extends Disposable {
}
}
protected async getSyncPreviewInProgress(): Promise<ISyncResourcePreview | null> {
return this.syncPreviewPromise ? this.syncPreviewPromise : null;
async acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
if (!this.syncPreviewPromise) {
return null;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
this.syncPreviewPromise = createCancelablePromise(token => this.updateSyncResourcePreviewContent(preview, resource, content, token));
return this.merge(resource, force, headers);
} finally {
this.syncHeaders = {};
}
}
async acceptConflict(conflictUri: URI, conflictContent: string): Promise<void> {
let preview = await this.getSyncPreviewInProgress();
if (!preview || !preview.hasConflicts) {
return;
async merge(resource: URI, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
if (!this.syncPreviewPromise) {
return null;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
const resourcePreview = preview.resourcePreviews.find(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (!resourcePreview) {
return preview;
}
this.syncPreviewPromise = createCancelablePromise(token => this.updatePreviewWithConflict(preview!, conflictUri, conflictContent, token));
preview = await this.syncPreviewPromise;
/* mark merged */
resourcePreview.merged = true;
if (!preview.hasConflicts) {
/* Add or remove the preview from conflicts */
const newConflicts = [...this._conflicts];
const index = newConflicts.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (resourcePreview.hasConflicts) {
if (newConflicts.indexOf(resourcePreview) === -1) {
newConflicts.push(resourcePreview);
}
} else {
if (index !== -1) {
newConflicts.splice(index, 1);
}
}
// apply preview
await this.applyPreview(preview, false);
const status = await this.updateConflictsAndApply(newConflicts, force);
this.setStatus(status);
return this.syncPreviewPromise;
// reset preview
this.syncPreviewPromise = null;
} finally {
this.syncHeaders = {};
}
}
this.setStatus(SyncStatus.Idle);
private async updateConflictsAndApply(conflicts: IMergableResourcePreview[], force: boolean): Promise<SyncStatus> {
if (!this.syncPreviewPromise) {
return SyncStatus.Idle;
}
const preview = await this.syncPreviewPromise;
// update conflicts
this.updateConflicts(conflicts);
if (this._conflicts.length) {
return SyncStatus.HasConflicts;
}
// check if all are merged
if (preview.resourcePreviews.some(r => !r.merged)) {
return SyncStatus.Syncing;
}
// apply preview
await this.applyPreview(preview.remoteUserData, preview.lastSyncUserData, preview.resourcePreviews, force);
// reset preview
this.syncPreviewPromise = null;
// reset preview folder
await this.clearPreviewFolder();
return SyncStatus.Idle;
}
private async updateSyncResourcePreviewContent(preview: ISyncResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<ISyncResourcePreview> {
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource, localChange, remoteChange }) =>
(localChange !== Change.None || remoteChange !== Change.None)
&& (isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)));
if (index !== -1) {
const resourcePreviews = [...preview.resourcePreviews];
const resourcePreview = await this.updateResourcePreviewContent(resourcePreviews[index], resource, previewContent, token);
resourcePreviews[index] = { ...resourcePreview, merged: resourcePreviews[index].merged };
preview = {
...preview,
resourcePreviews
};
}
return preview;
}
protected async updateResourcePreviewContent(resourcePreview: IResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IResourcePreview> {
return {
...resourcePreview,
previewContent,
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.Modified,
};
}
private async clearPreviewFolder(): Promise<void> {
try {
await this.fileService.del(this.syncPreviewFolder, { recursive: true });
} catch (error) { /* Ignore */ }
}
private updateConflicts(conflicts: IMergableResourcePreview[]): void {
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
}
}
async hasPreviouslySynced(): Promise<boolean> {
@@ -400,11 +496,6 @@ export abstract class AbstractSynchroniser extends Disposable {
return !!lastSyncData;
}
protected async isLastSyncFromCurrentMachine(remoteUserData: IRemoteUserData): Promise<boolean> {
const machineId = await this.currentMachineIdPromise;
return !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
}
async getRemoteSyncResourceHandles(): Promise<ISyncResourceHandle[]> {
const handles = await this.userDataSyncStoreService.getAllRefs(this.resource);
return handles.map(({ created, ref }) => ({ created, uri: this.toRemoteBackupResource(ref) }));
@@ -447,12 +538,47 @@ export abstract class AbstractSynchroniser extends Disposable {
return null;
}
protected async resolvePreviewContent(uri: URI): Promise<string | null> {
const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
if (syncPreview) {
for (const resourcePreview of syncPreview.resourcePreviews) {
if (resourcePreview.previewResource && isEqual(resourcePreview.previewResource, uri)) {
return resourcePreview.previewContent || '';
}
if (resourcePreview.remoteResource && isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent || '';
}
if (resourcePreview.localResource && isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent || '';
}
}
}
return null;
}
async resetLocal(): Promise<void> {
try {
await this.fileService.del(this.lastSyncResource);
} catch (e) { /* ignore */ }
}
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, merge: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
const machineId = await this.currentMachineIdPromise;
const isLastSyncFromCurrentMachine = !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
// For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine
const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData;
const resourcePreviews = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
// Mark merge
const mergableResourcePreviews = resourcePreviews.map(r => ({
...r,
merged: merge || (r.localChange === Change.None && r.remoteChange === Change.None) /* Mark previews with no changes as merged */
}));
return { remoteUserData, lastSyncUserData, resourcePreviews: mergableResourcePreviews, isLastSyncFromCurrentMachine };
}
async getLastSyncUserData<T extends IRemoteUserData>(): Promise<T | null> {
try {
const content = await this.fileService.readFile(this.lastSyncResource);
@@ -500,7 +626,7 @@ export abstract class AbstractSynchroniser extends Disposable {
} catch (error) {
this.logService.error(error);
}
throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with current version."), UserDataSyncErrorCode.Incompatible, this.resource);
throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with current version."), UserDataSyncErrorCode.IncompatibleRemoteContent, this.resource);
}
private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise<IUserData> {
@@ -526,26 +652,33 @@ export abstract class AbstractSynchroniser extends Disposable {
}
async stop(): Promise<void> {
this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
if (this.status === SyncStatus.Idle) {
return;
}
this.logService.trace(`${this.syncResourceLogLabel}: Stopping synchronizing ${this.resource.toLowerCase()}.`);
if (this.syncPreviewPromise) {
this.syncPreviewPromise.cancel();
this.syncPreviewPromise = null;
}
this.updateConflicts([]);
await this.clearPreviewFolder();
this.setStatus(SyncStatus.Idle);
this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
}
protected abstract readonly version: number;
protected abstract generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<ISyncResourcePreview>;
protected abstract generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<ISyncResourcePreview>;
protected abstract generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncResourcePreview>;
protected abstract generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<ISyncResourcePreview>;
protected abstract updatePreviewWithConflict(preview: ISyncResourcePreview, conflictResource: URI, content: string, token: CancellationToken): Promise<ISyncResourcePreview>;
protected abstract applyPreview(preview: ISyncResourcePreview, forcePush: boolean): Promise<void>;
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>;
}
export interface IFileSyncPreview extends ISyncResourcePreview {
export interface IFileResourcePreview extends IResourcePreview {
readonly fileContent: IFileContent | null;
readonly content: string | null;
}
export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
@@ -568,28 +701,6 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
}
async stop(): Promise<void> {
await super.stop();
try {
await this.fileService.del(this.localPreviewResource);
} catch (e) { /* ignore */ }
}
protected async resolvePreviewContent(conflictResource: URI): Promise<string | null> {
if (isEqual(this.remotePreviewResource, conflictResource) || isEqual(this.localPreviewResource, conflictResource)) {
const syncPreview = await this.getSyncPreviewInProgress();
if (syncPreview) {
if (isEqual(this.remotePreviewResource, conflictResource)) {
return syncPreview.remoteUserData && syncPreview.remoteUserData.syncData ? syncPreview.remoteUserData.syncData.content : null;
}
if (isEqual(this.localPreviewResource, conflictResource)) {
return (syncPreview as IFileSyncPreview).fileContent ? (syncPreview as IFileSyncPreview).fileContent!.value.toString() : null;
}
}
}
return null;
}
protected async getLocalFileContent(): Promise<IFileContent | null> {
try {
return await this.fileService.readFile(this.file);
@@ -598,14 +709,14 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
}
}
protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null): Promise<void> {
protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null, force: boolean): Promise<void> {
try {
if (oldContent) {
// file exists already
await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent);
await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), force ? undefined : oldContent);
} else {
// file does not exist
await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false });
await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: force });
}
} catch (e) {
if ((e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) ||
@@ -624,8 +735,6 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
this.triggerLocalChange();
}
protected abstract readonly localPreviewResource: URI;
protected abstract readonly remotePreviewResource: URI;
}
export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser {