globalStateSync.ts 12.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 { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, 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';
10
import { dirname, joinPath, basename } 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 { format } from 'vs/base/common/jsonFormatter';
import { applyEdits } from 'vs/base/common/jsonEdit';
S
Sandeep Somavarapu 已提交
22 23
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
24

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

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

36
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
37

S
Sandeep Somavarapu 已提交
38
	protected readonly version: number = 1;
39

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

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

		this.stop();

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

70 71
			const lastSyncUserData = await this.getLastSyncUserData();
			const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
72

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

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

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

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

		this.stop();

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

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

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

	}

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

117 118
	async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> {
		return [{ resource: joinPath(uri, 'globalState.json') }];
119 120
	}

121 122 123 124
	async resolveContent(uri: URI): Promise<string | null> {
		let content = await super.resolveContent(uri);
		if (content) {
			return content;
125
		}
126 127 128 129 130 131 132 133 134
		content = await super.resolveContent(dirname(uri));
		if (content) {
			const syncData = this.parseSyncData(content);
			if (syncData) {
				switch (basename(uri)) {
					case 'globalState.json':
						const edits = format(syncData.content, undefined, {});
						return applyEdits(syncData.content, edits);
				}
135 136 137 138 139
			}
		}
		return null;
	}

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

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

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

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

		const localGloablState = await this.getLocalGlobalState();

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

174
		const { local, remote } = merge(localGloablState.storage, remoteGlobalState.storage, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.storageKeysSyncRegistryService.storageKeys, this.logService);
175

176
		return { local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData };
177 178
	}

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

S
Sandeep Somavarapu 已提交
181 182
		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 已提交
183

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

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

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

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

	private async getLocalGlobalState(): Promise<IGlobalState> {
213
		const storage: IStringDictionary<IStorageValue> = {};
S
Sandeep Somavarapu 已提交
214 215 216 217
		const argvContent: string = await this.getLocalArgvContent();
		const argvValue: IStringDictionary<any> = parse(argvContent);
		for (const argvProperty of argvProperties) {
			if (argvValue[argvProperty] !== undefined) {
S
Sandeep Somavarapu 已提交
218
				storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] };
S
Sandeep Somavarapu 已提交
219 220
			}
		}
221 222 223 224 225 226
		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 已提交
227
		return { storage };
S
Sandeep Somavarapu 已提交
228 229 230
	}

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

S
Sandeep Somavarapu 已提交
238 239
	private async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary<IStorageValue>, updated: IStringDictionary<IStorageValue>, removed: string[] }): Promise<void> {
		const argv: IStringDictionary<any> = {};
240
		const updatedStorage: IStringDictionary<any> = {};
241 242
		const handleUpdatedStorage = (keys: string[], storage?: IStringDictionary<IStorageValue>): void => {
			for (const key of keys) {
S
Sandeep Somavarapu 已提交
243
				if (key.startsWith(argvStoragePrefx)) {
244
					argv[key.substring(argvStoragePrefx.length)] = storage ? storage[key].value : undefined;
S
Sandeep Somavarapu 已提交
245 246
					continue;
				}
247 248 249 250 251 252 253 254 255
				if (storage) {
					const storageValue = storage[key];
					if (storageValue.value !== String(this.storageService.get(key, StorageScope.GLOBAL))) {
						updatedStorage[key] = storageValue.value;
					}
				} else {
					if (this.storageService.get(key, StorageScope.GLOBAL) !== undefined) {
						updatedStorage[key] = undefined;
					}
S
Sandeep Somavarapu 已提交
256 257 258
				}
			}
		};
259 260 261
		handleUpdatedStorage(Object.keys(added), added);
		handleUpdatedStorage(Object.keys(updated), updated);
		handleUpdatedStorage(removed);
S
Sandeep Somavarapu 已提交
262 263 264 265 266
		if (Object.keys(argv).length) {
			this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`);
			await this.updateArgv(argv);
			this.logService.info(`${this.syncResourceLogLabel}: Updated locale`);
		}
267 268 269 270 271 272 273
		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));
274 275 276
		}
	}

S
Sandeep Somavarapu 已提交
277 278 279 280 281 282 283 284 285 286 287 288 289
	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.`);
		}
	}

290
}