UNPKG

@sussudio/platform

Version:

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

377 lines (376 loc) 15.3 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 { toFormattedString } from '@sussudio/base/common/jsonFormatter.mjs'; import { IConfigurationService } from '../../configuration/common/configuration.mjs'; import { IEnvironmentService } from '../../environment/common/environment.mjs'; import { IFileService } from '../../files/common/files.mjs'; import { IStorageService } from '../../storage/common/storage.mjs'; import { ITelemetryService } from '../../telemetry/common/telemetry.mjs'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.mjs'; import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.mjs'; import { AbstractSynchroniser } from './abstractSynchronizer.mjs'; import { merge } from './userDataProfilesManifestMerge.mjs'; import { IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, USER_DATA_SYNC_SCHEME, UserDataSyncError, } from './userDataSync.mjs'; let UserDataProfilesManifestSynchroniser = class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser { userDataProfilesService; version = 1; previewResource = this.extUri.joinPath(this.syncPreviewFolder, 'profiles.json'); baseResource = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); localResource = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }); remoteResource = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }); acceptedResource = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }); constructor( profile, collection, userDataProfilesService, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, logService, configurationService, userDataSyncEnablementService, telemetryService, uriIdentityService, ) { super( { syncResource: 'profiles' /* SyncResource.Profiles */, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService, ); this.userDataProfilesService = userDataProfilesService; } async getLastSyncedProfiles() { const lastSyncUserData = await this.getLastSyncUserData(); return lastSyncUserData?.syncData ? parseUserDataProfilesManifest(lastSyncUserData.syncData) : null; } async getRemoteSyncedProfiles(manifest) { const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData); return remoteUserData?.syncData ? parseUserDataProfilesManifest(remoteUserData.syncData) : null; } async generateSyncPreview(remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine) { if (!this.userDataProfilesService.isEnabled()) { throw new UserDataSyncError( 'Cannot sync profiles because they are disabled', 'LocalError' /* UserDataSyncErrorCode.LocalError */, ); } const remoteProfiles = remoteUserData.syncData ? parseUserDataProfilesManifest(remoteUserData.syncData) : null; const lastSyncProfiles = lastSyncUserData?.syncData ? parseUserDataProfilesManifest(lastSyncUserData.syncData) : null; const localProfiles = this.getLocalUserDataProfiles(); const { local, remote } = merge(localProfiles, remoteProfiles, lastSyncProfiles, []); const previewResult = { local, remote, content: lastSyncProfiles ? this.stringifyRemoteProfiles(lastSyncProfiles) : null, localChange: local.added.length > 0 || local.removed.length > 0 || local.updated.length > 0 ? 2 /* Change.Modified */ : 0 /* Change.None */, remoteChange: remote !== null ? 2 /* Change.Modified */ : 0 /* Change.None */, }; const localContent = stringifyLocalProfiles(localProfiles, false); return [ { baseResource: this.baseResource, baseContent: lastSyncProfiles ? this.stringifyRemoteProfiles(lastSyncProfiles) : null, localResource: this.localResource, localContent, remoteResource: this.remoteResource, remoteContent: remoteProfiles ? this.stringifyRemoteProfiles(remoteProfiles) : null, remoteProfiles, previewResource: this.previewResource, previewResult, localChange: previewResult.localChange, remoteChange: previewResult.remoteChange, acceptedResource: this.acceptedResource, }, ]; } async hasRemoteChanged(lastSyncUserData) { const lastSyncProfiles = lastSyncUserData?.syncData ? parseUserDataProfilesManifest(lastSyncUserData.syncData) : null; const localProfiles = this.getLocalUserDataProfiles(); const { remote } = merge(localProfiles, lastSyncProfiles, lastSyncProfiles, []); return !!remote?.added.length || !!remote?.removed.length || !!remote?.updated.length; } async getMergeResult(resourcePreview, token) { return { ...resourcePreview.previewResult, hasConflicts: false }; } async getAcceptResult(resourcePreview, resource, content, token) { /* Accept local resource */ if (this.extUri.isEqual(resource, this.localResource)) { return this.acceptLocal(resourcePreview); } /* Accept remote resource */ if (this.extUri.isEqual(resource, this.remoteResource)) { return this.acceptRemote(resourcePreview); } /* Accept preview resource */ if (this.extUri.isEqual(resource, this.previewResource)) { return resourcePreview.previewResult; } throw new Error(`Invalid Resource: ${resource.toString()}`); } async acceptLocal(resourcePreview) { const localProfiles = this.getLocalUserDataProfiles(); const mergeResult = merge(localProfiles, null, null, []); const { local, remote } = mergeResult; return { content: resourcePreview.localContent, local, remote, localChange: local.added.length > 0 || local.removed.length > 0 || local.updated.length > 0 ? 2 /* Change.Modified */ : 0 /* Change.None */, remoteChange: remote !== null ? 2 /* Change.Modified */ : 0 /* Change.None */, }; } async acceptRemote(resourcePreview) { const remoteProfiles = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null; const lastSyncProfiles = []; const localProfiles = []; for (const profile of this.getLocalUserDataProfiles()) { const remoteProfile = remoteProfiles?.find((remoteProfile) => remoteProfile.id === profile.id); if (remoteProfile) { lastSyncProfiles.push({ id: profile.id, name: profile.name, collection: remoteProfile.collection }); localProfiles.push(profile); } } if (remoteProfiles !== null) { const mergeResult = merge(localProfiles, remoteProfiles, lastSyncProfiles, []); const { local, remote } = mergeResult; return { content: resourcePreview.remoteContent, local, remote, localChange: local.added.length > 0 || local.removed.length > 0 || local.updated.length > 0 ? 2 /* Change.Modified */ : 0 /* Change.None */, remoteChange: remote !== null ? 2 /* Change.Modified */ : 0 /* Change.None */, }; } else { return { content: resourcePreview.remoteContent, local: { added: [], removed: [], updated: [] }, remote: null, localChange: 0 /* Change.None */, remoteChange: 0 /* Change.None */, }; } } async applyResult(remoteUserData, lastSyncUserData, resourcePreviews, force) { const { local, remote, localChange, remoteChange } = resourcePreviews[0][1]; if (localChange === 0 /* Change.None */ && remoteChange === 0 /* Change.None */) { this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing profiles.`); } const remoteProfiles = resourcePreviews[0][0].remoteProfiles || []; if (remoteProfiles.length + (remote?.added.length ?? 0) - (remote?.removed.length ?? 0) > 20) { throw new UserDataSyncError( 'Too many profiles to sync. Please remove some profiles and try again.', 'LocalTooManyProfiles' /* UserDataSyncErrorCode.LocalTooManyProfiles */, ); } if (localChange !== 0 /* Change.None */) { await this.backupLocal(stringifyLocalProfiles(this.getLocalUserDataProfiles(), false)); const promises = []; for (const profile of local.added) { promises.push( (async () => { this.logService.trace(`${this.syncResourceLogLabel}: Creating '${profile.name}' profile...`); await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName, }); this.logService.info(`${this.syncResourceLogLabel}: Created profile '${profile.name}'.`); })(), ); } for (const profile of local.removed) { promises.push( (async () => { this.logService.trace(`${this.syncResourceLogLabel}: Removing '${profile.name}' profile...`); await this.userDataProfilesService.removeProfile(profile); this.logService.info(`${this.syncResourceLogLabel}: Removed profile '${profile.name}'.`); })(), ); } for (const profile of local.updated) { const localProfile = this.userDataProfilesService.profiles.find((p) => p.id === profile.id); if (localProfile) { promises.push( (async () => { this.logService.trace(`${this.syncResourceLogLabel}: Updating '${profile.name}' profile...`); await this.userDataProfilesService.updateProfile(localProfile, { name: profile.name, shortName: profile.shortName, }); this.logService.info(`${this.syncResourceLogLabel}: Updated profile '${profile.name}'.`); })(), ); } else { this.logService.info( `${this.syncResourceLogLabel}: Could not find profile with id '${profile.id}' to update.`, ); } } await Promise.all(promises); } if (remoteChange !== 0 /* Change.None */) { this.logService.trace(`${this.syncResourceLogLabel}: Updating remote profiles...`); const addedCollections = []; const canAddRemoteProfiles = remoteProfiles.length + (remote?.added.length ?? 0) <= 20; if (canAddRemoteProfiles) { for (const profile of remote?.added || []) { const collection = await this.userDataSyncStoreService.createCollection(this.syncHeaders); addedCollections.push(collection); remoteProfiles.push({ id: profile.id, name: profile.name, collection, shortName: profile.shortName }); } } else { this.logService.info( `${this.syncResourceLogLabel}: Could not create remote profiles as there are too many profiles.`, ); } for (const profile of remote?.removed || []) { remoteProfiles.splice( remoteProfiles.findIndex(({ id }) => profile.id === id), 1, ); } for (const profile of remote?.updated || []) { const profileToBeUpdated = remoteProfiles.find(({ id }) => profile.id === id); if (profileToBeUpdated) { remoteProfiles.splice(remoteProfiles.indexOf(profileToBeUpdated), 1, { id: profile.id, name: profile.name, collection: profileToBeUpdated.collection, shortName: profile.shortName, }); } } try { remoteUserData = await this.updateRemoteProfiles(remoteProfiles, force ? null : remoteUserData.ref); this.logService.info( `${this.syncResourceLogLabel}: Updated remote profiles.${ canAddRemoteProfiles && remote?.added.length ? ` Added: ${JSON.stringify(remote.added.map((e) => e.name))}.` : '' }${remote?.updated.length ? ` Updated: ${JSON.stringify(remote.updated.map((e) => e.name))}.` : ''}${ remote?.removed.length ? ` Removed: ${JSON.stringify(remote.removed.map((e) => e.name))}.` : '' }`, ); } catch (error) { if (addedCollections.length) { this.logService.info( `${this.syncResourceLogLabel}: Failed to update remote profiles. Cleaning up added collections...`, ); for (const collection of addedCollections) { await this.userDataSyncStoreService.deleteCollection(collection, this.syncHeaders); } } throw error; } for (const profile of remote?.removed || []) { await this.userDataSyncStoreService.deleteCollection(profile.collection, this.syncHeaders); } } if (lastSyncUserData?.ref !== remoteUserData.ref) { // update last sync this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized profiles...`); await this.updateLastSyncUserData(remoteUserData); this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized profiles.`); } } async updateRemoteProfiles(profiles, ref) { return this.updateRemoteUserData(this.stringifyRemoteProfiles(profiles), ref); } async hasLocalData() { return this.getLocalUserDataProfiles().length > 0; } async resolveContent(uri) { if ( this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.baseResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri) ) { const content = await this.resolvePreviewContent(uri); return content ? toFormattedString(JSON.parse(content), {}) : content; } return null; } getLocalUserDataProfiles() { return this.userDataProfilesService.profiles.filter((p) => !p.isDefault && !p.isTransient); } stringifyRemoteProfiles(profiles) { return JSON.stringify([...profiles].sort((a, b) => a.name.localeCompare(b.name))); } }; UserDataProfilesManifestSynchroniser = __decorate( [ __param(2, IUserDataProfilesService), __param(3, IFileService), __param(4, IEnvironmentService), __param(5, IStorageService), __param(6, IUserDataSyncStoreService), __param(7, IUserDataSyncBackupStoreService), __param(8, IUserDataSyncLogService), __param(9, IConfigurationService), __param(10, IUserDataSyncEnablementService), __param(11, ITelemetryService), __param(12, IUriIdentityService), ], UserDataProfilesManifestSynchroniser, ); export { UserDataProfilesManifestSynchroniser }; export function stringifyLocalProfiles(profiles, format) { const result = [...profiles].sort((a, b) => a.name.localeCompare(b.name)).map((p) => ({ id: p.id, name: p.name })); return format ? toFormattedString(result, {}) : JSON.stringify(result); } export function parseUserDataProfilesManifest(syncData) { return JSON.parse(syncData.content); }