abstractSynchronizer.ts 6.4 KB
Newer Older
S
Sandeep Somavarapu 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { Disposable } from 'vs/base/common/lifecycle';
S
Sandeep Somavarapu 已提交
7
import { IFileService, IFileContent } from 'vs/platform/files/common/files';
S
Sandeep Somavarapu 已提交
8 9
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
S
Sandeep Somavarapu 已提交
10
import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync';
S
Sandeep Somavarapu 已提交
11
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
S
Sandeep Somavarapu 已提交
12
import { joinPath, dirname } from 'vs/base/common/resources';
S
Sandeep Somavarapu 已提交
13 14
import { toLocalISOString } from 'vs/base/common/date';
import { ThrottledDelayer } from 'vs/base/common/async';
S
Sandeep Somavarapu 已提交
15
import { Emitter, Event } from 'vs/base/common/event';
16 17 18 19 20
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';

type SyncConflictsClassification = {
	source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
S
Sandeep Somavarapu 已提交
21 22 23

export abstract class AbstractSynchroniser extends Disposable {

24
	protected readonly syncFolder: URI;
S
Sandeep Somavarapu 已提交
25 26
	private cleanUpDelayer: ThrottledDelayer<void>;

S
Sandeep Somavarapu 已提交
27 28 29 30 31 32 33 34 35 36
	private _status: SyncStatus = SyncStatus.Idle;
	get status(): SyncStatus { return this._status; }
	private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
	readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;

	protected readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
	readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;

	protected readonly lastSyncResource: URI;

S
Sandeep Somavarapu 已提交
37
	constructor(
38
		readonly source: SyncSource,
S
Sandeep Somavarapu 已提交
39
		@IFileService protected readonly fileService: IFileService,
S
Sandeep Somavarapu 已提交
40 41
		@IEnvironmentService environmentService: IEnvironmentService,
		@IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService,
42
		@ITelemetryService private readonly telemetryService: ITelemetryService,
S
Sandeep Somavarapu 已提交
43 44
	) {
		super();
S
Sandeep Somavarapu 已提交
45
		this.syncFolder = joinPath(environmentService.userDataSyncHome, source);
S
Sandeep Somavarapu 已提交
46
		this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`);
S
Sandeep Somavarapu 已提交
47 48 49
		this.cleanUpDelayer = new ThrottledDelayer(50);
	}

S
Sandeep Somavarapu 已提交
50 51
	protected setStatus(status: SyncStatus): void {
		if (this._status !== status) {
52
			const oldStatus = this._status;
S
Sandeep Somavarapu 已提交
53 54
			this._status = status;
			this._onDidChangStatus.fire(status);
55 56 57 58 59 60 61 62
			if (status === SyncStatus.HasConflicts) {
				// Log to telemetry when there is a sync conflict
				this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this.source });
			}
			if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) {
				// Log to telemetry when conflicts are resolved
				this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: this.source });
			}
S
Sandeep Somavarapu 已提交
63 64 65 66 67 68 69 70 71
		}
	}

	async hasPreviouslySynced(): Promise<boolean> {
		const lastSyncData = await this.getLastSyncUserData();
		return !!lastSyncData;
	}

	async hasRemoteData(): Promise<boolean> {
S
Sandeep Somavarapu 已提交
72 73
		const lastSyncData = await this.getLastSyncUserData();
		const remoteUserData = await this.getRemoteUserData(lastSyncData);
S
Sandeep Somavarapu 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
		return remoteUserData.content !== null;
	}

	async resetLocal(): Promise<void> {
		try {
			await this.fileService.del(this.lastSyncResource);
		} catch (e) { /* ignore */ }
	}

	protected async getLastSyncUserData<T extends IUserData>(): Promise<T | null> {
		try {
			const content = await this.fileService.readFile(this.lastSyncResource);
			return JSON.parse(content.value.toString());
		} catch (error) {
			return null;
		}
	}

	protected async updateLastSyncUserData<T extends IUserData>(lastSyncUserData: T): Promise<void> {
		await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
	}

S
Sandeep Somavarapu 已提交
96 97
	protected async getRemoteUserData(lastSyncData: IUserData | null): Promise<IUserData> {
		return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData, this.source);
S
Sandeep Somavarapu 已提交
98 99 100 101 102 103
	}

	protected async updateRemoteUserData(content: string, ref: string | null): Promise<string> {
		return this.userDataSyncStoreService.write(this.getRemoteDataResourceKey(), content, ref, this.source);
	}

S
Sandeep Somavarapu 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
	protected async backupLocal(content: VSBuffer): Promise<void> {
		const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''));
		await this.fileService.writeFile(resource, content);
		this.cleanUpDelayer.trigger(() => this.cleanUpBackup());
	}

	private async cleanUpBackup(): Promise<void> {
		const stat = await this.fileService.resolve(this.syncFolder);
		if (stat.children) {
			const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort();
			const toDelete = all.slice(0, Math.max(0, all.length - 9));
			await Promise.all(toDelete.map(stat => this.fileService.del(stat.resource)));
		}
	}

S
Sandeep Somavarapu 已提交
119 120 121 122 123 124 125 126 127 128 129
	protected abstract getRemoteDataResourceKey(): string;
}

export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {

	constructor(
		protected readonly file: URI,
		readonly source: SyncSource,
		@IFileService fileService: IFileService,
		@IEnvironmentService environmentService: IEnvironmentService,
		@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
130
		@ITelemetryService telemetryService: ITelemetryService,
S
Sandeep Somavarapu 已提交
131
	) {
132
		super(source, fileService, environmentService, userDataSyncStoreService, telemetryService);
S
Sandeep Somavarapu 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
		this._register(this.fileService.watch(dirname(file)));
		this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(file))(() => this._onDidChangeLocal.fire()));
	}

	protected async getLocalFileContent(): Promise<IFileContent | null> {
		try {
			return await this.fileService.readFile(this.file);
		} catch (error) {
			return null;
		}
	}

	protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null): Promise<void> {
		if (oldContent) {
			// file exists already
			await this.backupLocal(oldContent.value);
			await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent);
		} else {
			// file does not exist
			await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false });
		}
	}

S
Sandeep Somavarapu 已提交
156
}