src/vs/platform/userDataSync/common/globalStateSync.ts

537 LOC · 353 covered · 184 uncovered · 69 ranges · 585 concepts · 14 introducers · 348 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- abstractSynchronizer.ts ×49
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { VSBuffer } from '../../../base/common/buffer.js';
7 > import { CancellationToken } from '../../../base/common/cancellation.js';
8 > import { IStringDictionary } from '../../../base/common/collections.js';
9 > import { getErrorMessage } from '../../../base/common/errors.js';
10 > import { Event } from '../../../base/common/event.js';
11 > import { parse } from '../../../base/common/json.js';
12 > import { toFormattedString } from '../../../base/common/jsonFormatter.js';
13 > import { isWeb } from '../../../base/common/platform.js';
14 > import { URI } from '../../../base/common/uri.js';
15 > import { generateUuid } from '../../../base/common/uuid.js';
16 > import { IHeaders } from '../../../base/parts/request/common/request.js';
17 > import { IConfigurationService } from '../../configuration/common/configuration.js';
18 > import { IEnvironmentService } from '../../environment/common/environment.js';
19 > import { IFileService } from '../../files/common/files.js';
20 > import { ILogService } from '../../log/common/log.js';
21 > import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
22 > import { IStorageEntry, IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
23 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
24 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
25 > import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAcceptResult, IMergeResult, IResourcePreview, isSyncData } from './abstractSynchronizer.js';
26 > import { edit } from './content.js';
27 > import { merge } from './globalStateMerge.js';
28 > import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, IUserData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, SYNC_SERVICE_URL_TYPE, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreType, USER_DATA_SYNC_SCHEME } from './userDataSync.js';
29 > import { UserDataSyncStoreClient } from './userDataSyncStoreService.js';
30 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
31 > import { IUserDataProfileStorageService } from '../../userDataProfile/common/userDataProfileStorageService.js';
32 > import { IInstantiationService } from '../../instantiation/common/instantiation.js';
33 >
34 > const argvStoragePrefx = 'globalState.argv.';
35 > const argvProperties: string[] = ['locale'];
36 >
37 > type StorageKeys = { machine: string[]; user: string[]; unregistered: string[] };
38 >
39 > interface IGlobalStateResourceMergeResult extends IAcceptResult {
40 > readonly local: { added: IStringDictionary<IStorageValue>; removed: string[]; updated: IStringDictionary<IStorageValue> };
41 > readonly remote: { added: string[]; removed: string[]; updated: string[]; all: IStringDictionary<IStorageValue> | null };
42 > }
43 >
44 > interface IGlobalStateResourcePreview extends IResourcePreview {
45 > readonly localUserData: IGlobalState;
46 > readonly previewResult: IGlobalStateResourceMergeResult;
47 > readonly storageKeys: StorageKeys;
48 > }
49 >
50 > export function stringify(globalState: IGlobalState, format: boolean): string {
51 > const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : []; globalStateSync.ts ×15
52 > const storage: IStringDictionary<IStorageValue> = {};
53 > storageKeys.forEach(key => storage[key] = globalState.storage[key]);
54 > globalState.storage = storage;
55 > return format ? toFormattedString(globalState, {}) : JSON.stringify(globalState);
56 > }
58 > const GLOBAL_STATE_DATA_VERSION = 1;
59 >
60 > /**
61 > * Synchronises global state that includes
62 > * - Global storage with user scope
63 > * - Locale from argv properties
64 > *
65 > * Global storage is synced without checking version just like other resources (settings, keybindings).
66 > * If there is a change in format of the value of a storage key which requires migration then
67 > * Owner of that key should remove that key from user scope and replace that with new user scoped key.
68 > */
69 > export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
70 >
71 > protected readonly version: number = GLOBAL_STATE_DATA_VERSION;
72 > private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'globalState.json');
73 > private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' });
74 > private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
75 > private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
76 > private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
77 >
78 > private readonly localGlobalStateProvider: LocalGlobalStateProvider;
79 >
80 > constructor(
81 > profile: IUserDataProfile, userDataSyncService.ts ×19
82 > collection: string | undefined,
83 > @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService,
84 > @IFileService fileService: IFileService,
85 > @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
86 > @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService,
87 > @IUserDataSyncLogService logService: IUserDataSyncLogService,
88 > @IEnvironmentService environmentService: IEnvironmentService,
89 > @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
90 > @ITelemetryService telemetryService: ITelemetryService,
91 > @IConfigurationService configurationService: IConfigurationService,
92 > @IStorageService storageService: IStorageService,
93 > @IUriIdentityService uriIdentityService: IUriIdentityService,
94 > @IInstantiationService instantiationService: IInstantiationService,
95 > ) {
96 > super({ syncResource: SyncResource.GlobalState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);
97 > this.localGlobalStateProvider = instantiationService.createInstance(LocalGlobalStateProvider);
98 > this._register(fileService.watch(this.extUri.dirname(this.environmentService.argvResource)));
99 > this._register(
100 > Event.any(
101 > /* Locale change */
102 > Event.filter(fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)),
103 > Event.filter(userDataProfileStorageService.onDidChange, e => {
104 /* StorageTarget has changed in profile storage */
105 if (e.targetChanges.some(profile => this.syncResource.profile.id === profile.id)) {
106 return true;
107 }
108 /* User storage data has changed in profile storage */
109 if (e.valueChanges.some(({ profile, changes }) => this.syncResource.profile.id === profile.id && changes.some(change => change.target === StorageTarget.USER))) {
110 return true;
111 }
112 return false;
114 > )((() => this.triggerLocalChange()))
115 > );
116 > }
118 > protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean): Promise<IGlobalStateResourcePreview[]> {
119 > const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; globalStateSync.ts ×15
120 >
121 > // Use remote data as last sync data if last sync data does not exist and remote data is from same machine
122 > lastSyncUserData = lastSyncUserData === null && isRemoteDataFromCurrentMachine ? remoteUserData : lastSyncUserData;
123 > const lastSyncGlobalState: IGlobalState | null = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
124 >
125 > const localGlobalState = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile);
126 >
127 > if (remoteGlobalState) {
128 > this.logService.trace(`${this.syncResourceLogLabel}: Merging remote ui state with local ui state...`); globalStateSync.ts ×1
129 > } else { globalStateSync.ts ×15
130 > this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`);
131 > }
132 >
133 > const storageKeys = await this.getStorageKeys(lastSyncGlobalState);
134 > const { local, remote } = merge(localGlobalState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, storageKeys, this.logService);
135 > const previewResult: IGlobalStateResourceMergeResult = {
136 > content: null,
137 > local,
138 > remote,
139 > localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None,
140 > remoteChange: remote.all !== null ? Change.Modified : Change.None,
141 > };
142 >
143 > const localContent = stringify(localGlobalState, false);
144 > return [{
145 > baseResource: this.baseResource,
146 > baseContent: lastSyncGlobalState ? stringify(lastSyncGlobalState, false) : localContent,
147 > localResource: this.localResource,
148 > localContent,
149 > localUserData: localGlobalState,
150 > remoteResource: this.remoteResource,
151 > remoteContent: remoteGlobalState ? stringify(remoteGlobalState, false) : null,
152 > previewResource: this.previewResource,
153 > previewResult,
154 > localChange: previewResult.localChange,
155 > remoteChange: previewResult.remoteChange,
156 > acceptedResource: this.acceptedResource,
157 > storageKeys
158 > }];
159 > }
161 > protected async hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean> {
162 > const lastSyncGlobalState: IGlobalState | null = lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null; globalStateSync.ts ×2
163 > if (lastSyncGlobalState === null) {
164 return true;
165 }
166 > const localGlobalState = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile); globalStateSync.ts ×2
167 > const storageKeys = await this.getStorageKeys(lastSyncGlobalState);
168 > const { remote } = merge(localGlobalState.storage, lastSyncGlobalState.storage, lastSyncGlobalState.storage, storageKeys, this.logService);
169 > return remote.all !== null;
170 > }
172 > protected async getMergeResult(resourcePreview: IGlobalStateResourcePreview, token: CancellationToken): Promise<IMergeResult> {
173 > return { ...resourcePreview.previewResult, hasConflicts: false }; globalStateSync.ts ×6
174 > }
176 > protected async getAcceptResult(resourcePreview: IGlobalStateResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IGlobalStateResourceMergeResult> {
178 > /* Accept local resource */
179 > if (this.extUri.isEqual(resource, this.localResource)) {
180 return this.acceptLocal(resourcePreview);
181 }
183 > /* Accept remote resource */
184 > if (this.extUri.isEqual(resource, this.remoteResource)) {
185 return this.acceptRemote(resourcePreview);
186 }
188 > /* Accept preview resource */
189 > if (this.extUri.isEqual(resource, this.previewResource)) {
190 > return resourcePreview.previewResult;
191 > }
192
193 throw new Error(`Invalid Resource: ${resource.toString()}`);
196 > private async acceptLocal(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
197 if (resourcePreview.remoteContent !== null) {
198 const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent);
199 const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, remoteGlobalState.storage, resourcePreview.storageKeys, this.logService);
200 return {
201 content: resourcePreview.remoteContent,
202 local,
203 remote,
204 localChange: Change.None,
205 remoteChange: remote.all !== null ? Change.Modified : Change.None,
206 };
207 } else {
208 return {
209 content: resourcePreview.localContent,
210 local: { added: {}, removed: [], updated: {} },
211 remote: { added: Object.keys(resourcePreview.localUserData.storage), removed: [], updated: [], all: resourcePreview.localUserData.storage },
212 localChange: Change.None,
213 remoteChange: Change.Modified,
214 };
215 }
216 }
218 > private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
219 if (resourcePreview.remoteContent !== null) {
220 const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent);
221 const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, resourcePreview.localUserData.storage, resourcePreview.storageKeys, this.logService);
222 return {
223 content: resourcePreview.remoteContent,
224 local,
225 remote,
226 localChange: Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0 ? Change.Modified : Change.None,
227 remoteChange: Change.None,
228 };
229 } else {
230 return {
231 content: resourcePreview.remoteContent,
232 local: { added: {}, removed: [], updated: {} },
233 remote: { added: [], removed: [], updated: [], all: null },
234 localChange: Change.None,
235 remoteChange: Change.None,
236 };
237 }
238 }
240 > protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise<void> {
241 > const { localUserData } = resourcePreviews[0][0]; globalStateSync.ts ×15
242 > const { local, remote, localChange, remoteChange } = resourcePreviews[0][1];
243 >
244 > if (localChange === Change.None && remoteChange === Change.None) {
245 > this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`); globalStateSync.ts ×1
246 > }
248 > if (localChange !== Change.None) {
249 > // update local globalStateSync.ts ×6
250 > this.logService.trace(`${this.syncResourceLogLabel}: Updating local ui state...`);
251 > await this.backupLocal(JSON.stringify(localUserData));
252 > await this.localGlobalStateProvider.writeLocalGlobalState(local, this.syncResource.profile);
253 > this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`);
254 > }
256 > if (remoteChange !== Change.None) {
257 > // update remote globalStateSync.ts ×6
258 > this.logService.trace(`${this.syncResourceLogLabel}: Updating remote ui state...`);
259 > const content = JSON.stringify({ storage: remote.all });
260 > remoteUserData = await this.updateRemoteUserData(content, force ? null : remoteUserData.ref);
261 > this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state.${remote.added.length ? ` Added: ${remote.added}.` : ''}${remote.updated.length ? ` Updated: ${remote.updated}.` : ''}${remote.removed.length ? ` Removed: ${remote.removed}.` : ''}`);
262 > }
264 > if (lastSyncUserData?.ref !== remoteUserData.ref) {
265 > // update last sync
266 > this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`);
267 > await this.updateLastSyncUserData(remoteUserData);
268 > this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`);
269 > }
270 > }
272 > async resolveContent(uri: URI): Promise<string | null> {
273 if (this.extUri.isEqual(this.remoteResource, uri)
274 || this.extUri.isEqual(this.baseResource, uri)
275 || this.extUri.isEqual(this.localResource, uri)
276 || this.extUri.isEqual(this.acceptedResource, uri)
277 ) {
278 const content = await this.resolvePreviewContent(uri);
279 return content ? stringify(JSON.parse(content), true) : content;
280 }
281 return null;
282 }
284 > async hasLocalData(): Promise<boolean> {
285 try {
286 const { storage } = await this.localGlobalStateProvider.getLocalGlobalState(this.syncResource.profile);
287 if (Object.keys(storage).length > 1 || storage[`${argvStoragePrefx}.locale`]?.value !== 'en') {
288 return true;
289 }
290 } catch (error) {
291 /* ignore error */
292 }
293 return false;
294 }
296 > private async getStorageKeys(lastSyncGlobalState: IGlobalState | null): Promise<StorageKeys> {
297 > const storageData = await this.userDataProfileStorageService.readStorageData(this.syncResource.profile); globalStateSync.ts ×15
298 > const user: string[] = [], machine: string[] = [];
299 > for (const [key, value] of storageData) {
300 > if (value.target === StorageTarget.USER) { globalStateSync.ts ×3
301 > user.push(key);
302 > } else if (value.target === StorageTarget.MACHINE) {
303 > machine.push(key); globalStateSync.ts ×1
304 > }
306 > const registered = [...user, ...machine]; globalStateSync.ts ×15
307 > const unregistered = lastSyncGlobalState?.storage ? Object.keys(lastSyncGlobalState.storage).filter(key => !key.startsWith(argvStoragePrefx) && !registered.includes(key) && storageData.get(key) !== undefined) : [];
308 >
309 > if (!isWeb) {
310 > // Following keys are synced only in web. Do not sync these keys in other platforms
311 > const keysSyncedOnlyInWeb = [...ALL_SYNC_RESOURCES.map(resource => getEnablementKey(resource)), SYNC_SERVICE_URL_TYPE];
312 > unregistered.push(...keysSyncedOnlyInWeb);
313 > machine.push(...keysSyncedOnlyInWeb);
314 > }
315 >
316 > return { user, machine, unregistered };
317 > }
319 >
320 > export class LocalGlobalStateProvider {
321 > constructor(
322 > @IFileService private readonly fileService: IFileService, userDataSyncService.ts ×19
323 > @IEnvironmentService private readonly environmentService: IEnvironmentService,
324 > @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService,
325 > @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService
326 > ) { }
328 > async getLocalGlobalState(profile: IUserDataProfile): Promise<IGlobalState> {
329 > const storage: IStringDictionary<IStorageValue> = {}; globalStateSync.ts ×15
330 > if (profile.isDefault) {
331 > const argvContent: string = await this.getLocalArgvContent();
332 > const argvValue: IStringDictionary<any> = parse(argvContent);
333 > for (const argvProperty of argvProperties) {
334 > if (argvValue[argvProperty] !== undefined) {
335 > storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] }; globalStateSync.ts ×2
336 > }
338 > }
339 > const storageData = await this.userDataProfileStorageService.readStorageData(profile);
340 > for (const [key, value] of storageData) {
341 > if (value.value && value.target === StorageTarget.USER) { globalStateSync.ts ×3
342 > storage[key] = { version: 1, value: value.value, scope: value.scope };
343 > }
344 > }
345 > return { storage }; globalStateSync.ts ×15
346 > }
348 > private async getLocalArgvContent(): Promise<string> {
350 > this.logService.debug('GlobalStateSync#getLocalArgvContent', this.environmentService.argvResource);
351 > const content = await this.fileService.readFile(this.environmentService.argvResource);
352 > this.logService.debug('GlobalStateSync#getLocalArgvContent - Resolved', this.environmentService.argvResource); globalStateSync.ts ×2
353 > return content.value.toString();
354 > } catch (error) { globalStateSync.ts ×15
355 > this.logService.debug(getErrorMessage(error)); globalStateSync.ts ×1
356 > }
357 > return '{}';
360 > async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary<IStorageValue>; updated: IStringDictionary<IStorageValue>; removed: string[] }, profile: IUserDataProfile): Promise<void> {
361 > const syncResourceLogLabel = getSyncResourceLogLabel(SyncResource.GlobalState, profile); globalStateSync.ts ×6
362 > const argv: IStringDictionary<any> = {};
363 > const updatedProfileStorage = new Map<string, string | undefined>();
364 > const updatedSharedStorage = profile.isDefault ? new Map<string, string | undefined>() : undefined;
365 > const storageData = await this.userDataProfileStorageService.readStorageData(profile);
366 > const handleUpdatedStorage = (keys: string[], storage?: IStringDictionary<IStorageValue>): void => {
367 > for (const key of keys) {
368 > if (key.startsWith(argvStoragePrefx)) {
369 > argv[key.substring(argvStoragePrefx.length)] = storage ? storage[key].value : undefined; globalStateSync.ts ×2
370 > continue;
371 > }
372 > if (storage) { globalStateSync.ts ×2
373 > const storageValue = storage[key];
374 > if (storageValue.value !== storageData.get(key)?.value) {
375 > const targetMap = updatedSharedStorage && storageValue.scope === StorageScope.APPLICATION_SHARED ? updatedSharedStorage : updatedProfileStorage;
376 > targetMap.set(key, storageValue.value);
377 > }
378 > } else {
379 if (storageData.get(key) !== undefined) {
380 const targetMap = updatedSharedStorage && storageData.get(key)?.scope === StorageScope.APPLICATION_SHARED ? updatedSharedStorage : updatedProfileStorage;
381 targetMap.set(key, undefined);
382 }
383 }
385 > };
386 > handleUpdatedStorage(Object.keys(added), added);
387 > handleUpdatedStorage(Object.keys(updated), updated);
388 > handleUpdatedStorage(removed);
389 >
390 > if (Object.keys(argv).length) {
391 > this.logService.trace(`${syncResourceLogLabel}: Updating locale...`); globalStateSync.ts ×2
392 > const argvContent = await this.getLocalArgvContent();
393 > let content = argvContent;
394 > for (const argvProperty of Object.keys(argv)) {
395 > content = edit(content, [argvProperty], argv[argvProperty], {});
396 > }
397 > if (argvContent !== content) {
398 > this.logService.trace(`${syncResourceLogLabel}: Updating locale...`);
399 > await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
400 > this.logService.info(`${syncResourceLogLabel}: Updated locale.`);
401 > }
402 > this.logService.info(`${syncResourceLogLabel}: Updated locale`);
403 > }
405 > if (updatedProfileStorage.size) {
406 > this.logService.trace(`${syncResourceLogLabel}: Updating global state...`); globalStateSync.ts ×2
407 > await this.userDataProfileStorageService.updateStorageData(profile, updatedProfileStorage, StorageTarget.USER);
408 > this.logService.info(`${syncResourceLogLabel}: Updated global state`, [...updatedProfileStorage.keys()]);
409 > }
411 > if (updatedSharedStorage?.size) {
412 this.logService.trace(`${syncResourceLogLabel}: Updating application shared state...`);
413 await this.userDataProfileStorageService.updateStorageData(profile, updatedSharedStorage, StorageTarget.USER, StorageScope.APPLICATION_SHARED);
414 this.logService.info(`${syncResourceLogLabel}: Updated application shared state`, [...updatedSharedStorage.keys()]);
415 }
418 >
419 > export class GlobalStateInitializer extends AbstractInitializer {
420 >
421 > constructor(
422 @IStorageService storageService: IStorageService,
423 @IFileService fileService: IFileService,
424 @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
425 @IEnvironmentService environmentService: IEnvironmentService,
426 @IUserDataSyncLogService logService: IUserDataSyncLogService,
427 @IUriIdentityService uriIdentityService: IUriIdentityService,
428 ) {
429 super(SyncResource.GlobalState, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService);
430 }
432 > protected async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
433 const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
434 if (!remoteGlobalState) {
435 this.logService.info('Skipping initializing global state because remote global state does not exist.');
436 return;
437 }
438
439 const argv: IStringDictionary<any> = {};
440 const isDefaultProfile = this.storageService.hasScope(this.userDataProfilesService.defaultProfile);
441 const storage: IStringDictionary<any> = {};
442 for (const key of Object.keys(remoteGlobalState.storage)) {
443 if (key.startsWith(argvStoragePrefx)) {
444 argv[key.substring(argvStoragePrefx.length)] = remoteGlobalState.storage[key].value;
445 } else {
446 const isSharedScope = remoteGlobalState.storage[key].scope === StorageScope.APPLICATION_SHARED;
447 if (isSharedScope && !isDefaultProfile) {
448 continue; // Skip APPLICATION_SHARED keys for non-default profiles
449 }
450 const scope = isSharedScope ? StorageScope.APPLICATION_SHARED : StorageScope.PROFILE;
451 if (this.storageService.get(key, scope) === undefined) {
452 storage[key] = { value: remoteGlobalState.storage[key].value, scope };
453 }
454 }
455 }
456
457 if (Object.keys(argv).length) {
458 let content = '{}';
459 try {
460 const fileContent = await this.fileService.readFile(this.environmentService.argvResource);
461 content = fileContent.value.toString();
462 } catch (error) { }
463 for (const argvProperty of Object.keys(argv)) {
464 content = edit(content, [argvProperty], argv[argvProperty], {});
465 }
466 await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
467 }
468
469 if (Object.keys(storage).length) {
470 const storageEntries: Array<IStorageEntry> = [];
471 for (const key of Object.keys(storage)) {
472 storageEntries.push({ key, value: storage[key].value, scope: storage[key].scope, target: StorageTarget.USER });
473 }
474 this.storageService.storeAll(storageEntries, true);
475 }
476 }
478 > }
479 >
480 > export class UserDataSyncStoreTypeSynchronizer {
481 >
482 > constructor(
483 private readonly userDataSyncStoreClient: UserDataSyncStoreClient,
484 @IStorageService private readonly storageService: IStorageService,
485 @IEnvironmentService private readonly environmentService: IEnvironmentService,
486 @IFileService private readonly fileService: IFileService,
487 @ILogService private readonly logService: ILogService,
488 ) {
489 }
491 > getSyncStoreType(userData: IUserData): UserDataSyncStoreType | undefined {
492 const remoteGlobalState = this.parseGlobalState(userData);
493 return remoteGlobalState?.storage[SYNC_SERVICE_URL_TYPE]?.value as UserDataSyncStoreType;
494 }
496 > async sync(userDataSyncStoreType: UserDataSyncStoreType): Promise<void> {
497 const syncHeaders = createSyncHeaders(generateUuid());
498 try {
499 return await this.doSync(userDataSyncStoreType, syncHeaders);
500 } catch (e) {
501 if (e instanceof UserDataSyncError) {
502 switch (e.code) {
503 case UserDataSyncErrorCode.PreconditionFailed:
504 this.logService.info(`Failed to synchronize UserDataSyncStoreType as there is a new remote version available. Synchronizing again...`);
505 return this.doSync(userDataSyncStoreType, syncHeaders);
506 }
507 }
508 throw e;
509 }
510 }
512 > private async doSync(userDataSyncStoreType: UserDataSyncStoreType, syncHeaders: IHeaders): Promise<void> {
513 // Read the global state from remote
514 const globalStateUserData = await this.userDataSyncStoreClient.readResource(SyncResource.GlobalState, null, undefined, syncHeaders);
515 const remoteGlobalState = this.parseGlobalState(globalStateUserData) || { storage: {} };
516
517 // Update the sync store type
518 remoteGlobalState.storage[SYNC_SERVICE_URL_TYPE] = { value: userDataSyncStoreType, version: GLOBAL_STATE_DATA_VERSION };
519
520 // Write the global state to remote
521 const machineId = await getServiceMachineId(this.environmentService, this.fileService, this.storageService);
522 const syncDataToUpdate: ISyncData = { version: GLOBAL_STATE_DATA_VERSION, machineId, content: stringify(remoteGlobalState, false) };
523 await this.userDataSyncStoreClient.writeResource(SyncResource.GlobalState, JSON.stringify(syncDataToUpdate), globalStateUserData.ref, undefined, syncHeaders);
524 }
526 > private parseGlobalState({ content }: IUserData): IGlobalState | null {
527 if (!content) {
528 return null;
529 }
530 const syncData = JSON.parse(content);
531 if (isSyncData(syncData)) {
532 return syncData ? JSON.parse(syncData.content) : null;
533 }
534 throw new Error('Invalid remote data');
535 }
537 > }