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

@@ -3,7 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle, IUserDataManifest, ISyncTask } from 'vs/platform/userDataSync/common/userDataSync';
import {
IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode,
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID
} from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event';
@@ -21,6 +24,8 @@ import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSy
import { CancellationToken } from 'vs/base/common/cancellation';
import { IHeaders } from 'vs/base/parts/request/common/request';
import { generateUuid } from 'vs/base/common/uuid';
import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
import { isPromiseCanceledError } from 'vs/base/common/errors';
type SyncErrorClassification = {
resource?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -29,6 +34,12 @@ type SyncErrorClassification = {
const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime';
function createSyncHeaders(executionId: string): IHeaders {
const headers: IHeaders = {};
headers[HEADER_EXECUTION_ID] = executionId;
return headers;
}
export class UserDataSyncService extends Disposable implements IUserDataSyncService {
_serviceBrand: any;
@@ -40,15 +51,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
private _onSynchronizeResource: Emitter<SyncResource> = this._register(new Emitter<SyncResource>());
readonly onSynchronizeResource: Event<SyncResource> = this._onSynchronizeResource.event;
readonly onDidChangeLocal: Event<SyncResource>;
private _conflicts: SyncResourceConflicts[] = [];
get conflicts(): SyncResourceConflicts[] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<SyncResourceConflicts[]> = this._register(new Emitter<SyncResourceConflicts[]>());
readonly onDidChangeConflicts: Event<SyncResourceConflicts[]> = this._onDidChangeConflicts.event;
private _conflicts: [SyncResource, IResourcePreview[]][] = [];
get conflicts(): [SyncResource, IResourcePreview[]][] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<[SyncResource, IResourcePreview[]][]> = this._register(new Emitter<[SyncResource, IResourcePreview[]][]>());
readonly onDidChangeConflicts: Event<[SyncResource, IResourcePreview[]][]> = this._onDidChangeConflicts.event;
private _syncErrors: [SyncResource, UserDataSyncError][] = [];
private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>());
@@ -95,7 +103,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
try {
for (const synchroniser of this.synchronisers) {
try {
this._onSynchronizeResource.fire(synchroniser.resource);
await synchroniser.pull();
} catch (e) {
this.handleSynchronizerError(e, synchroniser.resource);
@@ -129,18 +136,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
private recoveredSettings: boolean = false;
async sync(): Promise<void> {
const syncTask = await this.createSyncTask();
return syncTask.run(CancellationToken.None);
}
async createSyncTask(): Promise<ISyncTask> {
this.telemetryService.publicLog2('sync/getmanifest');
await this.checkEnablement();
const executionId = generateUuid();
let manifest: IUserDataManifest | null;
try {
manifest = await this.userDataSyncStoreService.manifest({ 'X-Execution-Id': executionId });
manifest = await this.userDataSyncStoreService.manifest(createSyncHeaders(executionId));
} catch (error) {
if (error instanceof UserDataSyncError) {
this.telemetryService.publicLog2<{ resource?: string, executionId?: string }, SyncErrorClassification>(`sync/error/${error.code}`, { resource: error.resource, executionId });
@@ -150,20 +152,46 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
let executed = false;
const that = this;
let cancellablePromise: CancelablePromise<void> | undefined;
return {
manifest,
run(token: CancellationToken): Promise<void> {
run(): Promise<void> {
if (executed) {
throw new Error('Can run a task only once');
}
return that.doSync(manifest, executionId, token);
cancellablePromise = createCancelablePromise(token => that.sync(manifest, executionId, token));
return cancellablePromise.finally(() => cancellablePromise = undefined);
},
async stop(): Promise<void> {
if (cancellablePromise) {
cancellablePromise.cancel();
return that.stop();
}
}
};
}
private async doSync(manifest: IUserDataManifest | null, executionId: string, token: CancellationToken): Promise<void> {
async createManualSyncTask(): Promise<IManualSyncTask> {
await this.checkEnablement();
const executionId = generateUuid();
const syncHeaders = createSyncHeaders(executionId);
let manifest: IUserDataManifest | null;
try {
manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
} catch (error) {
if (error instanceof UserDataSyncError) {
this.telemetryService.publicLog2<{ resource?: string, executionId?: string }, SyncErrorClassification>(`sync/error/${error.code}`, { resource: error.resource, executionId });
}
throw error;
}
return new ManualSyncTask(executionId, manifest, syncHeaders, this.synchronisers, this.logService);
}
private recoveredSettings: boolean = false;
private async sync(manifest: IUserDataManifest | null, executionId: string, token: CancellationToken): Promise<void> {
if (!this.recoveredSettings) {
await this.settingsSynchroniser.recoverSettings();
this.recoveredSettings = true;
@@ -182,7 +210,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this.setStatus(SyncStatus.Syncing);
}
const syncHeaders: IHeaders = { 'X-Execution-Id': executionId };
const syncHeaders = createSyncHeaders(executionId);
for (const synchroniser of this.synchronisers) {
// Return if cancellation is requested
@@ -190,7 +218,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return;
}
try {
this._onSynchronizeResource.fire(synchroniser.resource);
await synchroniser.sync(manifest, syncHeaders);
} catch (e) {
this.handleSynchronizerError(e, synchroniser.resource);
@@ -211,18 +238,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
async replace(uri: URI): Promise<void> {
await this.checkEnablement();
for (const synchroniser of this.synchronisers) {
if (await synchroniser.replace(uri)) {
return;
}
}
}
async stop(): Promise<void> {
await this.checkEnablement();
private async stop(): Promise<void> {
if (this.status === SyncStatus.Idle) {
return;
}
@@ -239,15 +255,21 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
async acceptConflict(conflict: URI, content: string): Promise<void> {
async replace(uri: URI): Promise<void> {
await this.checkEnablement();
const syncResourceConflict = this.conflicts.filter(({ conflicts }) => conflicts.some(({ local, remote }) => isEqual(conflict, local) || isEqual(conflict, remote)))[0];
if (syncResourceConflict) {
const synchroniser = this.getSynchroniser(syncResourceConflict.syncResource);
await synchroniser.acceptConflict(conflict, content);
for (const synchroniser of this.synchronisers) {
if (await synchroniser.replace(uri)) {
return;
}
}
}
async acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string, executionId: string = generateUuid()): Promise<void> {
await this.checkEnablement();
const synchroniser = this.getSynchroniser(syncResource);
await synchroniser.acceptPreviewContent(resource, content, false, createSyncHeaders(executionId));
}
async resolveContent(resource: URI): Promise<string | null> {
for (const synchroniser of this.synchronisers) {
const content = await synchroniser.resolveContent(resource);
@@ -274,35 +296,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.getSynchroniser(resource).getMachineId(syncResourceHandle);
}
async isFirstTimeSyncingWithAnotherMachine(): Promise<boolean> {
await this.checkEnablement();
if (!await this.userDataSyncStoreService.manifest()) {
return false;
}
async hasLocalData(): Promise<boolean> {
// skip global state synchronizer
const synchronizers = [this.settingsSynchroniser, this.keybindingsSynchroniser, this.snippetsSynchroniser, this.extensionsSynchroniser];
let hasLocalData: boolean = false;
for (const synchroniser of synchronizers) {
if (await synchroniser.hasLocalData()) {
hasLocalData = true;
break;
}
}
if (!hasLocalData) {
return false;
}
for (const synchroniser of synchronizers) {
const preview = await synchroniser.generateSyncPreview();
if (preview && !preview.isLastSyncFromCurrentMachine && (preview.hasLocalChanged || preview.hasRemoteChanged)) {
return true;
}
}
return false;
}
@@ -312,6 +313,16 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
await this.resetLocal();
}
async resetRemote(): Promise<void> {
await this.checkEnablement();
try {
await this.userDataSyncStoreService.clear();
this.logService.info('Cleared data on server');
} catch (e) {
this.logService.error(e);
}
}
async resetLocal(): Promise<void> {
await this.checkEnablement();
this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL);
@@ -335,16 +346,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return false;
}
private async resetRemote(): Promise<void> {
await this.checkEnablement();
try {
await this.userDataSyncStoreService.clear();
this.logService.info('Cleared data on server');
} catch (e) {
this.logService.error(e);
}
}
private setStatus(status: SyncStatus): void {
const oldStatus = this._status;
if (this._status !== status) {
@@ -364,7 +365,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private updateConflicts(): void {
const conflicts = this.computeConflicts();
if (!equals(this._conflicts, conflicts, (a, b) => a.syncResource === b.syncResource && equals(a.conflicts, b.conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote)))) {
if (!equals(this._conflicts, conflicts, ([syncResourceA, conflictsA], [syncResourceB, conflictsB]) => syncResourceA === syncResourceA && equals(conflictsA, conflictsB, (a, b) => isEqual(a.previewResource, b.previewResource)))) {
this._conflicts = this.computeConflicts();
this._onDidChangeConflicts.fire(conflicts);
}
@@ -401,7 +402,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
case UserDataSyncErrorCode.LocalTooManyRequests:
case UserDataSyncErrorCode.Gone:
case UserDataSyncErrorCode.UpgradeRequired:
case UserDataSyncErrorCode.Incompatible:
case UserDataSyncErrorCode.IncompatibleRemoteContent:
case UserDataSyncErrorCode.IncompatibleLocalContent:
throw e;
}
}
@@ -409,13 +411,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this.logService.error(`${source}: ${toErrorMessage(e)}`);
}
private computeConflicts(): SyncResourceConflicts[] {
private computeConflicts(): [SyncResource, IResourcePreview[]][] {
return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)
.map(s => ({ syncResource: s.resource, conflicts: s.conflicts }));
.map(s => ([s.resource, s.conflicts.map(toStrictResourcePreview)]));
}
getSynchroniser(source: SyncResource): IUserDataSynchroniser {
return this.synchronisers.filter(s => s.resource === source)[0];
return this.synchronisers.find(s => s.resource === source)!;
}
private async checkEnablement(): Promise<void> {
@@ -425,3 +427,222 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
class ManualSyncTask extends Disposable implements IManualSyncTask {
private previewsPromise: CancelablePromise<[SyncResource, ISyncResourcePreview][]> | undefined;
private previews: [SyncResource, ISyncResourcePreview][] | undefined;
private synchronizingResources: [SyncResource, URI[]][] = [];
private _onSynchronizeResources = this._register(new Emitter<[SyncResource, URI[]][]>());
readonly onSynchronizeResources = this._onSynchronizeResources.event;
private isDisposed: boolean = false;
constructor(
readonly id: string,
readonly manifest: IUserDataManifest | null,
private readonly syncHeaders: IHeaders,
private readonly synchronisers: IUserDataSynchroniser[],
private readonly logService: IUserDataSyncLogService,
) {
super();
}
async preview(): Promise<[SyncResource, ISyncResourcePreview][]> {
if (this.isDisposed) {
throw new Error('Disposed');
}
if (!this.previewsPromise) {
this.previewsPromise = createCancelablePromise(token => this.getPreviews(token));
}
this.previews = await this.previewsPromise;
return this.previews;
}
async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.acceptPreviewContent(resource, content, force, this.syncHeaders));
}
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
if (resource) {
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.merge(resource, force, this.syncHeaders));
} else {
return this.mergeAll();
}
}
private async mergeOrAccept(resource: URI, mergeOrAccept: (synchroniser: IUserDataSynchroniser, force: boolean) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
if (!this.previews) {
throw new Error('You need to create preview before merging or accepting');
}
const index = this.previews.findIndex(([, preview]) => preview.resourcePreviews.some(({ localResource, previewResource, remoteResource }) =>
isEqual(resource, localResource) || isEqual(resource, previewResource) || isEqual(resource, remoteResource)));
if (index === -1) {
return this.previews;
}
const [syncResource, previews] = this.previews[index];
const resourcePreview = previews.resourcePreviews.find(({ localResource, remoteResource, previewResource }) => isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (!resourcePreview) {
return this.previews;
}
let synchronizingResources = this.synchronizingResources.find(s => s[0] === syncResource);
if (!synchronizingResources) {
synchronizingResources = [syncResource, []];
this.synchronizingResources.push(synchronizingResources);
}
if (!synchronizingResources[1].some(s => isEqual(s, resourcePreview.localResource))) {
synchronizingResources[1].push(resourcePreview.localResource);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
const synchroniser = this.synchronisers.find(s => s.resource === this.previews![index][0])!;
/* force only if the resource is local or remote resource */
const force = isEqual(resource, resourcePreview.localResource) || isEqual(resource, resourcePreview.remoteResource);
const preview = await mergeOrAccept(synchroniser, force);
preview ? this.previews.splice(index, 1, this.toSyncResourcePreview(synchroniser.resource, preview)) : this.previews.splice(index, 1);
const i = this.synchronizingResources.findIndex(s => s[0] === syncResource);
this.synchronizingResources[i][1].splice(synchronizingResources[1].findIndex(r => isEqual(r, resourcePreview.localResource)), 1);
if (!synchronizingResources[1].length) {
this.synchronizingResources.splice(i, 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
return this.previews;
}
private async mergeAll(): Promise<[SyncResource, ISyncResourcePreview][]> {
if (!this.previews) {
throw new Error('You need to create preview before merging');
}
if (this.synchronizingResources.length) {
throw new Error('Cannot merge while synchronizing resources');
}
const previews: [SyncResource, ISyncResourcePreview][] = [];
for (const [syncResource, preview] of this.previews) {
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
this._onSynchronizeResources.fire(this.synchronizingResources);
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
let syncResourcePreview = null;
for (const resourcePreview of preview.resourcePreviews) {
syncResourcePreview = await synchroniser.merge(resourcePreview.remoteResource, false, this.syncHeaders);
}
if (syncResourcePreview) {
previews.push([syncResource, syncResourcePreview]);
}
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
this.previews = previews;
return this.previews;
}
async pull(): Promise<void> {
if (!this.previews) {
throw new Error('You need to create preview before applying');
}
if (this.synchronizingResources.length) {
throw new Error('Cannot pull while synchronizing resources');
}
for (const [syncResource, preview] of this.previews) {
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
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.acceptPreviewContent(resourcePreview.remoteResource, content, true, this.syncHeaders);
}
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
this.previews = [];
}
async push(): Promise<void> {
if (!this.previews) {
throw new Error('You need to create preview before applying');
}
if (this.synchronizingResources.length) {
throw new Error('Cannot pull while synchronizing resources');
}
for (const [syncResource, preview] of this.previews) {
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
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.acceptPreviewContent(resourcePreview.localResource, content, true, this.syncHeaders);
}
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
this.previews = [];
}
async stop(): Promise<void> {
for (const synchroniser of this.synchronisers) {
try {
await synchroniser.stop();
} catch (error) {
if (!isPromiseCanceledError(error)) {
this.logService.error(error);
}
}
}
this.reset();
}
private async getPreviews(token: CancellationToken): Promise<[SyncResource, ISyncResourcePreview][]> {
const result: [SyncResource, ISyncResourcePreview][] = [];
for (const synchroniser of this.synchronisers) {
if (token.isCancellationRequested) {
return [];
}
const preview = await synchroniser.preview(this.manifest, this.syncHeaders);
if (preview) {
result.push(this.toSyncResourcePreview(synchroniser.resource, preview));
}
}
return result;
}
private toSyncResourcePreview(syncResource: SyncResource, preview: ISyncResourcePreview): [SyncResource, ISyncResourcePreview] {
return [
syncResource,
{
isLastSyncFromCurrentMachine: preview.isLastSyncFromCurrentMachine,
resourcePreviews: preview.resourcePreviews.map(toStrictResourcePreview)
}
];
}
private reset(): void {
if (this.previewsPromise) {
this.previewsPromise.cancel();
this.previewsPromise = undefined;
}
this.previews = undefined;
this.synchronizingResources = [];
}
dispose(): void {
this.reset();
this.isDisposed = true;
}
}
function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePreview {
return {
localResource: resourcePreview.localResource,
previewResource: resourcePreview.previewResource,
remoteResource: resourcePreview.remoteResource,
localChange: resourcePreview.localChange,
remoteChange: resourcePreview.remoteChange,
merged: resourcePreview.merged,
};
}