mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-25 14:02:58 +00:00
Improve troubleshooting and compatible setting handling
This commit is contained in:
@@ -29,8 +29,6 @@ function valueToString(value: string | number | boolean | object | undefined): s
|
||||
}
|
||||
|
||||
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
private _hasNotifiedAutoAcceptCompatibleUndefined = false;
|
||||
|
||||
private _collectMismatchedTweakKeys(current: TweakValues, preferred: Partial<TweakValues>) {
|
||||
const items = Object.keys(
|
||||
TweakValuesShouldMatchedTemplate
|
||||
@@ -64,33 +62,17 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
);
|
||||
if (!hasOnlyCompatibleLossyMismatches) return undefined;
|
||||
|
||||
let autoAcceptCompatibleTweak = this.settings.autoAcceptCompatibleTweak;
|
||||
if (this.settings.autoAcceptCompatibleTweak === undefined) {
|
||||
if (this._hasNotifiedAutoAcceptCompatibleUndefined) {
|
||||
return undefined;
|
||||
}
|
||||
this._hasNotifiedAutoAcceptCompatibleUndefined = true;
|
||||
const CHOICE_ENABLE = $msg("TweakMismatchResolve.Action.EnableAutoAcceptCompatible");
|
||||
const CHOICE_DISABLE = $msg("TweakMismatchResolve.Action.DisableAutoAcceptCompatible");
|
||||
const CHOICES = [CHOICE_ENABLE, CHOICE_DISABLE] as const;
|
||||
const message = $msg("TweakMismatchResolve.Message.AutoAcceptCompatibleUndefined");
|
||||
const ret = await this.core.confirm.askSelectStringDialogue(message, CHOICES, {
|
||||
title: $msg("TweakMismatchResolve.Title.AutoAcceptCompatible"),
|
||||
timeout: 0,
|
||||
defaultAction: CHOICE_ENABLE,
|
||||
});
|
||||
if (ret !== CHOICE_ENABLE) {
|
||||
return undefined;
|
||||
}
|
||||
await this.services.setting.applyPartial(
|
||||
{
|
||||
autoAcceptCompatibleTweak: true,
|
||||
},
|
||||
true
|
||||
);
|
||||
Logger("Auto-accept for compatible tweak mismatch has been enabled.");
|
||||
// Keep the settings object stable: settings panes and an in-flight replication retry can
|
||||
// retain this reference while the default is persisted.
|
||||
this.settings.autoAcceptCompatibleTweak = true;
|
||||
await this.services.setting.saveSettingData();
|
||||
autoAcceptCompatibleTweak = true;
|
||||
Logger("Automatic alignment of compatible chunk settings has been enabled.");
|
||||
}
|
||||
|
||||
if (this.settings.autoAcceptCompatibleTweak !== true) return undefined;
|
||||
if (autoAcceptCompatibleTweak !== true) return undefined;
|
||||
return this._selectNewerTweakSide(current, preferred);
|
||||
}
|
||||
|
||||
@@ -215,7 +197,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
} 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_REMOTE_WITH_REBUILD, [preferred, true]]);
|
||||
CHOICE_AND_VALUES.push([CHOICE_USE_MINE_WITH_REBUILD, [true, true]]);
|
||||
} else {
|
||||
CHOICE_AND_VALUES.push([CHOICE_USE_REMOTE, [preferred, false]]);
|
||||
@@ -255,9 +237,16 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||
return "CHECKAGAIN";
|
||||
}
|
||||
if (conf) {
|
||||
this.settings = { ...this.settings, ...conf };
|
||||
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
||||
// ReplicationService retains the current settings object while it performs the immediate
|
||||
// CHECKAGAIN retry. Update that object in place so the retry observes the accepted values.
|
||||
Object.assign(this.settings, extractObject(TweakValuesTemplate, conf));
|
||||
await this.services.setting.saveSettingData();
|
||||
if (!rebuildRequired) {
|
||||
// The failed replication has settled before mismatch resolution runs. Reinitialise the
|
||||
// chunk-generation managers now so hash and splitter changes take effect before retrying.
|
||||
await this.localDatabase.managers.reinitialise();
|
||||
}
|
||||
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
||||
if (rebuildRequired) {
|
||||
await this.core.rebuilder.$fetchLocal();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_SETTINGS, REMOTE_COUCHDB, type RemoteDBSettings, type TweakValues } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
REMOTE_COUCHDB,
|
||||
type RemoteDBSettings,
|
||||
type TweakValues,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { ModuleResolvingMismatchedTweaks } from "./ModuleResolveMismatchedTweaks";
|
||||
|
||||
function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
|
||||
const askSelectStringDialogue = vi.fn(async () => undefined);
|
||||
const askSelectStringDialogue = vi.fn(async (..._args: unknown[]): Promise<string | undefined> => undefined);
|
||||
const applyPartial = vi.fn(async (_partial: Record<string, unknown>): Promise<void> => undefined);
|
||||
const reinitialise = vi.fn(async () => undefined);
|
||||
const core = {
|
||||
_services: {
|
||||
API: {
|
||||
@@ -15,6 +22,12 @@ function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
|
||||
},
|
||||
setting: {
|
||||
saveSettingData: vi.fn(async () => undefined),
|
||||
applyPartial,
|
||||
},
|
||||
},
|
||||
localDatabase: {
|
||||
managers: {
|
||||
reinitialise,
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
@@ -26,6 +39,9 @@ function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
|
||||
askSelectStringDialogue,
|
||||
},
|
||||
} as any;
|
||||
applyPartial.mockImplementation(async (partial: Record<string, unknown>) => {
|
||||
core.settings = { ...core.settings, ...partial };
|
||||
});
|
||||
|
||||
Object.defineProperty(core, "services", {
|
||||
get() {
|
||||
@@ -34,10 +50,35 @@ function createModule(settingsOverride: Partial<typeof DEFAULT_SETTINGS> = {}) {
|
||||
});
|
||||
|
||||
const module = new ModuleResolvingMismatchedTweaks(core);
|
||||
return { module, core, askSelectStringDialogue };
|
||||
return { module, core, askSelectStringDialogue, applyPartial, reinitialise };
|
||||
}
|
||||
|
||||
describe("ModuleResolvingMismatchedTweaks", () => {
|
||||
it("should enable and auto-accept compatible mismatches when the preference is undefined", async () => {
|
||||
const { module, core, askSelectStringDialogue, applyPartial } = createModule({
|
||||
autoAcceptCompatibleTweak: undefined,
|
||||
hashAlg: "xxhash64",
|
||||
tweakModified: 100,
|
||||
});
|
||||
const initialSettings = core.settings;
|
||||
|
||||
const preferred = {
|
||||
...(DEFAULT_SETTINGS as unknown as TweakValues),
|
||||
hashAlg: "xxhash32",
|
||||
tweakModified: 200,
|
||||
} as Partial<TweakValues>;
|
||||
|
||||
const [conf, rebuild] = await module._checkAndAskResolvingMismatchedTweaks(preferred);
|
||||
|
||||
expect(conf).toEqual(preferred);
|
||||
expect(rebuild).toBe(false);
|
||||
expect(core.settings).toBe(initialSettings);
|
||||
expect(core.settings.autoAcceptCompatibleTweak).toBe(true);
|
||||
expect(core._services.setting.saveSettingData).toHaveBeenCalledTimes(1);
|
||||
expect(applyPartial).not.toHaveBeenCalled();
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should auto-accept compatible mismatches on connect check using newer remote tweakModified", async () => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
@@ -58,6 +99,28 @@ describe("ModuleResolvingMismatchedTweaks", () => {
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ label: "neither side has a recorded time", currentModified: 0, preferredModified: 0 },
|
||||
{ label: "the recorded times are equal", currentModified: 200, preferredModified: 200 },
|
||||
])("should use the remote compatible value when $label", async ({ currentModified, preferredModified }) => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
hashAlg: "xxhash64",
|
||||
tweakModified: currentModified,
|
||||
});
|
||||
const preferred = {
|
||||
...(DEFAULT_SETTINGS as unknown as TweakValues),
|
||||
hashAlg: "xxhash32",
|
||||
tweakModified: preferredModified,
|
||||
} as Partial<TweakValues>;
|
||||
|
||||
const [conf, rebuild] = await module._checkAndAskResolvingMismatchedTweaks(preferred);
|
||||
|
||||
expect(conf).toEqual(preferred);
|
||||
expect(rebuild).toBe(false);
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should fallback to manual confirmation when mismatches are mixed on connect check", async () => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
@@ -80,6 +143,24 @@ describe("ModuleResolvingMismatchedTweaks", () => {
|
||||
expect(askSelectStringDialogue).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should fetch after applying a compatible remote setting when the user selects the rebuild option", async () => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: false,
|
||||
hashAlg: "xxhash64",
|
||||
});
|
||||
askSelectStringDialogue.mockResolvedValueOnce("Apply settings to this device, and fetch again");
|
||||
|
||||
const preferred = {
|
||||
...(DEFAULT_SETTINGS as unknown as TweakValues),
|
||||
hashAlg: "xxhash32",
|
||||
} as TweakValues;
|
||||
|
||||
const [conf, rebuild] = await module._checkAndAskResolvingMismatchedTweaks(preferred);
|
||||
|
||||
expect(conf).toEqual(preferred);
|
||||
expect(rebuild).toBe(true);
|
||||
});
|
||||
|
||||
it("should auto-accept compatible mismatches on remote-config check using newer local tweakModified", async () => {
|
||||
const { module, askSelectStringDialogue } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
@@ -105,4 +186,42 @@ describe("ModuleResolvingMismatchedTweaks", () => {
|
||||
expect(result).toEqual({ result: false, requireFetch: false });
|
||||
expect(askSelectStringDialogue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should apply remote compatible settings in place and reinitialise managers before retrying", async () => {
|
||||
const { module, core, reinitialise } = createModule({
|
||||
autoAcceptCompatibleTweak: true,
|
||||
hashAlg: "xxhash64",
|
||||
tweakModified: 100,
|
||||
});
|
||||
const initialSettings = core.settings;
|
||||
const preferred = {
|
||||
...(DEFAULT_SETTINGS as unknown as TweakValues),
|
||||
hashAlg: "xxhash32",
|
||||
tweakModified: 200,
|
||||
} as TweakValues;
|
||||
const calls: string[] = [];
|
||||
core._services.tweakValue = {
|
||||
checkAndAskResolvingMismatched: vi.fn(async () => [preferred, false]),
|
||||
};
|
||||
core._services.setting.saveSettingData = vi.fn(async () => {
|
||||
calls.push("save");
|
||||
});
|
||||
core.replicator = {
|
||||
tweakSettingsMismatched: true,
|
||||
preferredTweakValue: preferred,
|
||||
setPreferredRemoteTweakSettings: vi.fn(async () => {
|
||||
calls.push("set-preferred");
|
||||
}),
|
||||
};
|
||||
reinitialise.mockImplementation(async () => {
|
||||
calls.push("reinitialise");
|
||||
});
|
||||
|
||||
const result = await module._askResolvingMismatchedTweaks();
|
||||
|
||||
expect(result).toBe("CHECKAGAIN");
|
||||
expect(core.settings).toBe(initialSettings);
|
||||
expect(core.settings.hashAlg).toBe("xxhash32");
|
||||
expect(calls).toEqual(["save", "reinitialise", "set-preferred"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,7 +206,8 @@ export class LiveSyncSetting extends Setting {
|
||||
const setValue = wrapMemo((value: boolean) => {
|
||||
toggle.setValue(opt?.invert ? !value : value);
|
||||
});
|
||||
this.invalidateValue = () => setValue(LiveSyncSetting.env.editingSettings[key] ?? false);
|
||||
this.invalidateValue = () =>
|
||||
setValue(LiveSyncSetting.env.editingSettings[key] ?? opt?.defaultToggleValue ?? false);
|
||||
this.invalidateValue();
|
||||
|
||||
toggle.onChange(async (value) => {
|
||||
|
||||
@@ -35,7 +35,9 @@ export function paneAdvanced(this: ObsidianLiveSyncSettingTab, paneEl: HTMLEleme
|
||||
clampMin: 10,
|
||||
onUpdate: this.onlyOnCouchDB,
|
||||
});
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("autoAcceptCompatibleTweak");
|
||||
new Setting(paneEl)
|
||||
.setClass("wizardHidden")
|
||||
.autoWireToggle("autoAcceptCompatibleTweak", { defaultToggleValue: true });
|
||||
// new Setting(paneEl)
|
||||
// .setClass("wizardHidden")
|
||||
// .autoWireToggle("sendChunksBulk", { onUpdate: onlyOnCouchDB })
|
||||
|
||||
@@ -75,6 +75,7 @@ export type AutoWireOption = {
|
||||
holdValue?: boolean;
|
||||
isPassword?: boolean;
|
||||
invert?: boolean;
|
||||
defaultToggleValue?: boolean;
|
||||
onUpdate?: OnUpdateFunc;
|
||||
obsolete?: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user