Keep startup ready after individual file failures

This commit is contained in:
vorotamoroz
2026-09-04 12:50:46 +00:00
parent 045a328697
commit 6abc5cba64
15 changed files with 335 additions and 5 deletions
+16
View File
@@ -49,6 +49,10 @@ import { MARK_LOG_NETWORK_ERROR, MARK_LOG_SEPARATOR } from "@vrtmrz/livesync-com
import { NetworkWarningStyles } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
import { generateReport } from "@/common/reportTool.ts";
import {
ANDROID_LINUX_PATH_COMPONENT_UTF8_WARNING_BOUNDARY,
findPathComponentsExceedingUtf8Limit,
} from "@/common/pathCompatibility.ts";
// This module cannot be a core module because it depends on the Obsidian UI.
@@ -293,6 +297,18 @@ export class ModuleLog extends AbstractObsidianModule {
reasonWarn.push("Some platforms may be unable to process this file correctly: " + labels.join(" "));
}
}
const oversizedPathComponents = findPathComponentsExceedingUtf8Limit(thisFile.path);
if (oversizedPathComponents.length > 0) {
const components = oversizedPathComponents
.map(({ component, utf8Bytes }) => `${component} (${utf8Bytes} bytes)`)
.join(", ");
reasonWarn.push(
$msg("moduleLog.pathComponentTooLong", {
maxBytes: `${ANDROID_LINUX_PATH_COMPONENT_UTF8_WARNING_BOUNDARY}`,
components,
})
);
}
// Case Sensitivity
if (this.services.vault.shouldCheckCaseInsensitively()) {
const f = (await this.core.storageAccess.getFiles())
@@ -412,7 +412,9 @@ export function paneMaintenance(
.setDisabled(false)
.onClick(async () => {
await this.services.database.resetDatabase();
await this.services.databaseEvents.initialiseDatabase();
if (!(await this.services.databaseEvents.initialiseDatabase())) {
Logger($msg("Ui.Common.LocalDatabaseInitialisationFailed"), LOG_LEVEL_NOTICE);
}
})
);
});
@@ -93,7 +93,7 @@ afterEach(() => {
vi.clearAllMocks();
});
describe("paneMaintenance Fresh Start Wipe", () => {
describe("paneMaintenance", () => {
it("does not announce success when the remote wipe reports failure", async () => {
const updateCheckPointInfo = vi.fn(async () => undefined);
const resetRemoteBucket = vi.fn(async () => false);
@@ -140,4 +140,49 @@ describe("paneMaintenance Fresh Start Wipe", () => {
);
expect(maintenanceHarness.logger).not.toHaveBeenCalledWith("Deleted all data on remote server", "notice");
});
it("reports when database initialisation after a local reset does not complete", async () => {
const resetDatabase = vi.fn(async () => undefined);
const initialiseDatabase = vi.fn(async () => false);
const addPanel = vi.fn((_parent: HTMLElement, heading: string) => ({
then(callback: (paneEl: HTMLElement) => void) {
if (heading === "Reset") {
callback({} as HTMLElement);
}
return Promise.resolve();
},
}));
const host = {
core: {},
createEl: vi.fn(),
editingSettings: {},
isConfiguredAs: vi.fn(),
onlyOnCouchDB: vi.fn(),
onlyOnCouchDBOrMinIO: vi.fn(),
onlyOnMinIO: vi.fn(),
services: {
appLifecycle: { askRestart: vi.fn() },
database: { resetDatabase },
databaseEvents: { initialiseDatabase },
setting: { saveSettingData: vi.fn() },
},
};
paneMaintenance.call(host as never, {} as HTMLElement, { addPanel } as never);
const deleteLocalDatabase = maintenanceHarness.createdSettings.find(
({ name }) => name === "Delete local database to reset or uninstall Self-hosted LiveSync"
);
if (!deleteLocalDatabase?.click) {
throw new Error("Delete local database action was not registered");
}
await deleteLocalDatabase.click();
expect(resetDatabase).toHaveBeenCalledOnce();
expect(initialiseDatabase).toHaveBeenCalledOnce();
expect(maintenanceHarness.logger).toHaveBeenCalledWith(
"Ui.Common.LocalDatabaseInitialisationFailed",
"notice"
);
});
});
@@ -15,6 +15,7 @@ import { PouchDB } from "@vrtmrz/livesync-commonlib/compat/pouchdb/pouchdb-brows
import { ExtraSuffixIndexedDB } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { migrateDatabases } from "./settingUtils.ts";
import { usesLegacyIndexedDBAdapter } from "@/common/compatibilitySettings.ts";
import { $msg } from "@/common/translation";
export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void {
void addPanel(paneEl, "Compatibility (Metadata)").then((paneEl) => {
@@ -142,7 +143,9 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
this.addOnSaved("additionalSuffixOfDatabaseName", async (key) => {
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL_NOTICE);
await this.services.databaseEvents.initialiseDatabase();
if (!(await this.services.databaseEvents.initialiseDatabase())) {
Logger($msg("Ui.Common.LocalDatabaseInitialisationFailed"), LOG_LEVEL_NOTICE);
}
});
new Setting(paneEl).autoWireDropDown("hashAlg", {
@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { LOG_LEVEL_NOTICE } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { panePatches } from "./PanePatches.ts";
const remediationHarness = vi.hoisted(() => {
@@ -14,11 +15,13 @@ const remediationHarness = vi.hoisted(() => {
};
const setButtonClassState = vi.fn();
const setSettingClassState = vi.fn();
const logger = vi.fn();
return {
createSpan,
dateElement,
inputEl,
logger,
setButtonClassState,
setSettingClassState,
textComponent,
@@ -59,9 +62,25 @@ vi.mock("./LiveSyncSetting.ts", () => ({
autoWireToggle(): this {
return this;
}
autoWireText(): this {
return this;
}
autoWireDropDown(): this {
return this;
}
},
}));
vi.mock("@/common/translation", () => ({
$msg: (message: string) => message,
}));
vi.mock("@vrtmrz/livesync-commonlib/compat/common/logger", () => ({
Logger: remediationHarness.logger,
}));
afterEach(() => {
Reflect.deleteProperty(globalThis, "activeDocument");
vi.clearAllMocks();
@@ -69,7 +88,7 @@ afterEach(() => {
remediationHarness.inputEl.type = "";
});
describe("panePatches remediation setting", () => {
describe("panePatches", () => {
it("creates the status element in the setting control instead of the document", () => {
const hierarchyError = new DOMException(
"Failed to execute 'appendChild' on 'Node': Only one element on document allowed.",
@@ -115,4 +134,36 @@ describe("panePatches remediation setting", () => {
);
expect(remediationHarness.setButtonClassState).toHaveBeenCalledWith("sls-setting-additional-action", true);
});
it("reports when database reinitialisation after a suffix change does not complete", async () => {
const initialiseDatabase = vi.fn(async () => false);
let onSuffixSaved: (() => Promise<void>) | undefined;
const host = {
addOnSaved: vi.fn((key: string, callback: () => Promise<void>) => {
if (key === "additionalSuffixOfDatabaseName") onSuffixSaved = callback;
}),
services: {
databaseEvents: { initialiseDatabase },
},
};
const addPanel = vi.fn((_paneEl: HTMLElement, title: string) => ({
then(callback: (paneEl: HTMLElement) => void) {
if (title === "Edge case addressing (Database)") {
callback({} as HTMLElement);
}
return Promise.resolve();
},
}));
panePatches.call(host as never, {} as HTMLElement, { addPanel } as never);
if (!onSuffixSaved) throw new Error("Database suffix save handler was not registered");
await onSuffixSaved();
expect(initialiseDatabase).toHaveBeenCalledOnce();
expect(remediationHarness.logger).toHaveBeenCalledWith(
"Ui.Common.LocalDatabaseInitialisationFailed",
LOG_LEVEL_NOTICE
);
});
});