sussudio
Version:
An unofficial VS Code Internal API
845 lines (844 loc) • 44.8 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 "../../../base/common/arrays.mjs";
import { createCancelablePromise, ThrottledDelayer } from "../../../base/common/async.mjs";
import { VSBuffer } from "../../../base/common/buffer.mjs";
import { CancellationToken } from "../../../base/common/cancellation.mjs";
import { Emitter } from "../../../base/common/event.mjs";
import { parse } from "../../../base/common/json.mjs";
import { Disposable } from "../../../base/common/lifecycle.mjs";
import { uppercaseFirstLetter } from "../../../base/common/strings.mjs";
import { isUndefined } from "../../../base/common/types.mjs";
import { localize } from "../../../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 */
});
}
/* Changed -> Apply ? (Merge ? Conflict | Accept) : Preview */
else {
/* 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 };