@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
476 lines (475 loc) • 18.1 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 { VSBuffer } from '@sussudio/base/common/buffer.mjs';
import { Event } from '@sussudio/base/common/event.mjs';
import { localize } from 'vscode-nls.mjs';
import { IConfigurationService } from '../../configuration/common/configuration.mjs';
import { ConfigurationModelParser } from '../../configuration/common/configurationModels.mjs';
import { IEnvironmentService } from '../../environment/common/environment.mjs';
import { IExtensionManagementService } from '../../extensionManagement/common/extensionManagement.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 { AbstractInitializer, AbstractJsonFileSynchroniser } from './abstractSynchronizer.mjs';
import { edit } from './content.mjs';
import { getIgnoredSettings, isEmpty, merge, updateIgnoredSettings } from './settingsMerge.mjs';
import {
CONFIGURATION_SYNC_STORE_KEY,
IUserDataSyncBackupStoreService,
IUserDataSyncLogService,
IUserDataSyncEnablementService,
IUserDataSyncStoreService,
IUserDataSyncUtilService,
UserDataSyncError,
USER_DATA_SYNC_CONFIGURATION_SCOPE,
USER_DATA_SYNC_SCHEME,
} from './userDataSync.mjs';
function isSettingsSyncContent(thing) {
return thing && thing.settings && typeof thing.settings === 'string' && Object.keys(thing).length === 1;
}
export function parseSettingsSyncContent(syncContent) {
const parsed = JSON.parse(syncContent);
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
}
let SettingsSynchroniser = class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
extensionManagementService;
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
version = 2;
previewResource = this.extUri.joinPath(this.syncPreviewFolder, 'settings.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,
fileService,
environmentService,
storageService,
userDataSyncStoreService,
userDataSyncBackupStoreService,
logService,
userDataSyncUtilService,
configurationService,
userDataSyncEnablementService,
telemetryService,
extensionManagementService,
uriIdentityService,
) {
super(
profile.settingsResource,
{ syncResource: 'settings' /* SyncResource.Settings */, profile },
collection,
fileService,
environmentService,
storageService,
userDataSyncStoreService,
userDataSyncBackupStoreService,
userDataSyncEnablementService,
telemetryService,
logService,
userDataSyncUtilService,
configurationService,
uriIdentityService,
);
this.extensionManagementService = extensionManagementService;
}
async getRemoteUserDataSyncConfiguration(manifest) {
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData);
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
const parser = new ConfigurationModelParser(USER_DATA_SYNC_CONFIGURATION_SCOPE);
if (remoteSettingsSyncContent?.settings) {
parser.parse(remoteSettingsSyncContent.settings);
}
return parser.configurationModel.getValue(USER_DATA_SYNC_CONFIGURATION_SCOPE) || {};
}
async generateSyncPreview(remoteUserData, lastSyncUserData, isRemoteDataFromCurrentMachine) {
const fileContent = await this.getLocalFileContent();
const formattingOptions = await this.getFormattingOptions();
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
// Use remote data as last sync data if last sync data does not exist and remote data is from same machine
lastSyncUserData = lastSyncUserData === null && isRemoteDataFromCurrentMachine ? remoteUserData : lastSyncUserData;
const lastSettingsSyncContent = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null;
const ignoredSettings = await this.getIgnoredSettings();
let mergedContent = null;
let hasLocalChanged = false;
let hasRemoteChanged = false;
let hasConflicts = false;
if (remoteSettingsSyncContent) {
let localContent = fileContent ? fileContent.value.toString().trim() : '{}';
localContent = localContent || '{}';
this.validateContent(localContent);
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`);
const result = merge(
localContent,
remoteSettingsSyncContent.settings,
lastSettingsSyncContent ? lastSettingsSyncContent.settings : null,
ignoredSettings,
[],
formattingOptions,
);
mergedContent = result.localContent || result.remoteContent;
hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null;
hasConflicts = result.hasConflicts;
}
// First time syncing to remote
else if (fileContent) {
this.logService.trace(
`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`,
);
mergedContent = fileContent.value.toString().trim() || '{}';
this.validateContent(mergedContent);
hasRemoteChanged = true;
}
const localContent = fileContent ? fileContent.value.toString() : null;
const baseContent = lastSettingsSyncContent?.settings ?? null;
const previewResult = {
content: hasConflicts ? baseContent : mergedContent,
localChange: hasLocalChanged ? 2 /* Change.Modified */ : 0 /* Change.None */,
remoteChange: hasRemoteChanged ? 2 /* Change.Modified */ : 0 /* Change.None */,
hasConflicts,
};
return [
{
fileContent,
baseResource: this.baseResource,
baseContent,
localResource: this.localResource,
localContent,
localChange: previewResult.localChange,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
remoteChange: previewResult.remoteChange,
previewResource: this.previewResource,
previewResult,
acceptedResource: this.acceptedResource,
},
];
}
async hasRemoteChanged(lastSyncUserData) {
const lastSettingsSyncContent = this.getSettingsSyncContent(lastSyncUserData);
if (lastSettingsSyncContent === null) {
return true;
}
const fileContent = await this.getLocalFileContent();
const localContent = fileContent ? fileContent.value.toString().trim() : '';
const ignoredSettings = await this.getIgnoredSettings();
const formattingOptions = await this.getFormattingOptions();
const result = merge(
localContent || '{}',
lastSettingsSyncContent.settings,
lastSettingsSyncContent.settings,
ignoredSettings,
[],
formattingOptions,
);
return result.remoteContent !== null;
}
async getMergeResult(resourcePreview, token) {
const formatUtils = await this.getFormattingOptions();
const ignoredSettings = await this.getIgnoredSettings();
return {
...resourcePreview.previewResult,
// remove ignored settings from the preview content
content: resourcePreview.previewResult.content
? updateIgnoredSettings(resourcePreview.previewResult.content, '{}', ignoredSettings, formatUtils)
: null,
};
}
async getAcceptResult(resourcePreview, resource, content, token) {
const formattingOptions = await this.getFormattingOptions();
const ignoredSettings = await this.getIgnoredSettings();
/* Accept local resource */
if (this.extUri.isEqual(resource, this.localResource)) {
return {
/* Remove ignored settings */
content: resourcePreview.fileContent
? updateIgnoredSettings(
resourcePreview.fileContent.value.toString(),
'{}',
ignoredSettings,
formattingOptions,
)
: null,
localChange: 0 /* Change.None */,
remoteChange: 2 /* Change.Modified */,
};
}
/* Accept remote resource */
if (this.extUri.isEqual(resource, this.remoteResource)) {
return {
/* Update ignored settings from local file content */
content:
resourcePreview.remoteContent !== null
? updateIgnoredSettings(
resourcePreview.remoteContent,
resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}',
ignoredSettings,
formattingOptions,
)
: null,
localChange: 2 /* Change.Modified */,
remoteChange: 0 /* Change.None */,
};
}
/* Accept preview resource */
if (this.extUri.isEqual(resource, this.previewResource)) {
if (content === undefined) {
return {
content: resourcePreview.previewResult.content,
localChange: resourcePreview.previewResult.localChange,
remoteChange: resourcePreview.previewResult.remoteChange,
};
} else {
return {
/* Add ignored settings from local file content */
content:
content !== null
? updateIgnoredSettings(
content,
resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}',
ignoredSettings,
formattingOptions,
)
: null,
localChange: 2 /* Change.Modified */,
remoteChange: 2 /* Change.Modified */,
};
}
}
throw new Error(`Invalid Resource: ${resource.toString()}`);
}
async applyResult(remoteUserData, lastSyncUserData, resourcePreviews, force) {
const { fileContent } = resourcePreviews[0][0];
let { content, localChange, remoteChange } = resourcePreviews[0][1];
if (localChange === 0 /* Change.None */ && remoteChange === 0 /* Change.None */) {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`);
}
content = content ? content.trim() : '{}';
content = content || '{}';
this.validateContent(content);
if (localChange !== 0 /* Change.None */) {
this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`);
if (fileContent) {
await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString())));
}
await this.updateLocalFileContent(content, fileContent, force);
await this.configurationService.reloadConfiguration(3 /* ConfigurationTarget.USER_LOCAL */);
this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`);
}
if (remoteChange !== 0 /* Change.None */) {
const formatUtils = await this.getFormattingOptions();
// Update ignored settings from remote
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
const ignoredSettings = await this.getIgnoredSettings(content);
content = updateIgnoredSettings(
content,
remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}',
ignoredSettings,
formatUtils,
);
this.logService.trace(`${this.syncResourceLogLabel}: Updating remote settings...`);
remoteUserData = await this.updateRemoteUserData(
JSON.stringify(this.toSettingsSyncContent(content)),
force ? null : remoteUserData.ref,
);
this.logService.info(`${this.syncResourceLogLabel}: Updated remote settings`);
}
// Delete the preview
try {
await this.fileService.del(this.previewResource);
} catch (e) {
/* ignore */
}
if (lastSyncUserData?.ref !== remoteUserData.ref) {
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized settings...`);
await this.updateLastSyncUserData(remoteUserData);
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized settings`);
}
}
async hasLocalData() {
try {
const localFileContent = await this.getLocalFileContent();
if (localFileContent) {
const formatUtils = await this.getFormattingOptions();
const content = edit(localFileContent.value.toString(), [CONFIGURATION_SYNC_STORE_KEY], undefined, formatUtils);
return !isEmpty(content);
}
} catch (error) {
if (error.fileOperationResult !== 1 /* FileOperationResult.FILE_NOT_FOUND */) {
return true;
}
}
return false;
}
async resolveContent(uri) {
if (
this.extUri.isEqual(this.remoteResource, uri) ||
this.extUri.isEqual(this.localResource, uri) ||
this.extUri.isEqual(this.acceptedResource, uri) ||
this.extUri.isEqual(this.baseResource, uri)
) {
return this.resolvePreviewContent(uri);
}
return null;
}
async resolvePreviewContent(resource) {
let content = await super.resolvePreviewContent(resource);
if (content) {
const formatUtils = await this.getFormattingOptions();
// remove ignored settings from the preview content
const ignoredSettings = await this.getIgnoredSettings();
content = updateIgnoredSettings(content, '{}', ignoredSettings, formatUtils);
}
return content;
}
getSettingsSyncContent(remoteUserData) {
return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
}
parseSettingsSyncContent(syncContent) {
try {
return parseSettingsSyncContent(syncContent);
} catch (e) {
this.logService.error(e);
}
return null;
}
toSettingsSyncContent(settings) {
return { settings };
}
_defaultIgnoredSettings = undefined;
async getIgnoredSettings(content) {
if (!this._defaultIgnoredSettings) {
this._defaultIgnoredSettings = this.userDataSyncUtilService.resolveDefaultIgnoredSettings();
const disposable = Event.any(
Event.filter(this.extensionManagementService.onDidInstallExtensions, (e) => e.some(({ local }) => !!local)),
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e) => !e.error),
)(() => {
disposable.dispose();
this._defaultIgnoredSettings = undefined;
});
}
const defaultIgnoredSettings = await this._defaultIgnoredSettings;
return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content);
}
validateContent(content) {
if (this.hasErrors(content, false)) {
throw new UserDataSyncError(
localize('errorInvalidSettings', 'Unable to sync settings as there are errors/warning in settings file.'),
'LocalInvalidContent' /* UserDataSyncErrorCode.LocalInvalidContent */,
this.resource,
);
}
}
};
SettingsSynchroniser = __decorate(
[
__param(2, IFileService),
__param(3, IEnvironmentService),
__param(4, IStorageService),
__param(5, IUserDataSyncStoreService),
__param(6, IUserDataSyncBackupStoreService),
__param(7, IUserDataSyncLogService),
__param(8, IUserDataSyncUtilService),
__param(9, IConfigurationService),
__param(10, IUserDataSyncEnablementService),
__param(11, ITelemetryService),
__param(12, IExtensionManagementService),
__param(13, IUriIdentityService),
],
SettingsSynchroniser,
);
export { SettingsSynchroniser };
let SettingsInitializer = class SettingsInitializer extends AbstractInitializer {
constructor(
fileService,
userDataProfilesService,
environmentService,
logService,
storageService,
uriIdentityService,
) {
super(
'settings' /* SyncResource.Settings */,
userDataProfilesService,
environmentService,
logService,
fileService,
storageService,
uriIdentityService,
);
}
async doInitialize(remoteUserData) {
const settingsSyncContent = remoteUserData.syncData
? this.parseSettingsSyncContent(remoteUserData.syncData.content)
: null;
if (!settingsSyncContent) {
this.logService.info('Skipping initializing settings because remote settings does not exist.');
return;
}
const isEmpty = await this.isEmpty();
if (!isEmpty) {
this.logService.info('Skipping initializing settings because local settings exist.');
return;
}
await this.fileService.writeFile(
this.userDataProfilesService.defaultProfile.settingsResource,
VSBuffer.fromString(settingsSyncContent.settings),
);
await this.updateLastSyncUserData(remoteUserData);
}
async isEmpty() {
try {
const fileContent = await this.fileService.readFile(this.userDataProfilesService.defaultProfile.settingsResource);
return isEmpty(fileContent.value.toString().trim());
} catch (error) {
return error.fileOperationResult === 1 /* FileOperationResult.FILE_NOT_FOUND */;
}
}
parseSettingsSyncContent(syncContent) {
try {
return parseSettingsSyncContent(syncContent);
} catch (e) {
this.logService.error(e);
}
return null;
}
};
SettingsInitializer = __decorate(
[
__param(0, IFileService),
__param(1, IUserDataProfilesService),
__param(2, IEnvironmentService),
__param(3, IUserDataSyncLogService),
__param(4, IStorageService),
__param(5, IUriIdentityService),
],
SettingsInitializer,
);
export { SettingsInitializer };