mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-08-28 14:27:08 +00:00
Fix CLI settings write-back behaviour
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
|
||||
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/remote-configurations";
|
||||
import type { CLICommand } from "./commands/types";
|
||||
|
||||
const SETTINGS_WRITE_COMMANDS = new Set<CLICommand>([
|
||||
"setup",
|
||||
"remote-add",
|
||||
"remote-rm",
|
||||
"remote-set",
|
||||
"remote-activate",
|
||||
]);
|
||||
|
||||
const REMOTE_SETTINGS_WRITE_COMMANDS = new Set<CLICommand>([
|
||||
"remote-add",
|
||||
"remote-rm",
|
||||
"remote-set",
|
||||
"remote-activate",
|
||||
]);
|
||||
|
||||
export const CLI_RUNTIME_ONLY_SETTING_KEYS = new Set<keyof ObsidianLiveSyncSettings>([
|
||||
"disableCheckingConfigMismatch",
|
||||
"suspendFileWatching",
|
||||
"suspendParseReplicationResult",
|
||||
]);
|
||||
|
||||
function cloneJsonValue<T>(value: T): T {
|
||||
if (value === undefined) return value;
|
||||
return JSON.parse(JSON.stringify(value)) as T;
|
||||
}
|
||||
|
||||
export function cloneSettings(settings: ObsidianLiveSyncSettings): ObsidianLiveSyncSettings {
|
||||
return cloneJsonValue(settings);
|
||||
}
|
||||
|
||||
function settingValuesEqual(left: unknown, right: unknown): boolean {
|
||||
return JSON.stringify(left) === JSON.stringify(right);
|
||||
}
|
||||
|
||||
export function settingsEqual(left: ObsidianLiveSyncSettings, right: ObsidianLiveSyncSettings): boolean {
|
||||
return settingValuesEqual(left, right);
|
||||
}
|
||||
|
||||
function settingsKeys(...settings: ObsidianLiveSyncSettings[]): Set<keyof ObsidianLiveSyncSettings> {
|
||||
return new Set(settings.flatMap((value) => Object.keys(value) as Array<keyof ObsidianLiveSyncSettings>));
|
||||
}
|
||||
|
||||
function copySetting(
|
||||
target: ObsidianLiveSyncSettings,
|
||||
source: ObsidianLiveSyncSettings,
|
||||
key: keyof ObsidianLiveSyncSettings
|
||||
): void {
|
||||
const targetRecord = target as unknown as Record<string, unknown>;
|
||||
const sourceRecord = source as unknown as Record<string, unknown>;
|
||||
if (Object.prototype.hasOwnProperty.call(sourceRecord, key)) {
|
||||
targetRecord[key] = cloneJsonValue(sourceRecord[key]);
|
||||
} else {
|
||||
delete targetRecord[key];
|
||||
}
|
||||
}
|
||||
|
||||
function remoteSettingKeys(...settings: ObsidianLiveSyncSettings[]): Set<keyof ObsidianLiveSyncSettings> {
|
||||
const keys = new Set<keyof ObsidianLiveSyncSettings>([
|
||||
"remoteConfigurations",
|
||||
"activeConfigurationId",
|
||||
"P2P_ActiveRemoteConfigurationId",
|
||||
"remoteType",
|
||||
]);
|
||||
for (const current of settings) {
|
||||
for (const configuration of Object.values(current.remoteConfigurations ?? {})) {
|
||||
try {
|
||||
const parsed = ConnectionStringParser.parse(configuration.uri);
|
||||
for (const key of Object.keys(parsed.settings) as Array<keyof ObsidianLiveSyncSettings>) {
|
||||
keys.add(key);
|
||||
}
|
||||
} catch {
|
||||
// The setting service reports invalid remote configurations when loading them.
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
export function changedSettingKeys(
|
||||
before: ObsidianLiveSyncSettings,
|
||||
after: ObsidianLiveSyncSettings
|
||||
): Set<keyof ObsidianLiveSyncSettings> {
|
||||
const changed = new Set<keyof ObsidianLiveSyncSettings>();
|
||||
for (const key of settingsKeys(before, after)) {
|
||||
if (!settingValuesEqual(before[key], after[key])) {
|
||||
changed.add(key);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
export function isSettingsWriteCommand(command: CLICommand): boolean {
|
||||
return SETTINGS_WRITE_COMMANDS.has(command);
|
||||
}
|
||||
|
||||
export function reconcileDurableSettings(options: {
|
||||
durableBase: ObsidianLiveSyncSettings;
|
||||
runtimeBaseline: ObsidianLiveSyncSettings;
|
||||
runtimeCurrent: ObsidianLiveSyncSettings;
|
||||
preserveKeys: ReadonlySet<keyof ObsidianLiveSyncSettings>;
|
||||
command?: CLICommand;
|
||||
}): ObsidianLiveSyncSettings {
|
||||
const durable = cloneSettings(options.durableBase);
|
||||
for (const key of settingsKeys(options.runtimeBaseline, options.runtimeCurrent)) {
|
||||
if (options.preserveKeys.has(key)) continue;
|
||||
if (!settingValuesEqual(options.runtimeBaseline[key], options.runtimeCurrent[key])) {
|
||||
copySetting(durable, options.runtimeCurrent, key);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.command) {
|
||||
if (REMOTE_SETTINGS_WRITE_COMMANDS.has(options.command)) {
|
||||
copySetting(durable, options.runtimeCurrent, "remoteConfigurations");
|
||||
copySetting(durable, options.runtimeCurrent, "activeConfigurationId");
|
||||
copySetting(durable, options.runtimeCurrent, "P2P_ActiveRemoteConfigurationId");
|
||||
|
||||
if (durable.activeConfigurationId) {
|
||||
activateRemoteConfiguration(durable, durable.activeConfigurationId);
|
||||
}
|
||||
} else {
|
||||
// Commands such as remote-status may activate a profile temporarily. Only
|
||||
// the dedicated remote settings commands are allowed to retain that switch.
|
||||
for (const key of remoteSettingKeys(options.durableBase, options.runtimeBaseline, options.runtimeCurrent)) {
|
||||
copySetting(durable, options.durableBase, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return durable;
|
||||
}
|
||||
|
||||
export function preserveStoredSetting(
|
||||
candidateText: string,
|
||||
originalText: string | undefined,
|
||||
key: keyof ObsidianLiveSyncSettings
|
||||
): string {
|
||||
if (originalText === undefined) return candidateText;
|
||||
try {
|
||||
const candidate = JSON.parse(candidateText) as ObsidianLiveSyncSettings;
|
||||
const original = JSON.parse(originalText) as ObsidianLiveSyncSettings;
|
||||
if (Object.prototype.hasOwnProperty.call(original, key)) {
|
||||
copySetting(candidate, original, key);
|
||||
} else {
|
||||
delete (candidate as unknown as Record<string, unknown>)[key];
|
||||
}
|
||||
return JSON.stringify(candidate, null, 2);
|
||||
} catch {
|
||||
return candidateText;
|
||||
}
|
||||
}
|
||||
|
||||
export function applyStoredSetting(
|
||||
target: ObsidianLiveSyncSettings,
|
||||
storedText: string | undefined,
|
||||
key: keyof ObsidianLiveSyncSettings
|
||||
): void {
|
||||
if (storedText === undefined) return;
|
||||
try {
|
||||
const stored = JSON.parse(storedText) as ObsidianLiveSyncSettings;
|
||||
if (Object.prototype.hasOwnProperty.call(stored, key)) {
|
||||
copySetting(target, stored, key);
|
||||
} else {
|
||||
delete (target as unknown as Record<string, unknown>)[key];
|
||||
}
|
||||
} catch {
|
||||
// The setting service owns validation and recovery of malformed files.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user