@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
975 lines (974 loc) • 35.7 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 '@sussudio/base/common/arrays.mjs';
import { createCancelablePromise } from '@sussudio/base/common/async.mjs';
import { CancellationTokenSource } from '@sussudio/base/common/cancellation.mjs';
import { toErrorMessage } from '@sussudio/base/common/errorMessage.mjs';
import { Emitter } from '@sussudio/base/common/event.mjs';
import { Disposable, DisposableStore, toDisposable } from '@sussudio/base/common/lifecycle.mjs';
import { isEqual } from '@sussudio/base/common/resources.mjs';
import { isBoolean, isUndefined } from '@sussudio/base/common/types.mjs';
import { generateUuid } from '@sussudio/base/common/uuid.mjs';
import { IConfigurationService } from '../../configuration/common/configuration.mjs';
import { IExtensionGalleryService } from '../../extensionManagement/common/extensionManagement.mjs';
import { IInstantiationService } from '../../instantiation/common/instantiation.mjs';
import { IStorageService } from '../../storage/common/storage.mjs';
import { ITelemetryService } from '../../telemetry/common/telemetry.mjs';
import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.mjs';
import { ExtensionsSynchroniser } from './extensionsSync.mjs';
import { GlobalStateSynchroniser } from './globalStateSync.mjs';
import { KeybindingsSynchroniser } from './keybindingsSync.mjs';
import { SettingsSynchroniser } from './settingsSync.mjs';
import { SnippetsSynchroniser } from './snippetsSync.mjs';
import { TasksSynchroniser } from './tasksSync.mjs';
import { UserDataProfilesManifestSynchroniser } from './userDataProfilesManifestSync.mjs';
import {
ALL_SYNC_RESOURCES,
createSyncHeaders,
IUserDataSyncEnablementService,
IUserDataSyncLogService,
IUserDataSyncStoreManagementService,
IUserDataSyncStoreService,
UserDataSyncError,
UserDataSyncStoreError,
USER_DATA_SYNC_CONFIGURATION_SCOPE,
IUserDataSyncResourceProviderService,
} from './userDataSync.mjs';
const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime';
let UserDataSyncService = class UserDataSyncService extends Disposable {
userDataSyncStoreService;
userDataSyncStoreManagementService;
instantiationService;
logService;
telemetryService;
storageService;
userDataSyncEnablementService;
userDataProfilesService;
userDataSyncResourceProviderService;
_serviceBrand;
_status = 'uninitialized' /* SyncStatus.Uninitialized */;
get status() {
return this._status;
}
_onDidChangeStatus = this._register(new Emitter());
onDidChangeStatus = this._onDidChangeStatus.event;
_onDidChangeLocal = this._register(new Emitter());
onDidChangeLocal = this._onDidChangeLocal.event;
_conflicts = [];
get conflicts() {
return this._conflicts;
}
_onDidChangeConflicts = this._register(new Emitter());
onDidChangeConflicts = this._onDidChangeConflicts.event;
_syncErrors = [];
_onSyncErrors = this._register(new Emitter());
onSyncErrors = this._onSyncErrors.event;
_lastSyncTime = undefined;
get lastSyncTime() {
return this._lastSyncTime;
}
_onDidChangeLastSyncTime = this._register(new Emitter());
onDidChangeLastSyncTime = this._onDidChangeLastSyncTime.event;
_onDidResetLocal = this._register(new Emitter());
onDidResetLocal = this._onDidResetLocal.event;
_onDidResetRemote = this._register(new Emitter());
onDidResetRemote = this._onDidResetRemote.event;
activeProfileSynchronizers = new Map();
constructor(
userDataSyncStoreService,
userDataSyncStoreManagementService,
instantiationService,
logService,
telemetryService,
storageService,
userDataSyncEnablementService,
userDataProfilesService,
userDataSyncResourceProviderService,
) {
super();
this.userDataSyncStoreService = userDataSyncStoreService;
this.userDataSyncStoreManagementService = userDataSyncStoreManagementService;
this.instantiationService = instantiationService;
this.logService = logService;
this.telemetryService = telemetryService;
this.storageService = storageService;
this.userDataSyncEnablementService = userDataSyncEnablementService;
this.userDataProfilesService = userDataProfilesService;
this.userDataSyncResourceProviderService = userDataSyncResourceProviderService;
this._status = userDataSyncStoreManagementService.userDataSyncStore
? 'idle' /* SyncStatus.Idle */
: 'uninitialized' /* SyncStatus.Uninitialized */;
this._lastSyncTime = this.storageService.getNumber(
LAST_SYNC_TIME_KEY,
-1 /* StorageScope.APPLICATION */,
undefined,
);
this._register(toDisposable(() => this.clearActiveProfileSynchronizers()));
}
async createSyncTask(manifest, disableCache) {
this.checkEnablement();
this.logService.info('Sync started.');
const startTime = new Date().getTime();
const executionId = generateUuid();
try {
const syncHeaders = createSyncHeaders(executionId);
if (disableCache) {
syncHeaders['Cache-Control'] = 'no-cache';
}
manifest = await this.userDataSyncStoreService.manifest(manifest, syncHeaders);
} catch (error) {
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
reportUserDataSyncError(
userDataSyncError,
executionId,
this.userDataSyncStoreManagementService,
this.telemetryService,
);
throw userDataSyncError;
}
const executed = false;
const that = this;
let cancellablePromise;
return {
manifest,
async run() {
if (executed) {
throw new Error('Can run a task only once');
}
cancellablePromise = createCancelablePromise((token) => that.sync(manifest, false, executionId, token));
await cancellablePromise.finally(() => (cancellablePromise = undefined));
that.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
that.updateLastSyncTime();
},
stop() {
cancellablePromise?.cancel();
return that.stop();
},
};
}
async createManualSyncTask() {
this.checkEnablement();
if (this.userDataSyncEnablementService.isEnabled()) {
throw new UserDataSyncError(
'Cannot start manual sync when sync is enabled',
'LocalError' /* UserDataSyncErrorCode.LocalError */,
);
}
this.logService.info('Sync started.');
const startTime = new Date().getTime();
const executionId = generateUuid();
const syncHeaders = createSyncHeaders(executionId);
let manifest;
try {
manifest = await this.userDataSyncStoreService.manifest(null, syncHeaders);
} catch (error) {
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
reportUserDataSyncError(
userDataSyncError,
executionId,
this.userDataSyncStoreManagementService,
this.telemetryService,
);
throw userDataSyncError;
}
/* Manual sync shall start on clean local state */
await this.resetLocal();
const that = this;
const cancellableToken = new CancellationTokenSource();
return {
id: executionId,
async merge() {
return that.sync(manifest, true, executionId, cancellableToken.token);
},
async apply() {
try {
try {
await that.applyManualSync(manifest, executionId, cancellableToken.token);
} catch (error) {
if (
UserDataSyncError.toUserDataSyncError(error).code ===
'MethodNotFound' /* UserDataSyncErrorCode.MethodNotFound */
) {
that.logService.info('Client is making invalid requests. Cleaning up data...');
await that.cleanUpRemoteData();
that.logService.info('Applying manual sync again...');
await that.applyManualSync(manifest, executionId, cancellableToken.token);
} else {
throw error;
}
}
} catch (error) {
that.logService.error(error);
throw error;
}
that.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
that.updateLastSyncTime();
},
async stop() {
cancellableToken.cancel();
await that.stop();
await that.resetLocal();
},
};
}
async sync(manifest, merge, executionId, token) {
this._syncErrors = [];
try {
if (this.status !== 'hasConflicts' /* SyncStatus.HasConflicts */) {
this.setStatus('syncing' /* SyncStatus.Syncing */);
}
// Sync Default Profile First
const defaultProfileSynchronizer = this.getOrCreateActiveProfileSynchronizer(
this.userDataProfilesService.defaultProfile,
undefined,
);
this._syncErrors.push(
...(await this.syncProfile(defaultProfileSynchronizer, manifest, merge, executionId, token)),
);
// Sync other profiles
const userDataProfileManifestSynchronizer = defaultProfileSynchronizer.enabled.find(
(s) => s.resource === 'profiles' /* SyncResource.Profiles */,
);
if (userDataProfileManifestSynchronizer) {
const syncProfiles = (await userDataProfileManifestSynchronizer.getLastSyncedProfiles()) || [];
if (token.isCancellationRequested) {
return;
}
await this.syncRemoteProfiles(syncProfiles, manifest, merge, executionId, token);
}
} finally {
this._onSyncErrors.fire(this._syncErrors);
}
}
async syncRemoteProfiles(remoteProfiles, manifest, merge, executionId, token) {
for (const syncProfile of remoteProfiles) {
if (token.isCancellationRequested) {
return;
}
const profile = this.userDataProfilesService.profiles.find((p) => p.id === syncProfile.id);
if (!profile) {
this.logService.error(
`Profile with id:${syncProfile.id} and name: ${syncProfile.name} does not exist locally to sync.`,
);
continue;
}
this.logService.info('Syncing profile.', syncProfile.name);
const profileSynchronizer = this.getOrCreateActiveProfileSynchronizer(profile, syncProfile);
this._syncErrors.push(...(await this.syncProfile(profileSynchronizer, manifest, merge, executionId, token)));
}
// Dispose & Delete profile synchronizers which do not exist anymore
for (const [key, profileSynchronizerItem] of this.activeProfileSynchronizers.entries()) {
if (this.userDataProfilesService.profiles.some((p) => p.id === profileSynchronizerItem[0].profile.id)) {
continue;
}
profileSynchronizerItem[1].dispose();
this.activeProfileSynchronizers.delete(key);
}
}
async applyManualSync(manifest, executionId, token) {
const profileSynchronizers = this.getActiveProfileSynchronizers();
for (const profileSynchronizer of profileSynchronizers) {
if (token.isCancellationRequested) {
return;
}
await profileSynchronizer.apply(executionId, token);
}
const defaultProfileSynchronizer = profileSynchronizers.find((s) => s.profile.isDefault);
if (!defaultProfileSynchronizer) {
return;
}
const userDataProfileManifestSynchronizer = defaultProfileSynchronizer.enabled.find(
(s) => s.resource === 'profiles' /* SyncResource.Profiles */,
);
if (!userDataProfileManifestSynchronizer) {
return;
}
// Sync remote profiles which are not synced locally
const remoteProfiles =
(await userDataProfileManifestSynchronizer.getRemoteSyncedProfiles(manifest?.latest ?? null)) || [];
const remoteProfilesToSync = remoteProfiles.filter((remoteProfile) =>
profileSynchronizers.every((s) => s.profile.id !== remoteProfile.id),
);
if (remoteProfilesToSync.length) {
await this.syncRemoteProfiles(remoteProfilesToSync, manifest, false, executionId, token);
}
}
async syncProfile(profileSynchronizer, manifest, merge, executionId, token) {
const errors = await profileSynchronizer.sync(manifest, merge, executionId, token);
return errors.map(([syncResource, error]) => ({ profile: profileSynchronizer.profile, syncResource, error }));
}
async stop() {
if (this.status !== 'idle' /* SyncStatus.Idle */) {
await Promise.allSettled(
this.getActiveProfileSynchronizers().map((profileSynchronizer) => profileSynchronizer.stop()),
);
}
}
async resolveContent(resource) {
const content = await this.userDataSyncResourceProviderService.resolveContent(resource);
if (content) {
return content;
}
for (const profileSynchronizer of this.getActiveProfileSynchronizers()) {
for (const synchronizer of profileSynchronizer.enabled) {
const content = await synchronizer.resolveContent(resource);
if (content) {
return content;
}
}
}
return null;
}
async replace(syncResourceHandle) {
this.checkEnablement();
const profileSyncResource =
this.userDataSyncResourceProviderService.resolveUserDataSyncResource(syncResourceHandle);
if (!profileSyncResource) {
return;
}
const content = await this.resolveContent(syncResourceHandle.uri);
if (!content) {
return;
}
await this.performAction(profileSyncResource.profile, async (synchronizer) => {
if (profileSyncResource.syncResource === synchronizer.resource) {
await synchronizer.replace(content);
return true;
}
return undefined;
});
return;
}
async accept(syncResource, resource, content, apply) {
this.checkEnablement();
await this.performAction(syncResource.profile, async (synchronizer) => {
if (syncResource.syncResource === synchronizer.resource) {
await synchronizer.accept(resource, content);
if (apply) {
await synchronizer.apply(isBoolean(apply) ? false : apply.force, createSyncHeaders(generateUuid()));
}
return true;
}
return undefined;
});
}
getRemoteProfiles() {
return this.userDataSyncResourceProviderService.getRemoteSyncedProfiles();
}
getRemoteSyncResourceHandles(syncResource, profile) {
return this.userDataSyncResourceProviderService.getRemoteSyncResourceHandles(syncResource, profile);
}
async getLocalSyncResourceHandles(syncResource, profile) {
return this.userDataSyncResourceProviderService.getLocalSyncResourceHandles(
syncResource,
profile ?? this.userDataProfilesService.defaultProfile,
);
}
async getAssociatedResources(syncResourceHandle) {
return this.userDataSyncResourceProviderService.getAssociatedResources(syncResourceHandle);
}
async getMachineId(syncResourceHandle) {
return this.userDataSyncResourceProviderService.getMachineId(syncResourceHandle);
}
async hasLocalData() {
const result = await this.performAction(this.userDataProfilesService.defaultProfile, async (synchronizer) => {
// skip global state synchronizer
if (
synchronizer.resource !== 'globalState' /* SyncResource.GlobalState */ &&
(await synchronizer.hasLocalData())
) {
return true;
}
return undefined;
});
return !!result;
}
async hasPreviouslySynced() {
const result = await this.performAction(this.userDataProfilesService.defaultProfile, async (synchronizer) => {
if (await synchronizer.hasPreviouslySynced()) {
return true;
}
return undefined;
});
return !!result;
}
async reset() {
this.checkEnablement();
await this.resetRemote();
await this.resetLocal();
}
async resetRemote() {
this.checkEnablement();
try {
await this.userDataSyncStoreService.clear();
this.logService.info('Cleared data on server');
} catch (e) {
this.logService.error(e);
}
this._onDidResetRemote.fire();
}
async resetLocal() {
this.checkEnablement();
this._lastSyncTime = undefined;
this.storageService.remove(LAST_SYNC_TIME_KEY, -1 /* StorageScope.APPLICATION */);
for (const [synchronizer] of this.activeProfileSynchronizers.values()) {
try {
await synchronizer.resetLocal();
} catch (e) {
this.logService.error(e);
}
}
this.clearActiveProfileSynchronizers();
this._onDidResetLocal.fire();
this.logService.info('Did reset the local sync state.');
}
async cleanUpRemoteData() {
const remoteProfiles = await this.userDataSyncResourceProviderService.getRemoteSyncedProfiles();
const remoteProfileCollections = remoteProfiles.map((profile) => profile.collection);
const allCollections = await this.userDataSyncStoreService.getAllCollections();
const redundantCollections = allCollections.filter((c) => !remoteProfileCollections.includes(c));
if (redundantCollections.length) {
this.logService.info(`Deleting ${redundantCollections.length} redundant collections on server`);
await Promise.allSettled(
redundantCollections.map((collectionId) => this.userDataSyncStoreService.deleteCollection(collectionId)),
);
this.logService.info(`Deleted redundant collections on server`);
}
const updatedRemoteProfiles = remoteProfiles.filter((profile) => allCollections.includes(profile.collection));
if (updatedRemoteProfiles.length !== remoteProfiles.length) {
this.logService.info(`Updating remote profiles with invalid collections on server`);
const profileManifestSynchronizer = this.instantiationService.createInstance(
UserDataProfilesManifestSynchroniser,
this.userDataProfilesService.defaultProfile,
undefined,
);
try {
await profileManifestSynchronizer.updateRemoteProfiles(updatedRemoteProfiles, null);
this.logService.info(`Updated remote profiles on server`);
} finally {
profileManifestSynchronizer.dispose();
}
}
}
async performAction(profile, action) {
const disposables = new DisposableStore();
try {
const activeProfileSyncronizer = this.activeProfileSynchronizers.get(profile.id);
if (activeProfileSyncronizer) {
const result = await this.performActionWithProfileSynchronizer(
activeProfileSyncronizer[0],
action,
disposables,
);
return isUndefined(result) ? null : result;
}
if (profile.isDefault) {
const defaultProfileSynchronizer = disposables.add(
this.instantiationService.createInstance(ProfileSynchronizer, profile, undefined),
);
const result = await this.performActionWithProfileSynchronizer(defaultProfileSynchronizer, action, disposables);
return isUndefined(result) ? null : result;
}
if (this.userDataProfilesService.isEnabled()) {
return null;
}
const userDataProfileManifestSynchronizer = disposables.add(
this.instantiationService.createInstance(UserDataProfilesManifestSynchroniser, profile, undefined),
);
const manifest = await this.userDataSyncStoreService.manifest(null);
const syncProfiles =
(await userDataProfileManifestSynchronizer.getRemoteSyncedProfiles(manifest?.latest ?? null)) || [];
const syncProfile = syncProfiles.find((syncProfile) => syncProfile.id === profile.id);
if (syncProfile) {
const profileSynchronizer = disposables.add(
this.instantiationService.createInstance(ProfileSynchronizer, profile, syncProfile.collection),
);
const result = await this.performActionWithProfileSynchronizer(profileSynchronizer, action, disposables);
return isUndefined(result) ? null : result;
}
return null;
} finally {
disposables.dispose();
}
}
async performActionWithProfileSynchronizer(profileSynchronizer, action, disposables) {
const allSynchronizers = [
...profileSynchronizer.enabled,
...profileSynchronizer.disabled.map((syncResource) =>
disposables.add(profileSynchronizer.createSynchronizer(syncResource)),
),
];
for (const synchronizer of allSynchronizers) {
const result = await action(synchronizer);
if (!isUndefined(result)) {
return result;
}
}
return undefined;
}
setStatus(status) {
const oldStatus = this._status;
if (this._status !== status) {
this._status = status;
this._onDidChangeStatus.fire(status);
if (oldStatus === 'hasConflicts' /* SyncStatus.HasConflicts */) {
this.updateLastSyncTime();
}
}
}
updateConflicts() {
const conflicts = this.getActiveProfileSynchronizers()
.map((synchronizer) => synchronizer.conflicts)
.flat();
if (
!equals(
this._conflicts,
conflicts,
(a, b) =>
a.profile.id === b.profile.id &&
a.syncResource === b.syncResource &&
equals(a.conflicts, b.conflicts, (a, b) => isEqual(a.previewResource, b.previewResource)),
)
) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
}
}
updateLastSyncTime() {
if (this.status === 'idle' /* SyncStatus.Idle */) {
this._lastSyncTime = new Date().getTime();
this.storageService.store(
LAST_SYNC_TIME_KEY,
this._lastSyncTime,
-1 /* StorageScope.APPLICATION */,
1 /* StorageTarget.MACHINE */,
);
this._onDidChangeLastSyncTime.fire(this._lastSyncTime);
}
}
getOrCreateActiveProfileSynchronizer(profile, syncProfile) {
let activeProfileSynchronizer = this.activeProfileSynchronizers.get(profile.id);
if (activeProfileSynchronizer && activeProfileSynchronizer[0].collection !== syncProfile?.collection) {
this.logService.error('Profile synchronizer collection does not match with the remote sync profile collection');
activeProfileSynchronizer[1].dispose();
activeProfileSynchronizer = undefined;
this.activeProfileSynchronizers.delete(profile.id);
}
if (!activeProfileSynchronizer) {
const disposables = new DisposableStore();
const profileSynchronizer = disposables.add(
this.instantiationService.createInstance(ProfileSynchronizer, profile, syncProfile?.collection),
);
disposables.add(profileSynchronizer.onDidChangeStatus((e) => this.setStatus(e)));
disposables.add(profileSynchronizer.onDidChangeConflicts((conflicts) => this.updateConflicts()));
disposables.add(profileSynchronizer.onDidChangeLocal((e) => this._onDidChangeLocal.fire(e)));
this.activeProfileSynchronizers.set(profile.id, (activeProfileSynchronizer = [profileSynchronizer, disposables]));
}
return activeProfileSynchronizer[0];
}
getActiveProfileSynchronizers() {
const profileSynchronizers = [];
for (const [profileSynchronizer] of this.activeProfileSynchronizers.values()) {
profileSynchronizers.push(profileSynchronizer);
}
return profileSynchronizers;
}
clearActiveProfileSynchronizers() {
this.activeProfileSynchronizers.forEach(([, disposable]) => disposable.dispose());
this.activeProfileSynchronizers.clear();
}
checkEnablement() {
if (!this.userDataSyncStoreManagementService.userDataSyncStore) {
throw new Error('Not enabled');
}
}
};
UserDataSyncService = __decorate(
[
__param(0, IUserDataSyncStoreService),
__param(1, IUserDataSyncStoreManagementService),
__param(2, IInstantiationService),
__param(3, IUserDataSyncLogService),
__param(4, ITelemetryService),
__param(5, IStorageService),
__param(6, IUserDataSyncEnablementService),
__param(7, IUserDataProfilesService),
__param(8, IUserDataSyncResourceProviderService),
],
UserDataSyncService,
);
export { UserDataSyncService };
let ProfileSynchronizer = class ProfileSynchronizer extends Disposable {
profile;
collection;
userDataSyncEnablementService;
instantiationService;
extensionGalleryService;
userDataSyncStoreManagementService;
telemetryService;
logService;
userDataProfilesService;
configurationService;
_enabled = [];
get enabled() {
return this._enabled.sort((a, b) => a[1] - b[1]).map(([synchronizer]) => synchronizer);
}
get disabled() {
return ALL_SYNC_RESOURCES.filter(
(syncResource) => !this.userDataSyncEnablementService.isResourceEnabled(syncResource),
);
}
_status = 'idle' /* SyncStatus.Idle */;
get status() {
return this._status;
}
_onDidChangeStatus = this._register(new Emitter());
onDidChangeStatus = this._onDidChangeStatus.event;
_onDidChangeLocal = this._register(new Emitter());
onDidChangeLocal = this._onDidChangeLocal.event;
_conflicts = [];
get conflicts() {
return this._conflicts;
}
_onDidChangeConflicts = this._register(new Emitter());
onDidChangeConflicts = this._onDidChangeConflicts.event;
constructor(
profile,
collection,
userDataSyncEnablementService,
instantiationService,
extensionGalleryService,
userDataSyncStoreManagementService,
telemetryService,
logService,
userDataProfilesService,
configurationService,
) {
super();
this.profile = profile;
this.collection = collection;
this.userDataSyncEnablementService = userDataSyncEnablementService;
this.instantiationService = instantiationService;
this.extensionGalleryService = extensionGalleryService;
this.userDataSyncStoreManagementService = userDataSyncStoreManagementService;
this.telemetryService = telemetryService;
this.logService = logService;
this.userDataProfilesService = userDataProfilesService;
this.configurationService = configurationService;
this._register(
userDataSyncEnablementService.onDidChangeResourceEnablement(([syncResource, enablement]) =>
this.onDidChangeResourceEnablement(syncResource, enablement),
),
);
this._register(
toDisposable(() =>
this._enabled.splice(0, this._enabled.length).forEach(([, , disposable]) => disposable.dispose()),
),
);
for (const syncResource of ALL_SYNC_RESOURCES) {
if (userDataSyncEnablementService.isResourceEnabled(syncResource)) {
this.registerSynchronizer(syncResource);
}
}
}
onDidChangeResourceEnablement(syncResource, enabled) {
if (enabled) {
this.registerSynchronizer(syncResource);
} else {
this.deRegisterSynchronizer(syncResource);
}
}
registerSynchronizer(syncResource) {
if (this._enabled.some(([synchronizer]) => synchronizer.resource === syncResource)) {
return;
}
if (syncResource === 'extensions' /* SyncResource.Extensions */ && !this.extensionGalleryService.isEnabled()) {
this.logService.info('Skipping extensions sync because gallery is not configured');
return;
}
if (syncResource === 'profiles' /* SyncResource.Profiles */) {
if (!this.profile.isDefault) {
return;
}
if (!this.userDataProfilesService.isEnabled()) {
return;
}
}
const disposables = new DisposableStore();
const synchronizer = disposables.add(this.createSynchronizer(syncResource));
disposables.add(synchronizer.onDidChangeStatus(() => this.updateStatus()));
disposables.add(synchronizer.onDidChangeConflicts(() => this.updateConflicts()));
disposables.add(synchronizer.onDidChangeLocal(() => this._onDidChangeLocal.fire(syncResource)));
const order = this.getOrder(syncResource);
this._enabled.push([synchronizer, order, disposables]);
}
deRegisterSynchronizer(syncResource) {
const index = this._enabled.findIndex(([synchronizer]) => synchronizer.resource === syncResource);
if (index !== -1) {
const [[synchronizer, , disposable]] = this._enabled.splice(index, 1);
disposable.dispose();
this.updateStatus();
Promise.allSettled([synchronizer.stop(), synchronizer.resetLocal()]).then(null, (error) =>
this.logService.error(error),
);
}
}
createSynchronizer(syncResource) {
switch (syncResource) {
case 'settings' /* SyncResource.Settings */:
return this.instantiationService.createInstance(SettingsSynchroniser, this.profile, this.collection);
case 'keybindings' /* SyncResource.Keybindings */:
return this.instantiationService.createInstance(KeybindingsSynchroniser, this.profile, this.collection);
case 'snippets' /* SyncResource.Snippets */:
return this.instantiationService.createInstance(SnippetsSynchroniser, this.profile, this.collection);
case 'tasks' /* SyncResource.Tasks */:
return this.instantiationService.createInstance(TasksSynchroniser, this.profile, this.collection);
case 'globalState' /* SyncResource.GlobalState */:
return this.instantiationService.createInstance(GlobalStateSynchroniser, this.profile, this.collection);
case 'extensions' /* SyncResource.Extensions */:
return this.instantiationService.createInstance(ExtensionsSynchroniser, this.profile, this.collection);
case 'profiles' /* SyncResource.Profiles */:
return this.instantiationService.createInstance(
UserDataProfilesManifestSynchroniser,
this.profile,
this.collection,
);
}
}
async sync(manifest, merge, executionId, token) {
// Return if cancellation is requested
if (token.isCancellationRequested) {
return [];
}
const synchronizers = this.enabled;
if (!synchronizers.length) {
return [];
}
try {
const syncErrors = [];
const syncHeaders = createSyncHeaders(executionId);
const resourceManifest =
(this.collection ? manifest?.collections?.[this.collection]?.latest : manifest?.latest) ?? null;
const userDataSyncConfiguration = merge ? await this.getUserDataSyncConfiguration(resourceManifest) : {};
for (const synchroniser of synchronizers) {
// Return if cancellation is requested
if (token.isCancellationRequested) {
return [];
}
// Return if resource is not enabled
if (!this.userDataSyncEnablementService.isResourceEnabled(synchroniser.resource)) {
return [];
}
try {
if (merge) {
const preview = await synchroniser.preview(resourceManifest, userDataSyncConfiguration, syncHeaders);
if (preview) {
for (const resourcePreview of preview.resourcePreviews) {
if (
(resourcePreview.localChange !== 0 /* Change.None */ ||
resourcePreview.remoteChange !== 0) /* Change.None */ &&
resourcePreview.mergeState === 'preview' /* MergeState.Preview */
) {
await synchroniser.merge(resourcePreview.previewResource);
}
}
}
} else {
await synchroniser.sync(resourceManifest, syncHeaders);
}
} catch (e) {
const userDataSyncError = UserDataSyncError.toUserDataSyncError(e);
reportUserDataSyncError(
userDataSyncError,
executionId,
this.userDataSyncStoreManagementService,
this.telemetryService,
);
if (canBailout(e)) {
throw userDataSyncError;
}
// Log and and continue
this.logService.error(e);
this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`);
syncErrors.push([synchroniser.resource, userDataSyncError]);
}
}
return syncErrors;
} finally {
this.updateStatus();
}
}
async apply(executionId, token) {
const syncHeaders = createSyncHeaders(executionId);
for (const synchroniser of this.enabled) {
if (token.isCancellationRequested) {
return;
}
try {
await synchroniser.apply(false, syncHeaders);
} catch (e) {
const userDataSyncError = UserDataSyncError.toUserDataSyncError(e);
reportUserDataSyncError(
userDataSyncError,
executionId,
this.userDataSyncStoreManagementService,
this.telemetryService,
);
if (canBailout(e)) {
throw userDataSyncError;
}
// Log and and continue
this.logService.error(e);
this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`);
}
}
}
async stop() {
for (const synchroniser of this.enabled) {
try {
if (synchroniser.status !== 'idle' /* SyncStatus.Idle */) {
await synchroniser.stop();
}
} catch (e) {
this.logService.error(e);
}
}
}
async resetLocal() {
for (const synchroniser of this.enabled) {
try {
await synchroniser.resetLocal();
} catch (e) {
this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`);
this.logService.error(e);
}
}
}
async getUserDataSyncConfiguration(manifest) {
if (!this.profile.isDefault) {
return {};
}
const local = this.configurationService.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE);
const settingsSynchronizer = this.enabled.find((synchronizer) => synchronizer instanceof SettingsSynchroniser);
if (settingsSynchronizer) {
const remote = await settingsSynchronizer.getRemoteUserDataSyncConfiguration(manifest);
return { ...local, ...remote };
}
return local;
}
setStatus(status) {
if (this._status !== status) {
this._status = status;
this._onDidChangeStatus.fire(status);
}
}
updateStatus() {
this.updateConflicts();
if (this.enabled.some((s) => s.status === 'hasConflicts' /* SyncStatus.HasConflicts */)) {
return this.setStatus('hasConflicts' /* SyncStatus.HasConflicts */);
}
if (this.enabled.some((s) => s.status === 'syncing' /* SyncStatus.Syncing */)) {
return this.setStatus('syncing' /* SyncStatus.Syncing */);
}
return this.setStatus('idle' /* SyncStatus.Idle */);
}
updateConflicts() {
const conflicts = this.enabled
.filter((s) => s.status === 'hasConflicts' /* SyncStatus.HasConflicts */)
.filter((s) => s.conflicts.conflicts.length > 0)
.map((s) => s.conflicts);
if (
!equals(
this._conflicts,
conflicts,
(a, b) =>
a.syncResource === b.syncResource &&
equals(a.conflicts, b.conflicts, (a, b) => isEqual(a.previewResource, b.previewResource)),
)
) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
}
}
getOrder(syncResource) {
switch (syncResource) {
case 'settings' /* SyncResource.Settings */:
return 0;
case 'keybindings' /* SyncResource.Keybindings */:
return 1;
case 'snippets' /* SyncResource.Snippets */:
return 2;
case 'tasks' /* SyncResource.Tasks */:
return 3;
case 'globalState' /* SyncResource.GlobalState */:
return 4;
case 'extensions' /* SyncResource.Extensions */:
return 5;
case 'profiles' /* SyncResource.Profiles */:
return 6;
}
}
};
ProfileSynchronizer = __decorate(
[
__param(2, IUserDataSyncEnablementService),
__param(3, IInstantiationService),
__param(4, IExtensionGalleryService),
__param(5, IUserDataSyncStoreManagementService),
__param(6, ITelemetryService),
__param(7, IUserDataSyncLogService),
__param(8, IUserDataProfilesService),
__param(9, IConfigurationService),
],
ProfileSynchronizer,
);
function canBailout(e) {
if (e instanceof UserDataSyncError) {
switch (e.code) {
case 'MethodNotFound' /* UserDataSyncErrorCode.MethodNotFound */:
case 'TooLarge' /* UserDataSyncErrorCode.TooLarge */:
case 'RemoteTooManyRequests' /* UserDataSyncErrorCode.TooManyRequests */:
case 'TooManyRequestsAndRetryAfter' /* UserDataSyncErrorCode.TooManyRequestsAndRetryAfter */:
case 'LocalTooManyRequests' /* UserDataSyncErrorCode.LocalTooManyRequests */:
case 'LocalTooManyProfiles' /* UserDataSyncErrorCode.LocalTooManyProfiles */:
case 'Gone' /* UserDataSyncErrorCode.Gone */:
case 'UpgradeRequired' /* UserDataSyncErrorCode.UpgradeRequired */:
case 'IncompatibleRemoteContent' /* UserDataSyncErrorCode.IncompatibleRemoteContent */:
case 'IncompatibleLocalContent' /* UserDataSyncErrorCode.IncompatibleLocalContent */:
return true;
}
}
return false;
}
function reportUserDataSyncError(userDataSyncError, executionId, userDataSyncStoreManagementService, telemetryService) {
telemetryService.publicLog2('sync/error', {
code: userDataSyncError.code,
serverCode: userDataSyncError instanceof UserDataSyncStoreError ? String(userDataSyncError.serverCode) : undefined,
url: userDataSyncError instanceof UserDataSyncStoreError ? userDataSyncError.url : undefined,
resource: userDataSyncError.resource,
executionId,
service: userDataSyncStoreManagementService.userDataSyncStore.url.toString(),
});
}