globalStateSync.ts 6.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*---------------------------------------------------------------------------------------------
 *  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';
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { joinPath, dirname } from 'vs/base/common/resources';
import { IFileService } from 'vs/platform/files/common/files';
import { IStringDictionary } from 'vs/base/common/collections';
import { edit } from 'vs/platform/userDataSync/common/content';
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
S
Sandeep Somavarapu 已提交
17
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
18
import { parse } from 'vs/base/common/json';
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

const argvProperties: string[] = ['locale'];

export class GlobalStateSynchroniser extends Disposable implements ISynchroniser {

	private static EXTERNAL_USER_DATA_GLOBAL_STATE_KEY: string = 'globalState';

	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;

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

	private readonly lastSyncGlobalStateResource: URI;

	constructor(
		@IFileService private readonly fileService: IFileService,
		@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
		@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
S
Sandeep Somavarapu 已提交
41
		@IConfigurationService private readonly configurationService: IConfigurationService,
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
	) {
		super();
		this.lastSyncGlobalStateResource = joinPath(environmentService.userRoamingDataHome, '.lastSyncGlobalState');
		this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
		this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
	}

	private setStatus(status: SyncStatus): void {
		if (this._status !== status) {
			this._status = status;
			this._onDidChangStatus.fire(status);
		}
	}

	async sync(): Promise<boolean> {
S
Sandeep Somavarapu 已提交
57 58 59 60 61
		if (!this.configurationService.getValue<boolean>('sync.enableUIState')) {
			this.logService.trace('UI State: Skipping synchronizing UI state as it is disabled.');
			return false;
		}

62
		if (this.status !== SyncStatus.Idle) {
S
Sandeep Somavarapu 已提交
63
			this.logService.trace('UI State: Skipping synchronizing ui state as it is running already.');
64 65 66
			return false;
		}

S
Sandeep Somavarapu 已提交
67
		this.logService.trace('UI State: Started synchronizing ui state...');
68 69 70 71
		this.setStatus(SyncStatus.Syncing);

		try {
			await this.doSync();
S
Sandeep Somavarapu 已提交
72
			this.logService.trace('UI State: Finised synchronizing ui state.');
73 74 75 76 77 78
			this.setStatus(SyncStatus.Idle);
			return true;
		} catch (e) {
			this.setStatus(SyncStatus.Idle);
			if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
				// Rejected as there is a new remote version. Syncing again,
S
Sandeep Somavarapu 已提交
79
				this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...');
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
				return this.sync();
			}
			throw e;
		}
	}

	stop(): void { }

	private async doSync(): Promise<void> {
		const lastSyncData = await this.getLastSyncUserData();
		const lastSyncGlobalState = lastSyncData && lastSyncData.content ? JSON.parse(lastSyncData.content) : null;

		let remoteData = await this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData);
		const remoteGlobalState: IGlobalState = remoteData.content ? JSON.parse(remoteData.content) : null;

		const localGloablState = await this.getLocalGlobalState();

		const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState);

		if (local) {
			// update local
S
Sandeep Somavarapu 已提交
101
			this.logService.info('UI State: Updating local ui state...');
102 103 104 105 106
			await this.writeLocalGlobalState(local);
		}

		if (remote) {
			// update remote
S
Sandeep Somavarapu 已提交
107
			this.logService.info('UI State: Updating remote ui state...');
108 109 110 111 112 113 114
			remoteData = await this.writeToRemote(remote, remoteData.ref);
		}

		if (remoteData.content
			&& (!lastSyncData || lastSyncData.ref !== remoteData.ref)
		) {
			// update last sync
S
Sandeep Somavarapu 已提交
115
			this.logService.info('UI State: Updating last synchronised ui state...');
116 117 118 119 120 121 122 123 124
			await this.updateLastSyncValue(remoteData);
		}
	}

	private async getLocalGlobalState(): Promise<IGlobalState> {
		const argv: IStringDictionary<any> = {};
		const storage: IStringDictionary<any> = {};
		try {
			const content = await this.fileService.readFile(this.environmentService.argvResource);
S
Sandeep Somavarapu 已提交
125
			const argvValue: IStringDictionary<any> = parse(content.value.toString());
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
			for (const argvProperty of argvProperties) {
				if (argvValue[argvProperty] !== undefined) {
					argv[argvProperty] = argvValue[argvProperty];
				}
			}
		} catch (error) { }
		return { argv, storage };
	}

	private async writeLocalGlobalState(globalState: IGlobalState): Promise<void> {
		const content = await this.fileService.readFile(this.environmentService.argvResource);
		let argvContent = content.value.toString();
		for (const argvProperty of Object.keys(globalState.argv)) {
			argvContent = edit(argvContent, [argvProperty], globalState.argv[argvProperty], {});
		}
		if (argvContent !== content.value.toString()) {
			await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(argvContent));
		}
	}

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

	private async updateLastSyncValue(remoteUserData: IUserData): Promise<void> {
		await this.fileService.writeFile(this.lastSyncGlobalStateResource, VSBuffer.fromString(JSON.stringify(remoteUserData)));
	}

	private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise<IUserData> {
		const content = JSON.stringify(globalState);
		ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref);
		return { content, ref };
	}

}