globalStateSync.ts 8.8 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

S
Sandeep Somavarapu 已提交
6
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync';
7
import { VSBuffer } from 'vs/base/common/buffer';
S
Sandeep Somavarapu 已提交
8
import { Event } from 'vs/base/common/event';
9
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
S
Sandeep Somavarapu 已提交
10
import { dirname } from 'vs/base/common/resources';
11 12 13 14
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 已提交
15
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
16
import { parse } from 'vs/base/common/json';
17
import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer';
18
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
19 20 21

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

22 23 24
interface ISyncPreviewResult {
	readonly local: IGlobalState | undefined;
	readonly remote: IGlobalState | undefined;
S
Sandeep Somavarapu 已提交
25
	readonly remoteUserData: IUserData;
26
	readonly lastSyncUserData: IUserData | null;
27 28
}

29
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
30 31

	constructor(
32
		@IFileService fileService: IFileService,
S
Sandeep Somavarapu 已提交
33
		@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
34 35
		@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
S
Sandeep Somavarapu 已提交
36
		@IConfigurationService private readonly configurationService: IConfigurationService,
37
		@ITelemetryService telemetryService: ITelemetryService,
38
	) {
39
		super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, telemetryService);
40 41 42 43
		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()));
	}

S
Sandeep Somavarapu 已提交
44
	protected getRemoteDataResourceKey(): string { return 'globalState'; }
45

46 47 48 49 50 51 52 53 54 55 56 57
	async pull(): Promise<void> {
		if (!this.configurationService.getValue<boolean>('sync.enableUIState')) {
			this.logService.info('UI State: Skipped pulling ui state as it is disabled.');
			return;
		}

		this.stop();

		try {
			this.logService.info('UI State: Started pulling ui state...');
			this.setStatus(SyncStatus.Syncing);

58 59
			const lastSyncUserData = await this.getLastSyncUserData();
			const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
60 61 62

			if (remoteUserData.content !== null) {
				const local: IGlobalState = JSON.parse(remoteUserData.content);
63
				await this.apply({ local, remote: undefined, remoteUserData, lastSyncUserData });
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
			}

			// No remote exists to pull
			else {
				this.logService.info('UI State: Remote UI state does not exist.');
			}

			this.logService.info('UI State: Finished pulling UI state.');
		} finally {
			this.setStatus(SyncStatus.Idle);
		}
	}

	async push(): Promise<void> {
		if (!this.configurationService.getValue<boolean>('sync.enableUIState')) {
			this.logService.info('UI State: Skipped pushing UI State as it is disabled.');
			return;
		}

		this.stop();

		try {
			this.logService.info('UI State: Started pushing UI State...');
			this.setStatus(SyncStatus.Syncing);

			const remote = await this.getLocalGlobalState();
90 91 92
			const lastSyncUserData = await this.getLastSyncUserData();
			const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
			await this.apply({ local: undefined, remote, remoteUserData, lastSyncUserData }, true);
93

S
Sandeep Somavarapu 已提交
94
			this.logService.info('UI State: Finished pushing UI State.');
95 96 97 98 99 100
		} finally {
			this.setStatus(SyncStatus.Idle);
		}

	}

S
Sandeep Somavarapu 已提交
101
	async sync(): Promise<void> {
S
Sandeep Somavarapu 已提交
102
		if (!this.configurationService.getValue<boolean>('sync.enableUIState')) {
103
			this.logService.info('UI State: Skipping synchronizing UI state as it is disabled.');
S
Sandeep Somavarapu 已提交
104
			return;
S
Sandeep Somavarapu 已提交
105
		}
106
		if (this.status !== SyncStatus.Idle) {
107
			this.logService.info('UI State: Skipping synchronizing ui state as it is running already.');
S
Sandeep Somavarapu 已提交
108
			return;
109 110
		}

S
Sandeep Somavarapu 已提交
111
		this.logService.trace('UI State: Started synchronizing ui state...');
112 113 114
		this.setStatus(SyncStatus.Syncing);

		try {
115 116
			const result = await this.getPreview();
			await this.apply(result);
D
Daniel Imms 已提交
117
			this.logService.trace('UI State: Finished synchronizing ui state.');
118 119
		} catch (e) {
			this.setStatus(SyncStatus.Idle);
S
Sandeep Somavarapu 已提交
120
			if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
121
				// Rejected as there is a new remote version. Syncing again,
S
Sandeep Somavarapu 已提交
122
				this.logService.info('UI State: Failed to synchronize ui state as there is a new remote version available. Synchronizing again...');
123 124 125
				return this.sync();
			}
			throw e;
126 127
		} finally {
			this.setStatus(SyncStatus.Idle);
128 129 130
		}
	}

S
Sandeep Somavarapu 已提交
131 132 133 134 135 136
	async stop(): Promise<void> { }

	async restart(): Promise<void> {
		throw new Error('UI State: Conflicts should not occur');
	}

S
Sandeep Somavarapu 已提交
137
	accept(content: string): Promise<void> {
S
Sandeep Somavarapu 已提交
138 139
		throw new Error('UI State: Conflicts should not occur');
	}
140

141 142 143 144 145 146 147 148 149 150 151 152
	async hasLocalData(): Promise<boolean> {
		try {
			const localGloablState = await this.getLocalGlobalState();
			if (localGloablState.argv['locale'] !== 'en') {
				return true;
			}
		} catch (error) {
			/* ignore error */
		}
		return false;
	}

153 154 155 156
	async getRemoteContent(): Promise<string | null> {
		return null;
	}

157
	private async getPreview(): Promise<ISyncPreviewResult> {
158 159
		const lastSyncUserData = await this.getLastSyncUserData();
		const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null;
160

161
		const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
162
		const remoteGlobalState: IGlobalState = remoteUserData.content ? JSON.parse(remoteUserData.content) : null;
163 164 165

		const localGloablState = await this.getLocalGlobalState();

S
Sandeep Somavarapu 已提交
166 167 168 169 170 171
		if (remoteGlobalState) {
			this.logService.trace('UI State: Merging remote ui state with local ui state...');
		} else {
			this.logService.trace('UI State: Remote ui state does not exist. Synchronizing ui state for the first time.');
		}

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

174
		return { local, remote, remoteUserData, lastSyncUserData };
175 176
	}

177
	private async apply({ local, remote, remoteUserData, lastSyncUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
S
Sandeep Somavarapu 已提交
178 179 180 181 182 183 184

		const hasChanges = local || remote;

		if (!hasChanges) {
			this.logService.trace('UI State: No changes found during synchronizing ui state.');
		}

185 186
		if (local) {
			// update local
S
Sandeep Somavarapu 已提交
187
			this.logService.trace('UI State: Updating local ui state...');
188
			await this.writeLocalGlobalState(local);
S
Sandeep Somavarapu 已提交
189
			this.logService.info('UI State: Updated local ui state');
190 191 192 193
		}

		if (remote) {
			// update remote
S
Sandeep Somavarapu 已提交
194
			this.logService.trace('UI State: Updating remote ui state...');
S
Sandeep Somavarapu 已提交
195 196
			const content = JSON.stringify(remote);
			const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
S
Sandeep Somavarapu 已提交
197
			this.logService.info('UI State: Updated remote ui state');
S
Sandeep Somavarapu 已提交
198
			remoteUserData = { ref, content };
199 200
		}

S
Sandeep Somavarapu 已提交
201
		if (lastSyncUserData?.ref !== remoteUserData.ref) {
202
			// update last sync
S
Sandeep Somavarapu 已提交
203
			this.logService.trace('UI State: Updating last synchronized ui state...');
S
Sandeep Somavarapu 已提交
204
			await this.updateLastSyncUserData(remoteUserData);
S
Sandeep Somavarapu 已提交
205
			this.logService.info('UI State: Updated last synchronized ui state');
206 207 208 209 210 211 212 213
		}
	}

	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 已提交
214
			const argvValue: IStringDictionary<any> = parse(content.value.toString());
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
			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));
		}
	}

}