globalStateSync.ts 6.8 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';
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

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 已提交
40
		@IConfigurationService private readonly configurationService: IConfigurationService,
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
	) {
		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 已提交
56 57 58 59 60
		if (!this.configurationService.getValue<boolean>('sync.enableUIState')) {
			this.logService.trace('UI State: Skipping synchronizing UI state as it is disabled.');
			return false;
		}

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

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

		try {
			await this.doSync();
S
Sandeep Somavarapu 已提交
71
			this.logService.trace('UI State: Finised synchronizing ui state.');
72 73 74 75 76 77
			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 已提交
78
				this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...');
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
				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 已提交
100
			this.logService.info('UI State: Updating local ui state...');
101 102 103 104 105
			await this.writeLocalGlobalState(local);
		}

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

		if (remoteData.content
			&& (!lastSyncData || lastSyncData.ref !== remoteData.ref)
		) {
			// update last sync
S
Sandeep Somavarapu 已提交
114
			this.logService.info('UI State: Updating last synchronised ui state...');
115 116 117 118 119 120 121 122 123 124 125 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
			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);
			const argvValue: IStringDictionary<any> = JSON.parse(content.value.toString());
			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 };
	}

}