mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-02-21 19:48:48 +00:00
300 lines
15 KiB
TypeScript
300 lines
15 KiB
TypeScript
import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
|
import { extractObject } from "octagonal-wheels/object";
|
|
import {
|
|
TweakValuesShouldMatchedTemplate,
|
|
IncompatibleChanges,
|
|
confName,
|
|
type TweakValues,
|
|
type RemoteDBSettings,
|
|
IncompatibleChangesInSpecificPattern,
|
|
CompatibleButLossyChanges,
|
|
} from "../../lib/src/common/types.ts";
|
|
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
|
import { AbstractModule } from "../AbstractModule.ts";
|
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
|
import type { LiveSyncCore } from "../../main.ts";
|
|
|
|
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
|
async _anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
|
if (!this.core.replicator.tweakSettingsMismatched && !this.core.replicator.preferredTweakValue) return false;
|
|
const preferred = this.core.replicator.preferredTweakValue;
|
|
if (!preferred) return false;
|
|
const ret = await this.services.tweakValue.askResolvingMismatched(preferred);
|
|
if (ret == "OK") return false;
|
|
if (ret == "CHECKAGAIN") return "CHECKAGAIN";
|
|
if (ret == "IGNORE") return true;
|
|
}
|
|
|
|
async _checkAndAskResolvingMismatchedTweaks(
|
|
preferred: Partial<TweakValues>
|
|
): Promise<[TweakValues | boolean, boolean]> {
|
|
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings);
|
|
const items = Object.entries(TweakValuesShouldMatchedTemplate);
|
|
let rebuildRequired = false;
|
|
let rebuildRecommended = false;
|
|
// Making tables:
|
|
// let table = `| Value name | This device | Configured | \n` + `|: --- |: --- :|: ---- :| \n`;
|
|
const tableRows = [];
|
|
// const items = [mine,preferred]
|
|
for (const v of items) {
|
|
const key = v[0] as keyof typeof TweakValuesShouldMatchedTemplate;
|
|
const valueMine = escapeMarkdownValue(mine[key]);
|
|
const valuePreferred = escapeMarkdownValue(preferred[key]);
|
|
if (valueMine == valuePreferred) continue;
|
|
if (IncompatibleChanges.indexOf(key) !== -1) {
|
|
rebuildRequired = true;
|
|
}
|
|
for (const pattern of IncompatibleChangesInSpecificPattern) {
|
|
if (pattern.key !== key) continue;
|
|
// if from value supplied, check if current value have been violated : in other words, if the current value is the same as the from value, it should require a rebuild.
|
|
const isFromConditionMet = "from" in pattern ? pattern.from === mine[key] : false;
|
|
// and, if to value supplied, same as above.
|
|
const isToConditionMet = "to" in pattern ? pattern.to === preferred[key] : false;
|
|
// if either of them is true, it should require a rebuild, if the pattern is not a recommendation.
|
|
if (isFromConditionMet || isToConditionMet) {
|
|
if (pattern.isRecommendation) {
|
|
rebuildRecommended = true;
|
|
} else {
|
|
rebuildRequired = true;
|
|
}
|
|
}
|
|
}
|
|
if (CompatibleButLossyChanges.indexOf(key) !== -1) {
|
|
rebuildRecommended = true;
|
|
}
|
|
|
|
// table += `| ${confName(key)} | ${valueMine} | ${valuePreferred} | \n`;
|
|
tableRows.push(
|
|
$msg("TweakMismatchResolve.Table.Row", {
|
|
name: confName(key),
|
|
self: valueMine,
|
|
remote: valuePreferred,
|
|
})
|
|
);
|
|
}
|
|
|
|
const additionalMessage =
|
|
rebuildRequired && this.core.settings.isConfigured
|
|
? $msg("TweakMismatchResolve.Message.WarningIncompatibleRebuildRequired")
|
|
: "";
|
|
const additionalMessage2 =
|
|
rebuildRecommended && this.core.settings.isConfigured
|
|
? $msg("TweakMismatchResolve.Message.WarningIncompatibleRebuildRecommended")
|
|
: "";
|
|
|
|
const table = $msg("TweakMismatchResolve.Table", { rows: tableRows.join("\n") });
|
|
|
|
const message = $msg("TweakMismatchResolve.Message.MainTweakResolving", {
|
|
table: table,
|
|
additionalMessage: [additionalMessage, additionalMessage2].filter((v) => v).join("\n"),
|
|
});
|
|
|
|
const CHOICE_USE_REMOTE = $msg("TweakMismatchResolve.Action.UseRemote");
|
|
const CHOICE_USE_REMOTE_WITH_REBUILD = $msg("TweakMismatchResolve.Action.UseRemoteWithRebuild");
|
|
const CHOICE_USE_REMOTE_PREVENT_REBUILD = $msg("TweakMismatchResolve.Action.UseRemoteAcceptIncompatible");
|
|
const CHOICE_USE_MINE = $msg("TweakMismatchResolve.Action.UseMine");
|
|
const CHOICE_USE_MINE_WITH_REBUILD = $msg("TweakMismatchResolve.Action.UseMineWithRebuild");
|
|
const CHOICE_USE_MINE_PREVENT_REBUILD = $msg("TweakMismatchResolve.Action.UseMineAcceptIncompatible");
|
|
const CHOICE_DISMISS = $msg("TweakMismatchResolve.Action.Dismiss");
|
|
|
|
const CHOICE_AND_VALUES = [] as [string, [result: TweakValues | boolean, rebuild: boolean]][];
|
|
|
|
if (rebuildRequired) {
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_REMOTE_WITH_REBUILD, [preferred, true]]);
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_MINE_WITH_REBUILD, [true, true]]);
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_REMOTE_PREVENT_REBUILD, [preferred, false]]);
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_MINE_PREVENT_REBUILD, [true, false]]);
|
|
} else if (rebuildRecommended) {
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_REMOTE, [preferred, false]]);
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_MINE, [true, false]]);
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_REMOTE_WITH_REBUILD, [true, true]]);
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_MINE_WITH_REBUILD, [true, true]]);
|
|
} else {
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_REMOTE, [preferred, false]]);
|
|
CHOICE_AND_VALUES.push([CHOICE_USE_MINE, [true, false]]);
|
|
}
|
|
CHOICE_AND_VALUES.push([CHOICE_DISMISS, [false, false]]);
|
|
const CHOICES = Object.fromEntries(CHOICE_AND_VALUES) as Record<
|
|
string,
|
|
[TweakValues | boolean, performRebuild: boolean]
|
|
>;
|
|
const retKey = await this.core.confirm.askSelectStringDialogue(message, Object.keys(CHOICES), {
|
|
title: $msg("TweakMismatchResolve.Title.TweakResolving"),
|
|
timeout: 60,
|
|
defaultAction: CHOICE_DISMISS,
|
|
});
|
|
if (!retKey) return [false, false];
|
|
return CHOICES[retKey];
|
|
}
|
|
|
|
async _askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
|
if (!this.core.replicator.tweakSettingsMismatched) {
|
|
return "OK";
|
|
}
|
|
const tweaks = this.core.replicator.preferredTweakValue;
|
|
if (!tweaks) {
|
|
return "IGNORE";
|
|
}
|
|
const preferred = extractObject(TweakValuesShouldMatchedTemplate, tweaks);
|
|
|
|
const [conf, rebuildRequired] = await this.services.tweakValue.checkAndAskResolvingMismatched(preferred);
|
|
if (!conf) return "IGNORE";
|
|
|
|
if (conf === true) {
|
|
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
|
if (rebuildRequired) {
|
|
await this.core.rebuilder.$rebuildRemote();
|
|
}
|
|
Logger(
|
|
`Tweak values on the remote server have been updated. Your other device will see this message.`,
|
|
LOG_LEVEL_NOTICE
|
|
);
|
|
return "CHECKAGAIN";
|
|
}
|
|
if (conf) {
|
|
this.settings = { ...this.settings, ...conf };
|
|
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
|
await this.services.setting.saveSettingData();
|
|
if (rebuildRequired) {
|
|
await this.core.rebuilder.$fetchLocal();
|
|
}
|
|
Logger(`Configuration has been updated as configured by the other device.`, LOG_LEVEL_NOTICE);
|
|
return "CHECKAGAIN";
|
|
}
|
|
return "IGNORE";
|
|
}
|
|
|
|
async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
|
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
|
|
if (!replicator) {
|
|
this._log("The remote type is not supported for fetching preferred tweak values.", LOG_LEVEL_NOTICE);
|
|
return false;
|
|
}
|
|
if (await replicator.tryConnectRemote(trialSetting)) {
|
|
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
|
|
if (preferred) {
|
|
return preferred;
|
|
}
|
|
this._log("Failed to get the preferred tweak values from the remote server.", LOG_LEVEL_NOTICE);
|
|
return false;
|
|
}
|
|
this._log("Failed to connect to the remote server.", LOG_LEVEL_NOTICE);
|
|
return false;
|
|
}
|
|
|
|
async _checkAndAskUseRemoteConfiguration(
|
|
trialSetting: RemoteDBSettings
|
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
|
const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting);
|
|
if (preferred) {
|
|
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred);
|
|
}
|
|
return { result: false, requireFetch: false };
|
|
}
|
|
|
|
async _askUseRemoteConfiguration(
|
|
trialSetting: RemoteDBSettings,
|
|
preferred: TweakValues
|
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
|
const items = Object.entries(TweakValuesShouldMatchedTemplate);
|
|
let rebuildRequired = false;
|
|
let rebuildRecommended = false;
|
|
// Making tables:
|
|
// let table = `| Value name | This device | On Remote | \n` + `|: --- |: ---- :|: ---- :| \n`;
|
|
let differenceCount = 0;
|
|
const tableRows = [] as string[];
|
|
// const items = [mine,preferred]
|
|
for (const v of items) {
|
|
const key = v[0] as keyof typeof TweakValuesShouldMatchedTemplate;
|
|
const remoteValueForDisplay = escapeMarkdownValue(preferred[key]);
|
|
const currentValueForDisplay = `${escapeMarkdownValue((trialSetting as TweakValues)?.[key])}`;
|
|
if ((trialSetting as TweakValues)?.[key] !== preferred[key]) {
|
|
if (IncompatibleChanges.indexOf(key) !== -1) {
|
|
rebuildRequired = true;
|
|
}
|
|
for (const pattern of IncompatibleChangesInSpecificPattern) {
|
|
if (pattern.key !== key) continue;
|
|
// if from value supplied, check if current value have been violated : in other words, if the current value is the same as the from value, it should require a rebuild.
|
|
const isFromConditionMet =
|
|
"from" in pattern ? pattern.from === (trialSetting as TweakValues)?.[key] : false;
|
|
// and, if to value supplied, same as above.
|
|
const isToConditionMet = "to" in pattern ? pattern.to === preferred[key] : false;
|
|
// if either of them is true, it should require a rebuild, if the pattern is not a recommendation.
|
|
if (isFromConditionMet || isToConditionMet) {
|
|
if (pattern.isRecommendation) {
|
|
rebuildRecommended = true;
|
|
} else {
|
|
rebuildRequired = true;
|
|
}
|
|
}
|
|
}
|
|
if (CompatibleButLossyChanges.indexOf(key) !== -1) {
|
|
rebuildRecommended = true;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
tableRows.push(
|
|
$msg("TweakMismatchResolve.Table.Row", {
|
|
name: confName(key),
|
|
self: currentValueForDisplay,
|
|
remote: remoteValueForDisplay,
|
|
})
|
|
);
|
|
differenceCount++;
|
|
}
|
|
|
|
if (differenceCount === 0) {
|
|
this._log("The settings in the remote database are the same as the local database.", LOG_LEVEL_NOTICE);
|
|
return { result: false, requireFetch: false };
|
|
}
|
|
const additionalMessage =
|
|
rebuildRequired && this.core.settings.isConfigured
|
|
? $msg("TweakMismatchResolve.Message.UseRemote.WarningRebuildRequired")
|
|
: "";
|
|
const additionalMessage2 =
|
|
rebuildRecommended && this.core.settings.isConfigured
|
|
? $msg("TweakMismatchResolve.Message.UseRemote.WarningRebuildRecommended")
|
|
: "";
|
|
|
|
const table = $msg("TweakMismatchResolve.Table", { rows: tableRows.join("\n") });
|
|
|
|
const message = $msg("TweakMismatchResolve.Message.Main", {
|
|
table: table,
|
|
additionalMessage: [additionalMessage, additionalMessage2].filter((v) => v).join("\n"),
|
|
});
|
|
|
|
const CHOICE_USE_REMOTE = $msg("TweakMismatchResolve.Action.UseConfigured");
|
|
const CHOICE_DISMISS = $msg("TweakMismatchResolve.Action.Dismiss");
|
|
// const CHOICE_AND_VALUES = [
|
|
// [CHOICE_USE_REMOTE, preferred],
|
|
// [CHOICE_DISMISS, false]]
|
|
const CHOICES = [CHOICE_USE_REMOTE, CHOICE_DISMISS];
|
|
const retKey = await this.core.confirm.askSelectStringDialogue(message, CHOICES, {
|
|
title: $msg("TweakMismatchResolve.Title.UseRemoteConfig"),
|
|
timeout: 0,
|
|
defaultAction: CHOICE_DISMISS,
|
|
});
|
|
if (!retKey) return { result: false, requireFetch: false };
|
|
if (retKey === CHOICE_DISMISS) return { result: false, requireFetch: false };
|
|
if (retKey === CHOICE_USE_REMOTE) {
|
|
return { result: { ...trialSetting, ...preferred }, requireFetch: rebuildRequired };
|
|
}
|
|
return { result: false, requireFetch: false };
|
|
}
|
|
|
|
override onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
|
services.tweakValue.fetchRemotePreferred.setHandler(this._fetchRemotePreferredTweakValues.bind(this));
|
|
services.tweakValue.checkAndAskResolvingMismatched.setHandler(
|
|
this._checkAndAskResolvingMismatchedTweaks.bind(this)
|
|
);
|
|
services.tweakValue.askResolvingMismatched.setHandler(this._askResolvingMismatchedTweaks.bind(this));
|
|
services.tweakValue.checkAndAskUseRemoteConfiguration.setHandler(
|
|
this._checkAndAskUseRemoteConfiguration.bind(this)
|
|
);
|
|
services.tweakValue.askUseRemoteConfiguration.setHandler(this._askUseRemoteConfiguration.bind(this));
|
|
services.replication.checkConnectionFailure.addHandler(this._anyAfterConnectCheckFailed.bind(this));
|
|
}
|
|
}
|