globalStateSync.ts 13.4 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.
 *--------------------------------------------------------------------------------------------*/

6
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, IStorageValue } 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 { parse } from 'vs/base/common/json';
S
Sandeep Somavarapu 已提交
16
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
17
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
S
Sandeep Somavarapu 已提交
18
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
19
import { URI } from 'vs/base/common/uri';
20 21
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
22

S
Sandeep Somavarapu 已提交
23
const argvStoragePrefx = 'globalState.argv.';
24 25
const argvProperties: string[] = ['locale'];

26
interface ISyncPreviewResult {
S
Sandeep Somavarapu 已提交
27 28
	readonly local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
	readonly remote: IStringDictionary<IStorageValue> | null;
29
	readonly localUserData: IGlobalState;
S
Sandeep Somavarapu 已提交
30 31
	readonly remoteUserData: IRemoteUserData;
	readonly lastSyncUserData: IRemoteUserData | null;
32 33
}

34
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
35

S
Sandeep Somavarapu 已提交
36
	protected readonly version: number = 1;
37

38
	constructor(
39
		@IFileService fileService: IFileService,
S
Sandeep Somavarapu 已提交
40
		@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
S
Sandeep Somavarapu 已提交
41
		@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
42
		@IUserDataSyncLogService logService: IUserDataSyncLogService,
43
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
S
Sandeep Somavarapu 已提交
44
		@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
45
		@ITelemetryService telemetryService: ITelemetryService,
S
Sandeep Somavarapu 已提交
46
		@IConfigurationService configurationService: IConfigurationService,
47 48
		@IStorageService private readonly storageService: IStorageService,
		@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
49
	) {
S
Sandeep Somavarapu 已提交
50
		super(SyncResource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
51
		this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
52
		this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
53
		this._register(Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key))(() => this._onDidChangeLocal.fire()));
54 55
	}

56
	async pull(): Promise<void> {
S
Sandeep Somavarapu 已提交
57
		if (!this.isEnabled()) {
S
Sandeep Somavarapu 已提交
58
			this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling ui state as it is disabled.`);
59 60 61 62 63 64
			return;
		}

		this.stop();

		try {
S
Sandeep Somavarapu 已提交
65
			this.logService.info(`${this.syncResourceLogLabel}: Started pulling ui state...`);
66 67
			this.setStatus(SyncStatus.Syncing);

68 69
			const lastSyncUserData = await this.getLastSyncUserData();
			const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
70

S
Sandeep Somavarapu 已提交
71
			if (remoteUserData.syncData !== null) {
72
				const localUserData = await this.getLocalGlobalState();
S
Sandeep Somavarapu 已提交
73 74 75
				const localGlobalState: IGlobalState = JSON.parse(remoteUserData.syncData.content);
				const { local, remote } = merge(localGlobalState.storage, null, null, this.storageKeysSyncRegistryService.storageKeys);
				await this.apply({ local, remote, remoteUserData, localUserData, lastSyncUserData });
76 77 78 79
			}

			// No remote exists to pull
			else {
S
Sandeep Somavarapu 已提交
80
				this.logService.info(`${this.syncResourceLogLabel}: Remote UI state does not exist.`);
81 82
			}

S
Sandeep Somavarapu 已提交
83
			this.logService.info(`${this.syncResourceLogLabel}: Finished pulling UI state.`);
84 85 86 87 88 89
		} finally {
			this.setStatus(SyncStatus.Idle);
		}
	}

	async push(): Promise<void> {
S
Sandeep Somavarapu 已提交
90
		if (!this.isEnabled()) {
S
Sandeep Somavarapu 已提交
91
			this.logService.info(`${this.syncResourceLogLabel}: Skipped pushing UI State as it is disabled.`);
92 93 94 95 96 97
			return;
		}

		this.stop();

		try {
S
Sandeep Somavarapu 已提交
98
			this.logService.info(`${this.syncResourceLogLabel}: Started pushing UI State...`);
99 100
			this.setStatus(SyncStatus.Syncing);

101
			const localUserData = await this.getLocalGlobalState();
102 103
			const lastSyncUserData = await this.getLastSyncUserData();
			const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
S
Sandeep Somavarapu 已提交
104
			await this.apply({ local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData }, true);
105

S
Sandeep Somavarapu 已提交
106
			this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`);
107 108 109 110 111 112
		} finally {
			this.setStatus(SyncStatus.Idle);
		}

	}

S
Sandeep Somavarapu 已提交
113 114
	async stop(): Promise<void> { }

115 116 117
	async getRemoteContent(ref?: string, fragment?: string): Promise<string | null> {
		let content = await super.getRemoteContent(ref);
		if (content !== null && fragment) {
118 119 120 121 122 123 124 125 126
			return this.getFragment(content, fragment);
		}
		return content;
	}

	async getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null> {
		let content = await super.getLocalBackupContent(ref);
		if (content !== null && fragment) {
			return this.getFragment(content, fragment);
127 128 129 130
		}
		return content;
	}

131 132 133 134 135 136 137 138 139 140 141
	private getFragment(content: string, fragment: string): string | null {
		const syncData = this.parseSyncData(content);
		if (syncData) {
			switch (fragment) {
				case 'globalState':
					return syncData.content;
			}
		}
		return null;
	}

142
	async acceptConflict(conflict: URI, content: string): Promise<void> {
S
Sandeep Somavarapu 已提交
143
		throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
S
Sandeep Somavarapu 已提交
144
	}
145

146 147
	async hasLocalData(): Promise<boolean> {
		try {
S
Sandeep Somavarapu 已提交
148 149
			const { storage } = await this.getLocalGlobalState();
			if (Object.keys(storage).length > 1 || storage[`${argvStoragePrefx}.locale`]?.value !== 'en') {
150 151 152 153 154 155 156 157
				return true;
			}
		} catch (error) {
			/* ignore error */
		}
		return false;
	}

158 159 160 161
	protected async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<SyncStatus> {
		const result = await this.getPreview(remoteUserData, lastSyncUserData);
		await this.apply(result);
		return SyncStatus.Idle;
162 163
	}

S
Sandeep Somavarapu 已提交
164
	private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncPreviewResult> {
S
Sandeep Somavarapu 已提交
165
		const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
S
Sandeep Somavarapu 已提交
166
		const lastSyncGlobalState: IGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
167 168 169

		const localGloablState = await this.getLocalGlobalState();

S
Sandeep Somavarapu 已提交
170
		if (remoteGlobalState) {
S
Sandeep Somavarapu 已提交
171
			this.logService.trace(`${this.syncResourceLogLabel}: Merging remote ui state with local ui state...`);
S
Sandeep Somavarapu 已提交
172
		} else {
S
Sandeep Somavarapu 已提交
173
			this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`);
S
Sandeep Somavarapu 已提交
174 175
		}

S
Sandeep Somavarapu 已提交
176
		const { local, remote } = merge(localGloablState.storage, remoteGlobalState.storage, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.storageKeysSyncRegistryService.storageKeys);
177

178
		return { local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData };
179 180
	}

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

S
Sandeep Somavarapu 已提交
183 184
		const hasLocalChanged = Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0;
		const hasRemoteChanged = remote !== null;
S
Sandeep Somavarapu 已提交
185

S
Sandeep Somavarapu 已提交
186
		if (!hasLocalChanged && !hasRemoteChanged) {
S
Sandeep Somavarapu 已提交
187
			this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`);
S
Sandeep Somavarapu 已提交
188 189
		}

S
Sandeep Somavarapu 已提交
190
		if (hasLocalChanged) {
191
			// update local
S
Sandeep Somavarapu 已提交
192
			this.logService.trace(`${this.syncResourceLogLabel}: Updating local ui state...`);
S
Sandeep Somavarapu 已提交
193
			await this.backupLocal(JSON.stringify(localUserData));
194
			await this.writeLocalGlobalState(local);
S
Sandeep Somavarapu 已提交
195
			this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`);
196 197
		}

S
Sandeep Somavarapu 已提交
198
		if (hasRemoteChanged) {
199
			// update remote
S
Sandeep Somavarapu 已提交
200
			this.logService.trace(`${this.syncResourceLogLabel}: Updating remote ui state...`);
S
Sandeep Somavarapu 已提交
201
			const content = JSON.stringify(<IGlobalState>{ storage: remote });
S
Sandeep Somavarapu 已提交
202
			remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
S
Sandeep Somavarapu 已提交
203
			this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`);
204 205
		}

S
Sandeep Somavarapu 已提交
206
		if (lastSyncUserData?.ref !== remoteUserData.ref) {
207
			// update last sync
S
Sandeep Somavarapu 已提交
208
			this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`);
S
Sandeep Somavarapu 已提交
209
			await this.updateLastSyncUserData(remoteUserData);
S
Sandeep Somavarapu 已提交
210
			this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`);
211 212 213 214
		}
	}

	private async getLocalGlobalState(): Promise<IGlobalState> {
215
		const storage: IStringDictionary<IStorageValue> = {};
S
Sandeep Somavarapu 已提交
216 217 218 219
		const argvContent: string = await this.getLocalArgvContent();
		const argvValue: IStringDictionary<any> = parse(argvContent);
		for (const argvProperty of argvProperties) {
			if (argvValue[argvProperty] !== undefined) {
S
Sandeep Somavarapu 已提交
220
				storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] };
S
Sandeep Somavarapu 已提交
221 222
			}
		}
223 224 225 226 227 228
		for (const { key, version } of this.storageKeysSyncRegistryService.storageKeys) {
			const value = this.storageService.get(key, StorageScope.GLOBAL);
			if (value) {
				storage[key] = { version, value };
			}
		}
S
Sandeep Somavarapu 已提交
229
		return { storage };
S
Sandeep Somavarapu 已提交
230 231 232
	}

	private async getLocalArgvContent(): Promise<string> {
233 234
		try {
			const content = await this.fileService.readFile(this.environmentService.argvResource);
S
Sandeep Somavarapu 已提交
235
			return content.value.toString();
236
		} catch (error) { }
S
Sandeep Somavarapu 已提交
237
		return '{}';
238 239
	}

S
Sandeep Somavarapu 已提交
240 241
	private async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary<IStorageValue>, updated: IStringDictionary<IStorageValue>, removed: string[] }): Promise<void> {
		const argv: IStringDictionary<any> = {};
242
		const updatedStorage: IStringDictionary<any> = {};
S
Sandeep Somavarapu 已提交
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
		const handleUpdatedStorage = (storage: IStringDictionary<IStorageValue>): void => {
			for (const key of Object.keys(storage)) {
				if (key.startsWith(argvStoragePrefx)) {
					argv[key.substring(argvStoragePrefx.length)] = storage[key].value;
					continue;
				}
				const { version, value } = storage[key];
				const storageKey = this.storageKeysSyncRegistryService.storageKeys.filter(storageKey => storageKey.key === key)[0];
				if (!storageKey) {
					this.logService.info(`${this.syncResourceLogLabel}: Skipped updating ${key} in storage. It is not registered to sync.`);
					continue;
				}
				if (storageKey.version !== version) {
					this.logService.info(`${this.syncResourceLogLabel}: Skipped updating ${key} in storage. Local version '${storageKey.version}' and remote version '${version} are not same.`);
					continue;
				}
				if (String(value) !== String(this.storageService.get(key, StorageScope.GLOBAL))) {
					updatedStorage[key] = value;
				}
			}
		};
		handleUpdatedStorage(added);
		handleUpdatedStorage(updated);
		for (const key of removed) {
			if (key.startsWith(argvStoragePrefx)) {
				argv[key.substring(argvStoragePrefx.length)] = undefined;
				continue;
			}
271
			const storageKey = this.storageKeysSyncRegistryService.storageKeys.filter(storageKey => storageKey.key === key)[0];
S
Sandeep Somavarapu 已提交
272 273 274 275 276 277
			if (!storageKey) {
				this.logService.info(`${this.syncResourceLogLabel}: Skipped updating ${key} in storage. It is not registered to sync.`);
				continue;
			}
			if (this.storageService.get(key, StorageScope.GLOBAL) !== undefined) {
				updatedStorage[key] = undefined;
278 279
			}
		}
S
Sandeep Somavarapu 已提交
280 281 282 283 284
		if (Object.keys(argv).length) {
			this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`);
			await this.updateArgv(argv);
			this.logService.info(`${this.syncResourceLogLabel}: Updated locale`);
		}
285 286 287 288 289 290 291
		const updatedStorageKeys: string[] = Object.keys(updatedStorage);
		if (updatedStorageKeys.length) {
			this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`);
			for (const key of Object.keys(updatedStorage)) {
				this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL);
			}
			this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, Object.keys(updatedStorage));
292 293 294
		}
	}

S
Sandeep Somavarapu 已提交
295 296 297 298 299 300 301 302 303 304 305 306 307
	private async updateArgv(argv: IStringDictionary<any>): Promise<void> {
		const argvContent = await this.getLocalArgvContent();
		let content = argvContent;
		for (const argvProperty of Object.keys(argv)) {
			content = edit(content, [argvProperty], argv[argvProperty], {});
		}
		if (argvContent !== content) {
			this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`);
			await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
			this.logService.info(`${this.syncResourceLogLabel}: Updated locale.`);
		}
	}

308
}