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

958 LOC · 788 covered · 170 uncovered · 219 ranges · 585 concepts · 79 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 { equals } from '../../../base/common/arrays.js';
7 > import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from '../../../base/common/async.js';
8 > import { VSBuffer } from '../../../base/common/buffer.js';
9 > import { CancellationToken } from '../../../base/common/cancellation.js';
10 > import { IStringDictionary } from '../../../base/common/collections.js';
11 > import { Emitter, Event } from '../../../base/common/event.js';
12 > import { parse, ParseError } from '../../../base/common/json.js';
13 > import { FormattingOptions } from '../../../base/common/jsonFormatter.js';
14 > import { Disposable } from '../../../base/common/lifecycle.js';
15 > import { IExtUri } from '../../../base/common/resources.js';
16 > import { uppercaseFirstLetter } from '../../../base/common/strings.js';
17 > import { isString, isUndefined } from '../../../base/common/types.js';
18 > import { URI } from '../../../base/common/uri.js';
19 > import { IHeaders } from '../../../base/parts/request/common/request.js';
20 > import { localize } from '../../../nls.js';
21 > import { IConfigurationService } from '../../configuration/common/configuration.js';
22 > import { IEnvironmentService } from '../../environment/common/environment.js';
23 > import { FileChangesEvent, FileOperationError, FileOperationResult, IFileContent, IFileService, toFileOperationResult } from '../../files/common/files.js';
24 > import { ILogService } from '../../log/common/log.js';
25 > import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
26 > import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
27 > import { ITelemetryService } from '../../telemetry/common/telemetry.js';
28 > import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js';
29 > import {
30 > Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData,
31 > IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLocalStoreService,
32 > IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService,
33 > IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode,
34 > USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, getPathSegments, IUserDataSyncResourceConflicts,
35 > IUserDataSyncResource, IUserDataSyncResourcePreview,
36 > NON_EXISTING_RESOURCE_REF,
37 > } from './userDataSync.js';
38 > import { IUserDataProfile, IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js';
39 >
40 > export function isRemoteUserData(thing: any): thing is IRemoteUserData {
42 > && (thing.ref !== undefined && typeof thing.ref === 'string' && thing.ref !== '')
43 > && (thing.syncData !== undefined && (thing.syncData === null || isSyncData(thing.syncData)))) {
44 > return true; abstractSynchronizer.ts ×5
45 > }
47 > return false;
48 > }
50 > export function isSyncData(thing: any): thing is ISyncData {
52 > && (thing.version !== undefined && typeof thing.version === 'number')
53 > && (thing.content !== undefined && typeof thing.content === 'string')) {
54 >
55 > // backward compatibility
56 > if (Object.keys(thing).length === 2) {
57 return true;
58 }
60 > if (Object.keys(thing).length === 3
61 > && (thing.machineId !== undefined && typeof thing.machineId === 'string')) {
62 > return true;
63 > }
64 > }
65
66 return false;
67 }
69 > export function getSyncResourceLogLabel(syncResource: SyncResource, profile: IUserDataProfile): string {
70 > return `${uppercaseFirstLetter(syncResource)}${profile.isDefault ? '' : ` (${profile.name})`}`; abstractSynchronizer.ts ×7
71 > }
73 > export interface IResourcePreview {
74 >
75 > readonly baseResource: URI;
76 > readonly baseContent: string | null;
77 >
78 > readonly remoteResource: URI;
79 > readonly remoteContent: string | null;
80 > readonly remoteChange: Change;
81 >
82 > readonly localResource: URI;
83 > readonly localContent: string | null;
84 > readonly localChange: Change;
85 >
86 > readonly previewResource: URI;
87 > readonly acceptedResource: URI;
88 > }
89 >
90 > export interface IAcceptResult {
91 > readonly content: string | null;
92 > readonly localChange: Change;
93 > readonly remoteChange: Change;
94 > }
95 >
96 > export interface IMergeResult extends IAcceptResult {
97 > readonly hasConflicts: boolean;
98 > }
99 >
100 > interface IEditableResourcePreview extends IBaseResourcePreview, IResourcePreview {
101 > localChange: Change;
102 > remoteChange: Change;
103 > mergeState: MergeState;
104 > acceptResult?: IAcceptResult;
105 > }
106 >
107 > export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
108 > readonly remoteUserData: IRemoteUserData;
109 > readonly lastSyncUserData: IRemoteUserData | null;
110 > readonly resourcePreviews: IEditableResourcePreview[];
111 > }
112 >
113 > interface ILastSyncUserDataState {
114 > readonly ref: string;
115 > readonly version: string | undefined;
116 > [key: string]: any;
117 > }
118 >
119 > export const enum SyncStrategy {
120 > Preview = 'preview', // Merge the local and remote data without applying.
121 > Merge = 'merge', // Merge the local and remote data and apply.
122 > PullOrPush = 'pull-push', // Pull the remote data or push the local data.
123 > }
124 >
125 > export abstract class AbstractSynchroniser extends Disposable implements IUserDataSynchroniser {
126 >
127 > private syncPreviewPromise: CancelablePromise<ISyncResourcePreview> | null = null;
128 >
129 > protected readonly syncFolder: URI;
130 > protected readonly syncPreviewFolder: URI;
131 > protected readonly extUri: IExtUri;
132 > protected readonly currentMachineIdPromise: Promise<string>;
133 >
134 > private _status: SyncStatus = SyncStatus.Idle;
135 > get status(): SyncStatus { return this._status; }
136 > private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
137 > readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
138 >
139 > private _conflicts: IBaseResourcePreview[] = [];
140 > get conflicts(): IUserDataSyncResourceConflicts { return { ...this.syncResource, conflicts: this._conflicts }; }
141 > private _onDidChangeConflicts = this._register(new Emitter<IUserDataSyncResourceConflicts>());
142 > readonly onDidChangeConflicts = this._onDidChangeConflicts.event;
143 >
144 > private readonly localChangeTriggerThrottler = this._register(new ThrottledDelayer<void>(50));
145 > private readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
146 > readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
147 >
148 > protected readonly lastSyncResource: URI;
149 > private readonly lastSyncUserDataStateKey: string;
150 > private hasSyncResourceStateVersionChanged: boolean = false;
151 > protected readonly syncResourceLogLabel: string;
152 >
153 > protected syncHeaders: IHeaders = {};
154 >
155 > readonly resource: SyncResource;
156 >
157 > constructor(
158 > readonly syncResource: IUserDataSyncResource, abstractSynchronizer.ts ×7
159 > readonly collection: string | undefined,
160 > @IFileService protected readonly fileService: IFileService,
161 > @IEnvironmentService protected readonly environmentService: IEnvironmentService,
162 > @IStorageService protected readonly storageService: IStorageService,
163 > @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService,
164 > @IUserDataSyncLocalStoreService protected readonly userDataSyncLocalStoreService: IUserDataSyncLocalStoreService,
165 > @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
166 > @ITelemetryService protected readonly telemetryService: ITelemetryService,
167 > @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
168 > @IConfigurationService protected readonly configurationService: IConfigurationService,
169 > @IUriIdentityService uriIdentityService: IUriIdentityService,
170 > ) {
171 > super();
172 > this.lastSyncUserDataStateKey = `${collection ? `${collection}.` : ''}${syncResource.syncResource}.lastSyncUserData`;
173 > this.resource = syncResource.syncResource;
174 > this.syncResourceLogLabel = getSyncResourceLogLabel(syncResource.syncResource, syncResource.profile);
175 > this.extUri = uriIdentityService.extUri;
176 > this.syncFolder = this.extUri.joinPath(environmentService.userDataSyncHome, ...getPathSegments(syncResource.profile.isDefault ? undefined : syncResource.profile.id, syncResource.syncResource));
177 > this.syncPreviewFolder = this.extUri.joinPath(this.syncFolder, PREVIEW_DIR_NAME);
178 > this.lastSyncResource = getLastSyncResourceUri(syncResource.profile.isDefault ? undefined : syncResource.profile.id, syncResource.syncResource, environmentService, this.extUri);
179 > this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
180 > }
182 > protected triggerLocalChange(): void {
183 > this.localChangeTriggerThrottler.trigger(() => this.doTriggerLocalChange()); abstractSynchronizer.ts ×1
184 > }
186 > protected async doTriggerLocalChange(): Promise<void> {
188 > // Sync again if current status is in conflicts
189 > if (this.status === SyncStatus.HasConflicts) {
190 this.logService.info(`${this.syncResourceLogLabel}: In conflicts state and local change detected. Syncing again...`);
191 const preview = await this.syncPreviewPromise!;
192 this.syncPreviewPromise = null;
193 const status = await this.performSync(preview.remoteUserData, preview.lastSyncUserData, SyncStrategy.Merge, this.getUserDataSyncConfiguration());
194 this.setStatus(status);
195 }
197 > // Check if local change causes remote change
198 > else {
199 > this.logService.trace(`${this.syncResourceLogLabel}: Checking for local changes...`);
200 > const lastSyncUserData = await this.getLastSyncUserData();
201 > const hasRemoteChanged = lastSyncUserData ? await this.hasRemoteChanged(lastSyncUserData) : true;
202 > if (hasRemoteChanged) {
203 > this._onDidChangeLocal.fire(); abstractSynchronizer.ts ×1
204 > }
206 > }
208 > protected setStatus(status: SyncStatus): void {
209 > if (this._status !== status) { abstractSynchronizer.ts ×5
210 > this._status = status;
211 > this._onDidChangStatus.fire(status);
212 > }
213 > }
215 > async sync(refOrUserData: string | IUserData | null, preview: boolean = false, userDataSyncConfiguration: IUserDataSyncConfiguration = this.getUserDataSyncConfiguration(), headers: IHeaders = {}): Promise<IUserDataSyncResourcePreview | null> {
217 > this.syncHeaders = { ...headers };
218 >
219 > if (this.status === SyncStatus.HasConflicts) {
220 > this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`); abstractSynchronizer.ts ×1
221 > return this.syncPreviewPromise;
222 > }
224 > if (this.status === SyncStatus.Syncing) {
225 > this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`); abstractSynchronizer.ts ×1
226 > return this.syncPreviewPromise;
227 > }
229 > this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`);
230 > this.setStatus(SyncStatus.Syncing);
231 >
232 > let status: SyncStatus = SyncStatus.Idle;
233 > try {
234 > const lastSyncUserData = await this.getLastSyncUserData();
235 > const remoteUserData = await this.getLatestRemoteUserData(refOrUserData, lastSyncUserData);
236 > status = await this.performSync(remoteUserData, lastSyncUserData, preview ? SyncStrategy.Preview : SyncStrategy.Merge, userDataSyncConfiguration);
237 > if (status === SyncStatus.HasConflicts) { abstractSynchronizer.ts ×3
238 > this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`); abstractSynchronizer.ts ×3
239 > } else if (status === SyncStatus.Idle) { abstractSynchronizer.ts ×3
240 > this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`); abstractSynchronizer.ts ×1
241 > }
242 > return this.syncPreviewPromise || null; abstractSynchronizer.ts ×3
243 > } finally { abstractSynchronizer.ts ×5
244 > this.setStatus(status);
245 > }
246 > } finally {
247 > this.syncHeaders = {};
248 > }
249 > }
251 > async apply(force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
253 > this.syncHeaders = { ...headers };
254 >
255 > const status = await this.doApply(force);
256 > this.setStatus(status);
257 >
258 > return this.syncPreviewPromise;
259 > } finally {
260 > this.syncHeaders = {};
261 > }
262 > }
264 > async replace(content: string): Promise<boolean> {
265 const syncData = this.parseSyncData(content);
266 if (!syncData) {
267 return false;
268 }
269
270 await this.stop();
271
272 try {
273 this.logService.trace(`${this.syncResourceLogLabel}: Started resetting ${this.resource.toLowerCase()}...`);
274 this.setStatus(SyncStatus.Syncing);
275 const lastSyncUserData = await this.getLastSyncUserData();
276 const remoteUserData = await this.getLatestRemoteUserData(null, lastSyncUserData);
277 const isRemoteDataFromCurrentMachine = await this.isRemoteDataFromCurrentMachine(remoteUserData);
278
279 /* use replace sync data */
280 const resourcePreviewResults = await this.generateSyncPreview({ ref: remoteUserData.ref, syncData }, lastSyncUserData, isRemoteDataFromCurrentMachine, this.getUserDataSyncConfiguration(), CancellationToken.None);
281
282 const resourcePreviews: [IResourcePreview, IAcceptResult][] = [];
283 for (const resourcePreviewResult of resourcePreviewResults) {
284 /* Accept remote resource */
285 const acceptResult: IAcceptResult = await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.remoteResource, undefined, CancellationToken.None);
286 /* compute remote change */
287 const { remoteChange } = await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.previewResource, resourcePreviewResult.remoteContent, CancellationToken.None);
288 resourcePreviews.push([resourcePreviewResult, { ...acceptResult, remoteChange: remoteChange !== Change.None ? remoteChange : Change.Modified }]);
289 }
290
291 await this.applyResult(remoteUserData, lastSyncUserData, resourcePreviews, false);
292 this.logService.info(`${this.syncResourceLogLabel}: Finished resetting ${this.resource.toLowerCase()}.`);
293 } finally {
294 this.setStatus(SyncStatus.Idle);
295 }
296
297 return true;
298 }
300 > private async isRemoteDataFromCurrentMachine(remoteUserData: IRemoteUserData): Promise<boolean> {
301 > const machineId = await this.currentMachineIdPromise; abstractSynchronizer.ts ×6
302 > return !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
303 > }
305 > protected async getLatestRemoteUserData(refOrLatestData: string | IUserData | null, lastSyncUserData: IRemoteUserData | null): Promise<IRemoteUserData> {
306 > if (refOrLatestData === null) { abstractSynchronizer.ts ×6
307 > return { ref: NON_EXISTING_RESOURCE_REF, syncData: null };
308 > }
310 > if (!isString(refOrLatestData)) {
311 return this.toRemoteUserData(refOrLatestData);
312 }
314 > // Last time synced resource and latest resource on server are same
315 > if (lastSyncUserData?.ref === refOrLatestData) { abstractSynchronizer.ts ×6
316 > return lastSyncUserData; abstractSynchronizer.ts ×1
317 > }
319 > return this.getRemoteUserData(lastSyncUserData);
322 > private async performSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, strategy: SyncStrategy, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise<SyncStatus> {
323 > if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { abstractSynchronizer.ts ×6
324 throw new UserDataSyncError(localize({ key: 'incompatible', comment: ['This is an error while syncing a resource that its local version is not compatible with its remote version.'] }, "Cannot sync {0} as its local version {1} is not compatible with its remote version {2}", this.resource, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.IncompatibleLocalContent, this.resource);
325 }
327 > try {
328 > return await this.doSync(remoteUserData, lastSyncUserData, strategy, userDataSyncConfiguration);
329 > } catch (e) {
330 > if (e instanceof UserDataSyncError) { abstractSynchronizer.ts ×2
331 > switch (e.code) { abstractSynchronizer.ts ×3
332 >
333 > case UserDataSyncErrorCode.LocalPreconditionFailed:
334 // Rejected as there is a new local version. Syncing again...
335 this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize ${this.syncResourceLogLabel} as there is a new local version available. Synchronizing again...`);
336 return this.performSync(remoteUserData, lastSyncUserData, strategy, userDataSyncConfiguration);
338 > case UserDataSyncErrorCode.Conflict:
339 > case UserDataSyncErrorCode.PreconditionFailed:
340 > // Rejected as there is a new remote version. Syncing again... abstractSynchronizer.ts ×1
341 > this.logService.info(`${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`);
342 >
343 > // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624
344 > remoteUserData = await this.getRemoteUserData(null);
345 >
346 > // Get the latest last sync user data. Because multiple parallel syncs (in Web) could share same last sync data
347 > // and one of them successfully updated remote and last sync state.
348 > lastSyncUserData = await this.getLastSyncUserData();
349 >
350 > return this.performSync(remoteUserData, lastSyncUserData, SyncStrategy.Merge, userDataSyncConfiguration);
352 > }
354 > }
357 > protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, strategy: SyncStrategy, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise<SyncStatus> {
359 >
360 > const isRemoteDataFromCurrentMachine = await this.isRemoteDataFromCurrentMachine(remoteUserData);
361 > const acceptRemote = !isRemoteDataFromCurrentMachine && lastSyncUserData === null && this.getStoredLastSyncUserDataStateContent() !== undefined;
362 > const merge = strategy === SyncStrategy.Preview || (strategy === SyncStrategy.Merge && !acceptRemote);
363 > const apply = strategy === SyncStrategy.Merge || strategy === SyncStrategy.PullOrPush;
364 >
365 > // generate or use existing preview
366 > if (!this.syncPreviewPromise) {
367 > this.syncPreviewPromise = createCancelablePromise(token => this.doGenerateSyncResourcePreview(remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine, merge, userDataSyncConfiguration, token));
368 > }
369 >
370 > let preview = await this.syncPreviewPromise;
372 > if (strategy === SyncStrategy.Merge && acceptRemote) { abstractSynchronizer.ts ×6
373 > this.logService.info(`${this.syncResourceLogLabel}: Accepting remote because it was synced before and the last sync data is not available.`); abstractSynchronizer.ts ×1
374 > for (const resourcePreview of preview.resourcePreviews) {
375 > preview = (await this.accept(resourcePreview.remoteResource)) || preview;
376 > }
377 > }
379 > else if (strategy === SyncStrategy.PullOrPush) {
380 for (const resourcePreview of preview.resourcePreviews) {
381 if (resourcePreview.mergeState === MergeState.Accepted) {
382 continue;
383 }
384 if (remoteUserData.ref === lastSyncUserData?.ref || isRemoteDataFromCurrentMachine) {
385 preview = (await this.accept(resourcePreview.localResource)) ?? preview;
386 } else {
387 preview = (await this.accept(resourcePreview.remoteResource)) ?? preview;
388 }
389 }
390 }
392 > this.updateConflicts(preview.resourcePreviews);
393 > if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
394 > return SyncStatus.HasConflicts; abstractSynchronizer.ts ×3
395 > }
397 > if (apply) {
398 > return await this.doApply(false); abstractSynchronizer.ts ×1
399 > }
401 > return SyncStatus.Syncing;
402 >
403 > } catch (error) { abstractSynchronizer.ts ×1
405 > // reset preview on error
406 > this.syncPreviewPromise = null;
407 >
408 > throw error;
409 > }
412 > async accept(resource: URI, content?: string | null): Promise<ISyncResourcePreview | null> {
413 > await this.updateSyncResourcePreview(resource, async (resourcePreview) => { abstractSynchronizer.ts ×1
414 > const acceptResult = await this.getAcceptResult(resourcePreview, resource, content, CancellationToken.None);
415 > resourcePreview.acceptResult = acceptResult;
416 > resourcePreview.mergeState = MergeState.Accepted;
417 > resourcePreview.localChange = acceptResult.localChange;
418 > resourcePreview.remoteChange = acceptResult.remoteChange;
419 > return resourcePreview;
420 > });
421 > return this.syncPreviewPromise;
422 > }
424 > async discard(resource: URI): Promise<ISyncResourcePreview | null> {
425 > await this.updateSyncResourcePreview(resource, async (resourcePreview) => { abstractSynchronizer.ts ×1
426 > const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None);
427 > await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(mergeResult.content || ''));
428 > resourcePreview.acceptResult = undefined;
429 > resourcePreview.mergeState = MergeState.Preview;
430 > resourcePreview.localChange = mergeResult.localChange;
431 > resourcePreview.remoteChange = mergeResult.remoteChange;
432 > return resourcePreview;
433 > });
434 > return this.syncPreviewPromise;
435 > }
437 > private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IEditableResourcePreview) => Promise<IEditableResourcePreview>): Promise<void> {
438 > if (!this.syncPreviewPromise) { abstractSynchronizer.ts ×5
439 return;
440 }
442 > let preview = await this.syncPreviewPromise;
443 > const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) =>
444 > this.extUri.isEqual(localResource, resource) || this.extUri.isEqual(remoteResource, resource) || this.extUri.isEqual(previewResource, resource));
445 > if (index === -1) {
446 return;
447 }
449 > this.syncPreviewPromise = createCancelablePromise(async token => {
450 > const resourcePreviews = [...preview.resourcePreviews];
451 > resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]);
452 > return {
453 > ...preview,
454 > resourcePreviews
455 > };
456 > });
457 >
458 > preview = await this.syncPreviewPromise;
459 > this.updateConflicts(preview.resourcePreviews);
460 > if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
461 > this.setStatus(SyncStatus.HasConflicts); abstractSynchronizer.ts ×1
463 > this.setStatus(SyncStatus.Syncing); abstractSynchronizer.ts ×1
464 > }
467 > private async doApply(force: boolean): Promise<SyncStatus> {
468 > if (!this.syncPreviewPromise) { abstractSynchronizer.ts ×5
469 return SyncStatus.Idle;
470 }
472 > const preview = await this.syncPreviewPromise;
473 >
474 > // check for conflicts
475 > if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
476 return SyncStatus.HasConflicts;
477 }
479 > // check if all are accepted
480 > if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) {
481 return SyncStatus.Syncing;
482 }
484 > // apply preview
485 > await this.applyResult(preview.remoteUserData, preview.lastSyncUserData, preview.resourcePreviews.map(resourcePreview => ([resourcePreview, resourcePreview.acceptResult!])), force);
487 > // reset preview
488 > this.syncPreviewPromise = null;
489 >
490 > // reset preview folder
491 > await this.clearPreviewFolder();
492 >
493 > return SyncStatus.Idle;
496 > private async clearPreviewFolder(): Promise<void> {
498 > await this.fileService.del(this.syncPreviewFolder, { recursive: true });
499 > } catch (error) { /* Ignore */ }
500 > }
502 > private updateConflicts(resourcePreviews: IEditableResourcePreview[]): void {
503 > const conflicts = resourcePreviews.filter(({ mergeState }) => mergeState === MergeState.Conflict); abstractSynchronizer.ts ×2
504 > if (!equals(this._conflicts, conflicts, (a, b) => this.extUri.isEqual(a.previewResource, b.previewResource))) {
505 > this._conflicts = conflicts; abstractSynchronizer.ts ×3
506 > this._onDidChangeConflicts.fire(this.conflicts);
507 > }
510 > async hasPreviouslySynced(): Promise<boolean> {
511 > const lastSyncData = await this.getLastSyncUserData(); userDataSyncService.ts ×9
512 > return !!lastSyncData && lastSyncData.syncData !== null /* `null` sync data implies resource is not synced */;
513 > }
515 > protected async resolvePreviewContent(uri: URI): Promise<string | null> {
516 > const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null; abstractSynchronizer.ts ×4
517 > if (syncPreview) {
518 > for (const resourcePreview of syncPreview.resourcePreviews) {
519 > if (this.extUri.isEqual(resourcePreview.acceptedResource, uri)) {
520 return resourcePreview.acceptResult ? resourcePreview.acceptResult.content : null;
521 }
522 > if (this.extUri.isEqual(resourcePreview.remoteResource, uri)) { abstractSynchronizer.ts ×4
523 > return resourcePreview.remoteContent;
524 > }
525 if (this.extUri.isEqual(resourcePreview.localResource, uri)) {
526 return resourcePreview.localContent;
527 }
528 if (this.extUri.isEqual(resourcePreview.baseResource, uri)) {
529 return resourcePreview.baseContent;
530 }
532 }
533 return null;
536 > async resetLocal(): Promise<void> {
537 > this.storageService.remove(this.lastSyncUserDataStateKey, StorageScope.APPLICATION); abstractSynchronizer.ts ×2
538 > try {
539 > await this.fileService.del(this.lastSyncResource);
540 > } catch (error) {
541 > if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { userDataAutoSyncService.ts ×6
542 this.logService.error(error);
543 }
547 > private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, merge: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, token: CancellationToken): Promise<ISyncResourcePreview> {
548 > const resourcePreviewResults = await this.generateSyncPreview(remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine, userDataSyncConfiguration, token); abstractSynchronizer.ts ×6
550 > const resourcePreviews: IEditableResourcePreview[] = [];
551 > for (const resourcePreviewResult of resourcePreviewResults) {
552 > const acceptedResource = resourcePreviewResult.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); abstractSynchronizer.ts ×2
553 >
554 > /* No change -> Accept */
555 > if (resourcePreviewResult.localChange === Change.None && resourcePreviewResult.remoteChange === Change.None) {
556 > resourcePreviews.push({ abstractSynchronizer.ts ×1
557 > ...resourcePreviewResult,
558 > acceptedResource,
559 > acceptResult: { content: null, localChange: Change.None, remoteChange: Change.None },
560 > mergeState: MergeState.Accepted
561 > });
562 > }
564 > /* Changed -> Apply ? (Merge ? Conflict | Accept) : Preview */
565 > else {
566 > /* Merge */
567 > const mergeResult = merge ? await this.getMergeResult(resourcePreviewResult, token) : undefined;
568 > if (token.isCancellationRequested) {
569 break;
570 }
571 > await this.fileService.writeFile(resourcePreviewResult.previewResource, VSBuffer.fromString(mergeResult?.content || '')); abstractSynchronizer.ts ×3
572 >
573 > /* Conflict | Accept */
574 > const acceptResult = mergeResult && !mergeResult.hasConflicts
575 > /* Accept if merged and there are no conflicts */ abstractSynchronizer.ts ×1
576 > ? await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.previewResource, undefined, token)
577 > : undefined; abstractSynchronizer.ts ×1
579 > resourcePreviews.push({
580 > ...resourcePreviewResult,
581 > acceptResult,
582 > mergeState: mergeResult?.hasConflicts ? MergeState.Conflict : acceptResult ? MergeState.Accepted : MergeState.Preview,
583 > localChange: acceptResult ? acceptResult.localChange : mergeResult ? mergeResult.localChange : resourcePreviewResult.localChange,
584 > remoteChange: acceptResult ? acceptResult.remoteChange : mergeResult ? mergeResult.remoteChange : resourcePreviewResult.remoteChange
585 > });
586 > }
589 > return { syncResource: this.resource, profile: this.syncResource.profile, remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine: isRemoteDataFromCurrentMachine };
592 > async getLastSyncUserData(): Promise<IRemoteUserData | null> {
593 > const storedLastSyncUserDataStateContent = this.getStoredLastSyncUserDataStateContent(); abstractSynchronizer.ts ×7
594 >
595 > // Last Sync Data state does not exist
596 > if (!storedLastSyncUserDataStateContent) {
597 > this.logService.info(`${this.syncResourceLogLabel}: Last sync data state does not exist.`);
598 > return null;
599 > }
601 > const lastSyncUserDataState: ILastSyncUserDataState = JSON.parse(storedLastSyncUserDataStateContent);
602 > const resourceSyncStateVersion = this.userDataSyncEnablementService.getResourceSyncStateVersion(this.resource);
603 > this.hasSyncResourceStateVersionChanged = !!lastSyncUserDataState.version && !!resourceSyncStateVersion && lastSyncUserDataState.version !== resourceSyncStateVersion; abstractSynchronizer.ts ×7
604 > if (this.hasSyncResourceStateVersionChanged) {
605 this.logService.info(`${this.syncResourceLogLabel}: Reset last sync state because last sync state version ${lastSyncUserDataState.version} is not compatible with current sync state version ${resourceSyncStateVersion}.`);
606 await this.resetLocal();
607 return null;
608 }
610 > let syncData: ISyncData | null | undefined = undefined;
611 >
612 > // Get Last Sync Data from Local
613 > let retrial = 1;
614 > while (syncData === undefined && retrial++ < 6 /* Retry 5 times */) { abstractSynchronizer.ts ×7
616 > const lastSyncStoredRemoteUserData = await this.readLastSyncStoredRemoteUserData();
617 > if (lastSyncStoredRemoteUserData) { abstractSynchronizer.ts ×4
618 > if (lastSyncStoredRemoteUserData.ref === lastSyncUserDataState.ref) { abstractSynchronizer.ts ×5
619 > syncData = lastSyncStoredRemoteUserData.syncData; abstractSynchronizer.ts ×1
621 > this.logService.info(`${this.syncResourceLogLabel}: Last sync data stored locally is not same as the last sync state.`); abstractSynchronizer.ts ×1
622 > }
625 > } catch (error) { abstractSynchronizer.ts ×10
626 > if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { abstractSynchronizer.ts ×2
627 > this.logService.info(`${this.syncResourceLogLabel}: Last sync resource does not exist locally.`);
628 > break;
629 > } else if (error instanceof UserDataSyncError) {
630 throw error;
631 } else {
632 // log and retry
633 this.logService.error(error, retrial);
634 }
637 >
638 > // Get Last Sync Data from Remote
639 > if (syncData === undefined) {
641 > const content = await this.userDataSyncStoreService.resolveResourceContent(this.resource, lastSyncUserDataState.ref, this.collection, this.syncHeaders);
642 > syncData = content === null ? null : this.parseSyncData(content);
643 > await this.writeLastSyncStoredRemoteUserData({ ref: lastSyncUserDataState.ref, syncData });
644 > } catch (error) {
645 > if (error instanceof UserDataSyncError && error.code === UserDataSyncErrorCode.NotFound) { abstractSynchronizer.ts ×3
646 > this.logService.info(`${this.syncResourceLogLabel}: Last sync resource does not exist remotely.`);
647 > } else {
648 throw error;
649 }
653 > // Last Sync Data Not Found
654 > if (syncData === undefined) {
655 > return null; abstractSynchronizer.ts ×3
656 > }
658 > return {
659 > ...lastSyncUserDataState,
660 > syncData,
661 > };
664 > protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
665 > if (additionalProps['ref'] || additionalProps['version']) { abstractSynchronizer.ts ×3
666 throw new Error('Cannot have core properties as additional');
667 }
669 > const version = this.userDataSyncEnablementService.getResourceSyncStateVersion(this.resource);
670 > const lastSyncUserDataState: ILastSyncUserDataState = {
671 > ref: lastSyncRemoteUserData.ref,
672 > version,
673 > ...additionalProps
674 > };
675 >
676 > this.storageService.store(this.lastSyncUserDataStateKey, JSON.stringify(lastSyncUserDataState), StorageScope.APPLICATION, StorageTarget.MACHINE);
677 > await this.writeLastSyncStoredRemoteUserData(lastSyncRemoteUserData);
678 > }
680 > private getStoredLastSyncUserDataStateContent(): string | undefined {
681 > return this.storageService.get(this.lastSyncUserDataStateKey, StorageScope.APPLICATION); abstractSynchronizer.ts ×7
682 > }
684 > private async readLastSyncStoredRemoteUserData(): Promise<IRemoteUserData | undefined> {
685 > const content = (await this.fileService.readFile(this.lastSyncResource)).value.toString(); abstractSynchronizer.ts ×10
687 > const lastSyncStoredRemoteUserData = content ? JSON.parse(content) : undefined; abstractSynchronizer.ts ×10
688 > if (isRemoteUserData(lastSyncStoredRemoteUserData)) {
689 > return lastSyncStoredRemoteUserData; abstractSynchronizer.ts ×5
690 > }
691 > } catch (e) { abstractSynchronizer.ts ×10
692 this.logService.error(e);
693 }
694 > return undefined; abstractSynchronizer.ts ×2
697 > private async writeLastSyncStoredRemoteUserData(lastSyncRemoteUserData: IRemoteUserData): Promise<void> {
698 > await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncRemoteUserData))); abstractSynchronizer.ts ×3
699 > }
701 > async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise<IRemoteUserData> {
702 > const userData = await this.getUserData(lastSyncData); abstractSynchronizer.ts ×4
703 > return this.toRemoteUserData(userData);
704 > }
706 > private toRemoteUserData({ ref, content }: IUserData): IRemoteUserData {
707 > let syncData: ISyncData | null = null; abstractSynchronizer.ts ×4
708 > if (content !== null) {
709 > syncData = this.parseSyncData(content); abstractSynchronizer.ts ×1
710 > }
711 > return { ref, syncData }; abstractSynchronizer.ts ×4
712 > }
714 > protected parseSyncData(content: string): ISyncData {
716 > const syncData: ISyncData = JSON.parse(content);
717 > if (isSyncData(syncData)) {
718 > return syncData;
719 > }
720 > } catch (error) {
721 this.logService.error(error);
722 }
723 throw new UserDataSyncError(localize('incompatible sync data', "Cannot parse sync data as it is not compatible with the current version."), UserDataSyncErrorCode.IncompatibleRemoteContent, this.resource);
726 > private async getUserData(lastSyncData: IRemoteUserData | null): Promise<IUserData> {
727 > const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null; abstractSynchronizer.ts ×4
728 > return this.userDataSyncStoreService.readResource(this.resource, lastSyncUserData, this.collection, this.syncHeaders);
729 > }
731 > protected async updateRemoteUserData(content: string, ref: string | null): Promise<IRemoteUserData> {
732 > const machineId = await this.currentMachineIdPromise; abstractSynchronizer.ts ×2
733 > const syncData: ISyncData = { version: this.version, machineId, content };
734 > try {
735 > ref = await this.userDataSyncStoreService.writeResource(this.resource, JSON.stringify(syncData), ref, this.collection, this.syncHeaders);
736 > return { ref, syncData };
737 > } catch (error) {
738 > if (error instanceof UserDataSyncError && error.code === UserDataSyncErrorCode.TooLarge) { abstractSynchronizer.ts ×2
739 error = new UserDataSyncError(error.message, error.code, this.resource);
740 }
741 > throw error; abstractSynchronizer.ts ×2
742 > }
745 > protected async backupLocal(content: string): Promise<void> {
746 > const syncData: ISyncData = { version: this.version, content }; userDataSyncLocalStoreService.ts ×2
747 > return this.userDataSyncLocalStoreService.writeResource(this.resource, JSON.stringify(syncData), new Date(), this.syncResource.profile.isDefault ? undefined : this.syncResource.profile.id);
748 > }
750 > async stop(): Promise<void> {
751 > if (this.status === SyncStatus.Idle) { abstractSynchronizer.ts ×2
753 > }
755 > this.logService.trace(`${this.syncResourceLogLabel}: Stopping synchronizing ${this.resource.toLowerCase()}.`);
756 > if (this.syncPreviewPromise) {
757 this.syncPreviewPromise.cancel();
758 this.syncPreviewPromise = null;
759 }
761 > this.updateConflicts([]);
762 > await this.clearPreviewFolder();
763 >
764 > this.setStatus(SyncStatus.Idle);
765 > this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
768 > private getUserDataSyncConfiguration(): IUserDataSyncConfiguration {
769 > return this.configurationService.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE); userDataSyncClient.ts ×2
770 > }
772 > protected abstract readonly version: number;
773 > protected abstract generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration, token: CancellationToken): Promise<IResourcePreview[]>;
774 > protected abstract getMergeResult(resourcePreview: IResourcePreview, token: CancellationToken): Promise<IMergeResult>;
775 > protected abstract getAcceptResult(resourcePreview: IResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult>;
776 > protected abstract applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, result: [IResourcePreview, IAcceptResult][], force: boolean): Promise<void>;
777 > protected abstract hasRemoteChanged(lastSyncUserData: IRemoteUserData): Promise<boolean>;
778 >
779 > abstract hasLocalData(): Promise<boolean>;
780 > abstract resolveContent(uri: URI): Promise<string | null>;
781 > }
782 >
783 > export interface IFileResourcePreview extends IResourcePreview {
784 > readonly fileContent: IFileContent | null;
785 > }
786 >
787 > export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
788 >
789 > constructor(
790 > protected readonly file: URI, userDataSyncService.ts ×19
791 > syncResource: IUserDataSyncResource,
792 > collection: string | undefined,
793 > @IFileService fileService: IFileService,
794 > @IEnvironmentService environmentService: IEnvironmentService,
795 > @IStorageService storageService: IStorageService,
796 > @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
797 > @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService,
798 > @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
799 > @ITelemetryService telemetryService: ITelemetryService,
800 > @IUserDataSyncLogService logService: IUserDataSyncLogService,
801 > @IConfigurationService configurationService: IConfigurationService,
802 > @IUriIdentityService uriIdentityService: IUriIdentityService,
803 > ) {
804 > super(syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);
805 > this._register(this.fileService.watch(this.extUri.dirname(file)));
806 > this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
807 > }
809 > protected async getLocalFileContent(): Promise<IFileContent | null> {
811 > return await this.fileService.readFile(this.file);
812 > } catch (error) {
813 > return null; abstractSynchronizer.ts ×1
814 > }
817 > protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null, force: boolean): Promise<void> {
819 > if (oldContent) {
820 > // file exists already abstractSynchronizer.ts ×1
821 > await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), force ? undefined : oldContent);
823 > // file does not exist abstractSynchronizer.ts ×1
824 > await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: force });
825 > }
826 > } catch (e) { abstractSynchronizer.ts ×4
827 if ((e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) ||
828 (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) {
829 throw new UserDataSyncError(e.message, UserDataSyncErrorCode.LocalPreconditionFailed);
830 } else {
831 throw e;
832 }
833 }
836 > protected async deleteLocalFile(): Promise<void> {
838 > await this.fileService.del(this.file);
839 > } catch (e) {
840 > if (!(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND)) { abstractJsonSynchronizer.ts ×3
841 throw e;
842 }
846 > private onFileChanges(e: FileChangesEvent): void {
847 > if (!e.contains(this.file)) { abstractSynchronizer.ts ×2
848 > return;
849 > }
850 > this.triggerLocalChange(); abstractSynchronizer.ts ×1
853 > }
854 >
855 > export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser {
856 >
857 > constructor(
858 > file: URI, userDataSyncService.ts ×19
859 > syncResource: IUserDataSyncResource,
860 > collection: string | undefined,
861 > @IFileService fileService: IFileService,
862 > @IEnvironmentService environmentService: IEnvironmentService,
863 > @IStorageService storageService: IStorageService,
864 > @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
865 > @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService,
866 > @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
867 > @ITelemetryService telemetryService: ITelemetryService,
868 > @IUserDataSyncLogService logService: IUserDataSyncLogService,
869 > @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService,
870 > @IConfigurationService configurationService: IConfigurationService,
871 > @IUriIdentityService uriIdentityService: IUriIdentityService,
872 > ) {
873 > super(file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService);
874 > }
875 >
876 > protected hasErrors(content: string, isArray: boolean): boolean {
877 > const parseErrors: ParseError[] = []; abstractSynchronizer.ts ×1
878 > const result = parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true });
879 > return parseErrors.length > 0 || (!isUndefined(result) && isArray !== Array.isArray(result));
880 > }
882 > private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
883 > protected getFormattingOptions(): Promise<FormattingOptions> { abstractSynchronizer.ts ×49
884 > if (!this._formattingOptions) { abstractSynchronizer.ts ×1
885 > this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.file);
886 > }
887 > return this._formattingOptions;
888 > }
890 > }
891 >
892 > export abstract class AbstractInitializer implements IUserDataSyncResourceInitializer {
893 >
894 > protected readonly extUri: IExtUri;
895 > private readonly lastSyncResource: URI;
896 >
897 > constructor(
898 readonly resource: SyncResource,
899 @IUserDataProfilesService protected readonly userDataProfilesService: IUserDataProfilesService,
900 @IEnvironmentService protected readonly environmentService: IEnvironmentService,
901 @ILogService protected readonly logService: ILogService,
902 @IFileService protected readonly fileService: IFileService,
903 @IStorageService protected readonly storageService: IStorageService,
904 @IUriIdentityService uriIdentityService: IUriIdentityService,
905 ) {
906 this.extUri = uriIdentityService.extUri;
907 this.lastSyncResource = getLastSyncResourceUri(undefined, this.resource, environmentService, this.extUri);
908 }
910 > async initialize({ ref, content }: IUserData): Promise<void> {
911 if (!content) {
912 this.logService.info('Remote content does not exist.', this.resource);
913 return;
914 }
915
916 const syncData = this.parseSyncData(content);
917 if (!syncData) {
918 return;
919 }
920
921 try {
922 await this.doInitialize({ ref, syncData });
923 } catch (error) {
924 this.logService.error(error);
925 }
926 }
928 > private parseSyncData(content: string): ISyncData | undefined {
929 try {
930 const syncData: ISyncData = JSON.parse(content);
931 if (isSyncData(syncData)) {
932 return syncData;
933 }
934 } catch (error) {
935 this.logService.error(error);
936 }
937 this.logService.info('Cannot parse sync data as it is not compatible with the current version.', this.resource);
938 return undefined;
939 }
941 > protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
942 if (additionalProps['ref'] || additionalProps['version']) {
943 throw new Error('Cannot have core properties as additional');
944 }
945
946 const lastSyncUserDataState: ILastSyncUserDataState = {
947 ref: lastSyncRemoteUserData.ref,
948 version: undefined,
949 ...additionalProps
950 };
951
952 this.storageService.store(`${this.resource}.lastSyncUserData`, JSON.stringify(lastSyncUserDataState), StorageScope.APPLICATION, StorageTarget.MACHINE);
953 await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncRemoteUserData)));
954 }
956 > protected abstract doInitialize(remoteUserData: IRemoteUserData): Promise<void>;
957 >
958 > }