UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

1,160 lines (1,159 loc) 40.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; import { equals } from '@sussudio/base/common/arrays.mjs'; import { createCancelablePromise, ThrottledDelayer } from '@sussudio/base/common/async.mjs'; import { VSBuffer } from '@sussudio/base/common/buffer.mjs'; import { CancellationToken } from '@sussudio/base/common/cancellation.mjs'; import { Emitter } from '@sussudio/base/common/event.mjs'; import { parse } from '@sussudio/base/common/json.mjs'; import { Disposable } from '@sussudio/base/common/lifecycle.mjs'; import { uppercaseFirstLetter } from '@sussudio/base/common/strings.mjs'; import { isUndefined } from '@sussudio/base/common/types.mjs'; import { localize } from 'vscode-nls.mjs'; import { IConfigurationService } from '../../configuration/common/configuration.mjs'; import { IEnvironmentService } from '../../environment/common/environment.mjs'; import { FileOperationError, IFileService, toFileOperationResult } from '../../files/common/files.mjs'; import { ILogService } from '../../log/common/log.mjs'; import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.mjs'; import { IStorageService } from '../../storage/common/storage.mjs'; import { ITelemetryService } from '../../telemetry/common/telemetry.mjs'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.mjs'; import { getLastSyncResourceUri, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, PREVIEW_DIR_NAME, UserDataSyncError, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, getPathSegments, } from './userDataSync.mjs'; import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.mjs'; export function isRemoteUserData(thing) { if ( thing && thing.ref !== undefined && typeof thing.ref === 'string' && thing.ref !== '' && thing.syncData !== undefined && (thing.syncData === null || isSyncData(thing.syncData)) ) { return true; } return false; } export function isSyncData(thing) { if ( thing && thing.version !== undefined && typeof thing.version === 'number' && thing.content !== undefined && typeof thing.content === 'string' ) { // backward compatibility if (Object.keys(thing).length === 2) { return true; } if (Object.keys(thing).length === 3 && thing.machineId !== undefined && typeof thing.machineId === 'string') { return true; } } return false; } export function getSyncResourceLogLabel(syncResource, profile) { return `${uppercaseFirstLetter(syncResource)}${profile.isDefault ? '' : ` (${profile.name})`}`; } let AbstractSynchroniser = class AbstractSynchroniser extends Disposable { syncResource; collection; fileService; environmentService; storageService; userDataSyncStoreService; userDataSyncBackupStoreService; userDataSyncEnablementService; telemetryService; logService; configurationService; syncPreviewPromise = null; syncFolder; syncPreviewFolder; extUri; currentMachineIdPromise; _status = 'idle' /* SyncStatus.Idle */; get status() { return this._status; } _onDidChangStatus = this._register(new Emitter()); onDidChangeStatus = this._onDidChangStatus.event; _conflicts = []; get conflicts() { return { ...this.syncResource, conflicts: this._conflicts }; } _onDidChangeConflicts = this._register(new Emitter()); onDidChangeConflicts = this._onDidChangeConflicts.event; localChangeTriggerThrottler = new ThrottledDelayer(50); _onDidChangeLocal = this._register(new Emitter()); onDidChangeLocal = this._onDidChangeLocal.event; lastSyncResource; lastSyncUserDataStateKey = `${this.collection ? `${this.collection}.` : ''}${ this.syncResource.syncResource }.lastSyncUserData`; hasSyncResourceStateVersionChanged = false; syncResourceLogLabel; syncHeaders = {}; resource = this.syncResource.syncResource; constructor( syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService, ) { super(); this.syncResource = syncResource; this.collection = collection; this.fileService = fileService; this.environmentService = environmentService; this.storageService = storageService; this.userDataSyncStoreService = userDataSyncStoreService; this.userDataSyncBackupStoreService = userDataSyncBackupStoreService; this.userDataSyncEnablementService = userDataSyncEnablementService; this.telemetryService = telemetryService; this.logService = logService; this.configurationService = configurationService; this.syncResourceLogLabel = getSyncResourceLogLabel(syncResource.syncResource, syncResource.profile); this.extUri = uriIdentityService.extUri; this.syncFolder = this.extUri.joinPath( environmentService.userDataSyncHome, ...getPathSegments( syncResource.profile.isDefault ? undefined : syncResource.profile.id, syncResource.syncResource, ), ); this.syncPreviewFolder = this.extUri.joinPath(this.syncFolder, PREVIEW_DIR_NAME); this.lastSyncResource = getLastSyncResourceUri( syncResource.profile.isDefault ? undefined : syncResource.profile.id, syncResource.syncResource, environmentService, this.extUri, ); this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService); } triggerLocalChange() { this.localChangeTriggerThrottler.trigger(() => this.doTriggerLocalChange()); } async doTriggerLocalChange() { // Sync again if current status is in conflicts if (this.status === 'hasConflicts' /* SyncStatus.HasConflicts */) { this.logService.info( `${this.syncResourceLogLabel}: In conflicts state and local change detected. Syncing again...`, ); const preview = await this.syncPreviewPromise; this.syncPreviewPromise = null; const status = await this.performSync( preview.remoteUserData, preview.lastSyncUserData, true, this.getUserDataSyncConfiguration(), ); this.setStatus(status); } // Check if local change causes remote change else { this.logService.trace(`${this.syncResourceLogLabel}: Checking for local changes...`); const lastSyncUserData = await this.getLastSyncUserData(); const hasRemoteChanged = lastSyncUserData ? await this.hasRemoteChanged(lastSyncUserData) : true; if (hasRemoteChanged) { this._onDidChangeLocal.fire(); } } } setStatus(status) { if (this._status !== status) { this._status = status; this._onDidChangStatus.fire(status); } } async sync(manifest, headers = {}) { await this._sync(manifest, true, this.getUserDataSyncConfiguration(), headers); } async preview(manifest, userDataSyncConfiguration, headers = {}) { return this._sync(manifest, false, userDataSyncConfiguration, headers); } async apply(force, headers = {}) { try { this.syncHeaders = { ...headers }; const status = await this.doApply(force); this.setStatus(status); return this.syncPreviewPromise; } finally { this.syncHeaders = {}; } } async _sync(manifest, apply, userDataSyncConfiguration, headers) { try { this.syncHeaders = { ...headers }; if (this.status === 'hasConflicts' /* SyncStatus.HasConflicts */) { this.logService.info( `${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`, ); return this.syncPreviewPromise; } if (this.status === 'syncing' /* SyncStatus.Syncing */) { this.logService.info( `${ this.syncResourceLogLabel }: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`, ); return this.syncPreviewPromise; } this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`); this.setStatus('syncing' /* SyncStatus.Syncing */); let status = 'idle'; /* SyncStatus.Idle */ try { const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData); status = await this.performSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); if (status === 'hasConflicts' /* SyncStatus.HasConflicts */) { this.logService.info( `${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`, ); } else if (status === 'idle' /* SyncStatus.Idle */) { this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`); } return this.syncPreviewPromise || null; } finally { this.setStatus(status); } } finally { this.syncHeaders = {}; } } async replace(content) { const syncData = this.parseSyncData(content); if (!syncData) { return false; } await this.stop(); try { this.logService.trace(`${this.syncResourceLogLabel}: Started resetting ${this.resource.toLowerCase()}...`); this.setStatus('syncing' /* SyncStatus.Syncing */); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getLatestRemoteUserData(null, lastSyncUserData); const isRemoteDataFromCurrentMachine = await this.isRemoteDataFromCurrentMachine(remoteUserData); /* use replace sync data */ const resourcePreviewResults = await this.generateSyncPreview( { ref: remoteUserData.ref, syncData }, lastSyncUserData, isRemoteDataFromCurrentMachine, this.getUserDataSyncConfiguration(), CancellationToken.None, ); const resourcePreviews = []; for (const resourcePreviewResult of resourcePreviewResults) { /* Accept remote resource */ const acceptResult = await this.getAcceptResult( resourcePreviewResult, resourcePreviewResult.remoteResource, undefined, CancellationToken.None, ); /* compute remote change */ const { remoteChange } = await this.getAcceptResult( resourcePreviewResult, resourcePreviewResult.previewResource, resourcePreviewResult.remoteContent, CancellationToken.None, ); resourcePreviews.push([ resourcePreviewResult, { ...acceptResult, remoteChange: remoteChange !== 0 /* Change.None */ ? remoteChange : 2 /* Change.Modified */, }, ]); } await this.applyResult(remoteUserData, lastSyncUserData, resourcePreviews, false); this.logService.info(`${this.syncResourceLogLabel}: Finished resetting ${this.resource.toLowerCase()}.`); } finally { this.setStatus('idle' /* SyncStatus.Idle */); } return true; } async isRemoteDataFromCurrentMachine(remoteUserData) { const machineId = await this.currentMachineIdPromise; return !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId; } async getLatestRemoteUserData(manifest, lastSyncUserData) { if (lastSyncUserData) { const latestRef = manifest ? manifest[this.resource] : undefined; // Last time synced resource and latest resource on server are same if (lastSyncUserData.ref === latestRef) { return lastSyncUserData; } // There is no resource on server and last time it was synced with no resource if (latestRef === undefined && lastSyncUserData.syncData === null) { return lastSyncUserData; } } return this.getRemoteUserData(lastSyncUserData); } async performSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration) { if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { // current version is not compatible with cloud version this.telemetryService.publicLog2('sync/incompatible', { source: this.resource }); 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, ), 'IncompatibleLocalContent' /* UserDataSyncErrorCode.IncompatibleLocalContent */, this.resource, ); } try { return await this.doSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); } catch (e) { if (e instanceof UserDataSyncError) { switch (e.code) { case 'LocalPreconditionFailed' /* UserDataSyncErrorCode.LocalPreconditionFailed */: // Rejected as there is a new local version. Syncing again... this.logService.info( `${this.syncResourceLogLabel}: Failed to synchronize ${this.syncResourceLogLabel} as there is a new local version available. Synchronizing again...`, ); return this.performSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); case 'Conflict' /* UserDataSyncErrorCode.Conflict */: case 'PreconditionFailed' /* UserDataSyncErrorCode.PreconditionFailed */: // Rejected as there is a new remote version. Syncing again... this.logService.info( `${this.syncResourceLogLabel}: Failed to synchronize as there is a new remote version available. Synchronizing again...`, ); // Avoid cache and get latest remote user data - https://github.com/microsoft/vscode/issues/90624 remoteUserData = await this.getRemoteUserData(null); // Get the latest last sync user data. Because multiple parallel syncs (in Web) could share same last sync data // and one of them successfully updated remote and last sync state. lastSyncUserData = await this.getLastSyncUserData(); return this.performSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration); } } throw e; } } async doSync(remoteUserData, lastSyncUserData, apply, userDataSyncConfiguration) { try { const isRemoteDataFromCurrentMachine = await this.isRemoteDataFromCurrentMachine(remoteUserData); const acceptRemote = !isRemoteDataFromCurrentMachine && lastSyncUserData === null && this.getStoredLastSyncUserDataStateContent() !== undefined; const merge = apply && !acceptRemote; // generate or use existing preview if (!this.syncPreviewPromise) { this.syncPreviewPromise = createCancelablePromise((token) => this.doGenerateSyncResourcePreview( remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine, merge, userDataSyncConfiguration, token, ), ); } let preview = await this.syncPreviewPromise; if (apply && acceptRemote) { this.logService.info( `${this.syncResourceLogLabel}: Accepting remote because it was synced before and the last sync data is not available.`, ); for (const resourcePreview of preview.resourcePreviews) { preview = (await this.accept(resourcePreview.remoteResource)) || preview; } } this.updateConflicts(preview.resourcePreviews); if (preview.resourcePreviews.some(({ mergeState }) => mergeState === 'conflict' /* MergeState.Conflict */)) { return 'hasConflicts' /* SyncStatus.HasConflicts */; } if (apply) { return await this.doApply(false); } return 'syncing' /* SyncStatus.Syncing */; } catch (error) { // reset preview on error this.syncPreviewPromise = null; throw error; } } async merge(resource) { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None); await this.fileService.writeFile( resourcePreview.previewResource, VSBuffer.fromString(mergeResult?.content || ''), ); const acceptResult = mergeResult && !mergeResult.hasConflicts ? await this.getAcceptResult( resourcePreview, resourcePreview.previewResource, undefined, CancellationToken.None, ) : undefined; resourcePreview.acceptResult = acceptResult; resourcePreview.mergeState = mergeResult.hasConflicts ? 'conflict' /* MergeState.Conflict */ : acceptResult ? 'accepted' /* MergeState.Accepted */ : 'preview' /* MergeState.Preview */; resourcePreview.localChange = acceptResult ? acceptResult.localChange : mergeResult.localChange; resourcePreview.remoteChange = acceptResult ? acceptResult.remoteChange : mergeResult.remoteChange; return resourcePreview; }); return this.syncPreviewPromise; } async accept(resource, content) { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { const acceptResult = await this.getAcceptResult(resourcePreview, resource, content, CancellationToken.None); resourcePreview.acceptResult = acceptResult; resourcePreview.mergeState = 'accepted' /* MergeState.Accepted */; resourcePreview.localChange = acceptResult.localChange; resourcePreview.remoteChange = acceptResult.remoteChange; return resourcePreview; }); return this.syncPreviewPromise; } async discard(resource) { await this.updateSyncResourcePreview(resource, async (resourcePreview) => { const mergeResult = await this.getMergeResult(resourcePreview, CancellationToken.None); await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(mergeResult.content || '')); resourcePreview.acceptResult = undefined; resourcePreview.mergeState = 'preview' /* MergeState.Preview */; resourcePreview.localChange = mergeResult.localChange; resourcePreview.remoteChange = mergeResult.remoteChange; return resourcePreview; }); return this.syncPreviewPromise; } async updateSyncResourcePreview(resource, updateResourcePreview) { if (!this.syncPreviewPromise) { return; } let preview = await this.syncPreviewPromise; const index = preview.resourcePreviews.findIndex( ({ localResource, remoteResource, previewResource }) => this.extUri.isEqual(localResource, resource) || this.extUri.isEqual(remoteResource, resource) || this.extUri.isEqual(previewResource, resource), ); if (index === -1) { return; } this.syncPreviewPromise = createCancelablePromise(async (token) => { const resourcePreviews = [...preview.resourcePreviews]; resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]); return { ...preview, resourcePreviews, }; }); preview = await this.syncPreviewPromise; this.updateConflicts(preview.resourcePreviews); if (preview.resourcePreviews.some(({ mergeState }) => mergeState === 'conflict' /* MergeState.Conflict */)) { this.setStatus('hasConflicts' /* SyncStatus.HasConflicts */); } else { this.setStatus('syncing' /* SyncStatus.Syncing */); } } async doApply(force) { if (!this.syncPreviewPromise) { return 'idle' /* SyncStatus.Idle */; } const preview = await this.syncPreviewPromise; // check for conflicts if (preview.resourcePreviews.some(({ mergeState }) => mergeState === 'conflict' /* MergeState.Conflict */)) { return 'hasConflicts' /* SyncStatus.HasConflicts */; } // check if all are accepted if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== 'accepted' /* MergeState.Accepted */)) { return 'syncing' /* SyncStatus.Syncing */; } // apply preview await this.applyResult( preview.remoteUserData, preview.lastSyncUserData, preview.resourcePreviews.map((resourcePreview) => [resourcePreview, resourcePreview.acceptResult]), force, ); // reset preview this.syncPreviewPromise = null; // reset preview folder await this.clearPreviewFolder(); return 'idle' /* SyncStatus.Idle */; } async clearPreviewFolder() { try { await this.fileService.del(this.syncPreviewFolder, { recursive: true }); } catch (error) { /* Ignore */ } } updateConflicts(resourcePreviews) { const conflicts = resourcePreviews.filter(({ mergeState }) => mergeState === 'conflict' /* MergeState.Conflict */); if (!equals(this._conflicts, conflicts, (a, b) => this.extUri.isEqual(a.previewResource, b.previewResource))) { this._conflicts = conflicts; this._onDidChangeConflicts.fire(this.conflicts); } } async hasPreviouslySynced() { const lastSyncData = await this.getLastSyncUserData(); return !!lastSyncData && lastSyncData.syncData !== null /* `null` sync data implies resource is not synced */; } async resolvePreviewContent(uri) { const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null; if (syncPreview) { for (const resourcePreview of syncPreview.resourcePreviews) { if (this.extUri.isEqual(resourcePreview.acceptedResource, uri)) { return resourcePreview.acceptResult ? resourcePreview.acceptResult.content : null; } if (this.extUri.isEqual(resourcePreview.remoteResource, uri)) { return resourcePreview.remoteContent; } if (this.extUri.isEqual(resourcePreview.localResource, uri)) { return resourcePreview.localContent; } if (this.extUri.isEqual(resourcePreview.baseResource, uri)) { return resourcePreview.baseContent; } } } return null; } async resetLocal() { this.storageService.remove(this.lastSyncUserDataStateKey, -1 /* StorageScope.APPLICATION */); try { await this.fileService.del(this.lastSyncResource); } catch (error) { if (toFileOperationResult(error) !== 1 /* FileOperationResult.FILE_NOT_FOUND */) { this.logService.error(error); } } } async doGenerateSyncResourcePreview( remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine, merge, userDataSyncConfiguration, token, ) { const resourcePreviewResults = await this.generateSyncPreview( remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine, userDataSyncConfiguration, token, ); const resourcePreviews = []; for (const resourcePreviewResult of resourcePreviewResults) { const acceptedResource = resourcePreviewResult.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted', }); /* No change -> Accept */ if ( resourcePreviewResult.localChange === 0 /* Change.None */ && resourcePreviewResult.remoteChange === 0 /* Change.None */ ) { resourcePreviews.push({ ...resourcePreviewResult, acceptedResource, acceptResult: { content: null, localChange: 0 /* Change.None */, remoteChange: 0 /* Change.None */ }, mergeState: 'accepted' /* MergeState.Accepted */, }); } else { /* Changed -> Apply ? (Merge ? Conflict | Accept) : Preview */ /* Merge */ const mergeResult = merge ? await this.getMergeResult(resourcePreviewResult, token) : undefined; if (token.isCancellationRequested) { break; } await this.fileService.writeFile( resourcePreviewResult.previewResource, VSBuffer.fromString(mergeResult?.content || ''), ); /* Conflict | Accept */ const acceptResult = mergeResult && !mergeResult.hasConflicts ? /* Accept if merged and there are no conflicts */ await this.getAcceptResult(resourcePreviewResult, resourcePreviewResult.previewResource, undefined, token) : undefined; resourcePreviews.push({ ...resourcePreviewResult, acceptResult, mergeState: mergeResult?.hasConflicts ? 'conflict' /* MergeState.Conflict */ : acceptResult ? 'accepted' /* MergeState.Accepted */ : 'preview' /* MergeState.Preview */, localChange: acceptResult ? acceptResult.localChange : mergeResult ? mergeResult.localChange : resourcePreviewResult.localChange, remoteChange: acceptResult ? acceptResult.remoteChange : mergeResult ? mergeResult.remoteChange : resourcePreviewResult.remoteChange, }); } } return { syncResource: this.resource, profile: this.syncResource.profile, remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine: isRemoteDataFromCurrentMachine, }; } async getLastSyncUserData() { let storedLastSyncUserDataStateContent = this.getStoredLastSyncUserDataStateContent(); if (!storedLastSyncUserDataStateContent) { storedLastSyncUserDataStateContent = await this.migrateLastSyncUserData(); } // Last Sync Data state does not exist if (!storedLastSyncUserDataStateContent) { this.logService.info(`${this.syncResourceLogLabel}: Last sync data state does not exist.`); return null; } const lastSyncUserDataState = JSON.parse(storedLastSyncUserDataStateContent); const resourceSyncStateVersion = this.userDataSyncEnablementService.getResourceSyncStateVersion(this.resource); this.hasSyncResourceStateVersionChanged = !!lastSyncUserDataState.version && !!resourceSyncStateVersion && lastSyncUserDataState.version !== resourceSyncStateVersion; if (this.hasSyncResourceStateVersionChanged) { 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}.`, ); await this.resetLocal(); return null; } let syncData = undefined; // Get Last Sync Data from Local let retrial = 1; while (syncData === undefined && retrial++ < 6 /* Retry 5 times */) { try { const lastSyncStoredRemoteUserData = await this.readLastSyncStoredRemoteUserData(); if (lastSyncStoredRemoteUserData) { if (lastSyncStoredRemoteUserData.ref === lastSyncUserDataState.ref) { syncData = lastSyncStoredRemoteUserData.syncData; } else { this.logService.info( `${this.syncResourceLogLabel}: Last sync data stored locally is not same as the last sync state.`, ); } } break; } catch (error) { if ( error instanceof FileOperationError && error.fileOperationResult === 1 /* FileOperationResult.FILE_NOT_FOUND */ ) { this.logService.info(`${this.syncResourceLogLabel}: Last sync resource does not exist locally.`); break; } else if (error instanceof UserDataSyncError) { throw error; } else { // log and retry this.logService.error(error, retrial); } } } // Get Last Sync Data from Remote if (syncData === undefined) { try { const content = await this.userDataSyncStoreService.resolveResourceContent( this.resource, lastSyncUserDataState.ref, this.collection, this.syncHeaders, ); syncData = content === null ? null : this.parseSyncData(content); await this.writeLastSyncStoredRemoteUserData({ ref: lastSyncUserDataState.ref, syncData }); } catch (error) { if (error instanceof UserDataSyncError && error.code === 'NotFound' /* UserDataSyncErrorCode.NotFound */) { this.logService.info(`${this.syncResourceLogLabel}: Last sync resource does not exist remotely.`); } else { throw error; } } } // Last Sync Data Not Found if (syncData === undefined) { return null; } return { ...lastSyncUserDataState, syncData, }; } async updateLastSyncUserData(lastSyncRemoteUserData, additionalProps = {}) { if (additionalProps['ref'] || additionalProps['version']) { throw new Error('Cannot have core properties as additional'); } const version = this.userDataSyncEnablementService.getResourceSyncStateVersion(this.resource); const lastSyncUserDataState = { ref: lastSyncRemoteUserData.ref, version, ...additionalProps, }; this.storageService.store( this.lastSyncUserDataStateKey, JSON.stringify(lastSyncUserDataState), -1 /* StorageScope.APPLICATION */, 1 /* StorageTarget.MACHINE */, ); await this.writeLastSyncStoredRemoteUserData(lastSyncRemoteUserData); } getStoredLastSyncUserDataStateContent() { return this.storageService.get(this.lastSyncUserDataStateKey, -1 /* StorageScope.APPLICATION */); } async readLastSyncStoredRemoteUserData() { const content = (await this.fileService.readFile(this.lastSyncResource)).value.toString(); try { const lastSyncStoredRemoteUserData = content ? JSON.parse(content) : undefined; if (isRemoteUserData(lastSyncStoredRemoteUserData)) { return lastSyncStoredRemoteUserData; } } catch (e) { this.logService.error(e); } return undefined; } async writeLastSyncStoredRemoteUserData(lastSyncRemoteUserData) { await this.fileService.writeFile( this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncRemoteUserData)), ); } async migrateLastSyncUserData() { try { const content = await this.fileService.readFile(this.lastSyncResource); const userData = JSON.parse(content.value.toString()); await this.fileService.del(this.lastSyncResource); if (userData.ref && userData.content !== undefined) { this.storageService.store( this.lastSyncUserDataStateKey, JSON.stringify({ ...userData, content: undefined, }), -1 /* StorageScope.APPLICATION */, 1 /* StorageTarget.MACHINE */, ); await this.writeLastSyncStoredRemoteUserData({ ref: userData.ref, syncData: userData.content === null ? null : JSON.parse(userData.content), }); } } catch (error) { if ( error instanceof FileOperationError && error.fileOperationResult === 1 /* FileOperationResult.FILE_NOT_FOUND */ ) { this.logService.debug(`${this.syncResourceLogLabel}: Migrating last sync user data. Resource does not exist.`); } else { this.logService.error(error); } } return this.storageService.get(this.lastSyncUserDataStateKey, -1 /* StorageScope.APPLICATION */); } async getRemoteUserData(lastSyncData) { const { ref, content } = await this.getUserData(lastSyncData); let syncData = null; if (content !== null) { syncData = this.parseSyncData(content); } return { ref, syncData }; } parseSyncData(content) { try { const syncData = JSON.parse(content); if (isSyncData(syncData)) { return syncData; } } catch (error) { this.logService.error(error); } throw new UserDataSyncError( localize('incompatible sync data', 'Cannot parse sync data as it is not compatible with the current version.'), 'IncompatibleRemoteContent' /* UserDataSyncErrorCode.IncompatibleRemoteContent */, this.resource, ); } async getUserData(lastSyncData) { const lastSyncUserData = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null; return this.userDataSyncStoreService.readResource( this.resource, lastSyncUserData, this.collection, this.syncHeaders, ); } async updateRemoteUserData(content, ref) { const machineId = await this.currentMachineIdPromise; const syncData = { version: this.version, machineId, content }; try { ref = await this.userDataSyncStoreService.writeResource( this.resource, JSON.stringify(syncData), ref, this.collection, this.syncHeaders, ); return { ref, syncData }; } catch (error) { if (error instanceof UserDataSyncError && error.code === 'TooLarge' /* UserDataSyncErrorCode.TooLarge */) { error = new UserDataSyncError(error.message, error.code, this.resource); } throw error; } } async backupLocal(content) { const syncData = { version: this.version, content }; return this.userDataSyncBackupStoreService.backup( this.syncResource.profile, this.resource, JSON.stringify(syncData), ); } async stop() { if (this.status === 'idle' /* SyncStatus.Idle */) { return; } this.logService.trace(`${this.syncResourceLogLabel}: Stopping synchronizing ${this.resource.toLowerCase()}.`); if (this.syncPreviewPromise) { this.syncPreviewPromise.cancel(); this.syncPreviewPromise = null; } this.updateConflicts([]); await this.clearPreviewFolder(); this.setStatus('idle' /* SyncStatus.Idle */); this.logService.info(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`); } getUserDataSyncConfiguration() { return this.configurationService.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE); } }; AbstractSynchroniser = __decorate( [ __param(2, IFileService), __param(3, IEnvironmentService), __param(4, IStorageService), __param(5, IUserDataSyncStoreService), __param(6, IUserDataSyncBackupStoreService), __param(7, IUserDataSyncEnablementService), __param(8, ITelemetryService), __param(9, IUserDataSyncLogService), __param(10, IConfigurationService), __param(11, IUriIdentityService), ], AbstractSynchroniser, ); export { AbstractSynchroniser }; let AbstractFileSynchroniser = class AbstractFileSynchroniser extends AbstractSynchroniser { file; constructor( file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService, ) { super( syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService, ); this.file = file; this._register(this.fileService.watch(this.extUri.dirname(file))); this._register(this.fileService.onDidFilesChange((e) => this.onFileChanges(e))); } async getLocalFileContent() { try { return await this.fileService.readFile(this.file); } catch (error) { return null; } } async updateLocalFileContent(newContent, oldContent, force) { try { if (oldContent) { // file exists already await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), force ? undefined : oldContent); } else { // file does not exist await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: force }); } } catch (e) { if ( (e instanceof FileOperationError && e.fileOperationResult === 1) /* FileOperationResult.FILE_NOT_FOUND */ || (e instanceof FileOperationError && e.fileOperationResult === 3) /* FileOperationResult.FILE_MODIFIED_SINCE */ ) { throw new UserDataSyncError( e.message, 'LocalPreconditionFailed' /* UserDataSyncErrorCode.LocalPreconditionFailed */, ); } else { throw e; } } } async deleteLocalFile() { try { await this.fileService.del(this.file); } catch (e) { if ( !((e instanceof FileOperationError && e.fileOperationResult === 1) /* FileOperationResult.FILE_NOT_FOUND */) ) { throw e; } } } onFileChanges(e) { if (!e.contains(this.file)) { return; } this.triggerLocalChange(); } }; AbstractFileSynchroniser = __decorate( [ __param(3, IFileService), __param(4, IEnvironmentService), __param(5, IStorageService), __param(6, IUserDataSyncStoreService), __param(7, IUserDataSyncBackupStoreService), __param(8, IUserDataSyncEnablementService), __param(9, ITelemetryService), __param(10, IUserDataSyncLogService), __param(11, IConfigurationService), __param(12, IUriIdentityService), ], AbstractFileSynchroniser, ); export { AbstractFileSynchroniser }; let AbstractJsonFileSynchroniser = class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser { userDataSyncUtilService; constructor( file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService, ) { super( file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService, ); this.userDataSyncUtilService = userDataSyncUtilService; } hasErrors(content, isArray) { const parseErrors = []; const result = parse(content, parseErrors, { allowEmptyContent: true, allowTrailingComma: true }); return parseErrors.length > 0 || (!isUndefined(result) && isArray !== Array.isArray(result)); } _formattingOptions = undefined; getFormattingOptions() { if (!this._formattingOptions) { this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.file); } return this._formattingOptions; } }; AbstractJsonFileSynchroniser = __decorate( [ __param(3, IFileService), __param(4, IEnvironmentService), __param(5, IStorageService), __param(6, IUserDataSyncStoreService), __param(7, IUserDataSyncBackupStoreService), __param(8, IUserDataSyncEnablementService), __param(9, ITelemetryService), __param(10, IUserDataSyncLogService), __param(11, IUserDataSyncUtilService), __param(12, IConfigurationService), __param(13, IUriIdentityService), ], AbstractJsonFileSynchroniser, ); export { AbstractJsonFileSynchroniser }; let AbstractInitializer = class AbstractInitializer { resource; userDataProfilesService; environmentService; logService; fileService; storageService; extUri; lastSyncResource; constructor( resource, userDataProfilesService, environmentService, logService, fileService, storageService, uriIdentityService, ) { this.resource = resource; this.userDataProfilesService = userDataProfilesService; this.environmentService = environmentService; this.logService = logService; this.fileService = fileService; this.storageService = storageService; this.extUri = uriIdentityService.extUri; this.lastSyncResource = getLastSyncResourceUri(undefined, this.resource, environmentService, this.extUri); } async initialize({ ref, content }) { if (!content) { this.logService.info('Remote content does not exist.', this.resource); return; } const syncData = this.parseSyncData(content); if (!syncData) { return; } try { await this.doInitialize({ ref, syncData }); } catch (error) { this.logService.error(error); } } parseSyncData(content) { try { const syncData = JSON.parse(content); if (isSyncData(syncData)) { return syncData; } } catch (error) { this.logService.error(error); } this.logService.info('Cannot parse sync data as it is not compatible with the current version.', this.resource); return undefined; } async updateLastSyncUserData(lastSyncRemoteUserData, additionalProps = {}) { if (additionalProps['ref'] || additionalProps['version']) { throw new Error('Cannot have core properties as additional'); } const lastSyncUserDataState = { ref: lastSyncRemoteUserData.ref, version: undefined, ...additionalProps, }; this.storageService.store( `${this.resource}.lastSyncUserData`, JSON.stringify(lastSyncUserDataState), -1 /* StorageScope.APPLICATION */, 1 /* StorageTarget.MACHINE */, ); await this.fileService.writeFile( this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncRemoteUserData)), ); } }; AbstractInitializer = __decorate( [ __param(1, IUserDataProfilesService), __param(2, IEnvironmentService), __param(3, ILogService), __param(4, IFileService), __param(5, IStorageService), __param(6, IUriIdentityService), ], AbstractInitializer, ); export { AbstractInitializer };