diff --git a/apps/client/src/widgets/type_widgets/options/sync.tsx b/apps/client/src/widgets/type_widgets/options/sync.tsx index 1ffb40f373..18137b344f 100644 --- a/apps/client/src/widgets/type_widgets/options/sync.tsx +++ b/apps/client/src/widgets/type_widgets/options/sync.tsx @@ -1,16 +1,18 @@ +import { SyncTestResponse } from "@triliumnext/commons"; import { useRef } from "preact/hooks"; + import { t } from "../../../services/i18n"; +import server from "../../../services/server"; +import toast from "../../../services/toast"; import { openInAppHelpFromUrl } from "../../../services/utils"; import Button from "../../react/Button"; import FormGroup from "../../react/FormGroup"; -import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox"; +import FormText from "../../react/FormText"; +import FormTextBox from "../../react/FormTextBox"; +import { useTriliumOptions } from "../../react/hooks"; import RawHtml from "../../react/RawHtml"; import OptionsSection from "./components/OptionsSection"; -import { useTriliumOptions } from "../../react/hooks"; -import FormText from "../../react/FormText"; -import server from "../../../services/server"; -import toast from "../../../services/toast"; -import { SyncTestResponse } from "@triliumnext/commons"; +import TimeSelector from "./components/TimeSelector"; export default function SyncOptions() { return ( @@ -18,13 +20,12 @@ export default function SyncOptions() { - ) + ); } export function SyncConfiguration() { - const [ options, setOptions ] = useTriliumOptions("syncServerHost", "syncServerTimeout", "syncProxy"); + const [ options, setOptions ] = useTriliumOptions("syncServerHost", "syncProxy"); const syncServerHost = useRef(options.syncServerHost); - const syncServerTimeout = useRef(options.syncServerTimeout); const syncProxy = useRef(options.syncProxy); return ( @@ -32,13 +33,12 @@ export function SyncConfiguration() {
{ setOptions({ syncServerHost: syncServerHost.current, - syncServerTimeout: syncServerTimeout.current, syncProxy: syncProxy.current }); e.preventDefault(); }}> - syncServerHost.current = newValue} /> @@ -50,27 +50,28 @@ export function SyncConfiguration() { } > - syncProxy.current = newValue} /> - - syncServerTimeout.current = newValue} - /> - -
+ + + + - ) + ); } export function SyncTest() { @@ -90,5 +91,5 @@ export function SyncTest() { }} /> - ) -} \ No newline at end of file + ); +} diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 9e21dcb7b7..912fef055e 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -32,6 +32,7 @@ const ALLOWED_OPTIONS = new Set([ "codeNoteTheme", "syncServerHost", "syncServerTimeout", + "syncServerTimeoutTimeScale", "syncProxy", "hoistedNoteId", "mainFontSize", diff --git a/apps/server/src/services/options_init.spec.ts b/apps/server/src/services/options_init.spec.ts new file mode 100644 index 0000000000..b133c6ded8 --- /dev/null +++ b/apps/server/src/services/options_init.spec.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { migrateSyncTimeoutFromMilliseconds } from "./options_init.js"; + +describe("migrateSyncTimeoutFromMilliseconds", () => { + it("returns null when no migration is needed", () => { + // Values < 1000 are already in seconds/minutes format + expect(migrateSyncTimeoutFromMilliseconds(120)).toBeNull(); + expect(migrateSyncTimeoutFromMilliseconds(500)).toBeNull(); + expect(migrateSyncTimeoutFromMilliseconds(999)).toBeNull(); + expect(migrateSyncTimeoutFromMilliseconds(NaN)).toBeNull(); + }); + + it("migrates to minutes when divisible by 60", () => { + expect(migrateSyncTimeoutFromMilliseconds(60000)).toEqual({ value: 1, scale: 60 }); // 1 minute + expect(migrateSyncTimeoutFromMilliseconds(120000)).toEqual({ value: 2, scale: 60 }); // 2 minutes + expect(migrateSyncTimeoutFromMilliseconds(300000)).toEqual({ value: 5, scale: 60 }); // 5 minutes + expect(migrateSyncTimeoutFromMilliseconds(3600000)).toEqual({ value: 60, scale: 60 }); // 60 minutes + }); + + it("migrates to seconds when not divisible by 60", () => { + expect(migrateSyncTimeoutFromMilliseconds(1000)).toEqual({ value: 1, scale: 1 }); // 1 second + expect(migrateSyncTimeoutFromMilliseconds(45000)).toEqual({ value: 45, scale: 1 }); // 45 seconds + expect(migrateSyncTimeoutFromMilliseconds(90000)).toEqual({ value: 90, scale: 1 }); // 90 seconds + expect(migrateSyncTimeoutFromMilliseconds(150000)).toEqual({ value: 150, scale: 1 }); // 150 seconds + }); + + it("rounds milliseconds to nearest second", () => { + expect(migrateSyncTimeoutFromMilliseconds(120500)).toEqual({ value: 121, scale: 1 }); + }); +}); diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index 92912222d0..bb6336b1f7 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -66,7 +66,7 @@ async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = optionService.createOption("textNoteEditorType", "ckeditor-classic", true); optionService.createOption("syncServerHost", opts.syncServerHost || "", false); - optionService.createOption("syncServerTimeout", "120000", false); + optionService.createOption("syncServerTimeout", "2", false); // 2 minutes (with default scale of 60) optionService.createOption("syncProxy", opts.syncProxy || "", false); } @@ -74,6 +74,7 @@ async function initNotSyncedOptions(initialized: boolean, opts: NotSyncedOpts = * Contains all the default options that must be initialized on new and existing databases (at startup). The value can also be determined based on other options, provided they have already been initialized. */ const defaultOptions: DefaultOption[] = [ + { name: "syncServerTimeoutTimeScale", value: "60", isSynced: false }, // default to Minutes { name: "revisionSnapshotTimeInterval", value: "600", isSynced: true }, { name: "revisionSnapshotTimeIntervalTimeScale", value: "60", isSynced: true }, // default to Minutes { name: "revisionSnapshotNumberLimit", value: "-1", isSynced: true }, @@ -255,6 +256,36 @@ function initStartupOptions() { ]) ); } + + // Migrate syncServerTimeout from milliseconds to seconds/minutes (for existing installations) + const syncTimeout = parseInt(optionsMap.syncServerTimeout, 10); + const migrated = migrateSyncTimeoutFromMilliseconds(syncTimeout); + if (migrated) { + optionService.setOption("syncServerTimeout", String(migrated.value)); + optionService.setOption("syncServerTimeoutTimeScale", String(migrated.scale)); + const unit = migrated.scale === 60 ? "minutes" : "seconds"; + log.info(`Migrated syncServerTimeout from ${syncTimeout}ms to ${migrated.value} ${unit}`); + } +} + +/** + * Migrates a sync timeout value from milliseconds to a value/scale pair. + * Values >= 1000 are assumed to be in milliseconds (since 1000+ seconds = 16+ minutes is unlikely). + * + * @returns The migrated value and scale, or null if no migration is needed. + */ +export function migrateSyncTimeoutFromMilliseconds(milliseconds: number): { value: number; scale: number } | null { + if (isNaN(milliseconds) || milliseconds < 1000) { + return null; + } + + const seconds = Math.round(milliseconds / 1000); + + // If divisible by 60, store as minutes; otherwise store as seconds + if (seconds >= 60 && seconds % 60 === 0) { + return { value: seconds / 60, scale: 60 }; + } + return { value: seconds, scale: 1 }; } function getKeyboardDefaultOptions() { diff --git a/apps/server/src/services/sync_options.ts b/apps/server/src/services/sync_options.ts index 657c1b2149..5b6c19cb8a 100644 --- a/apps/server/src/services/sync_options.ts +++ b/apps/server/src/services/sync_options.ts @@ -1,7 +1,7 @@ -"use strict"; -import optionService from "./options.js"; + import config from "./config.js"; +import optionService from "./options.js"; import { normalizeUrl } from "./utils.js"; /* @@ -29,6 +29,11 @@ export default { // and we need to override it with config from config.ini return !!syncServerHost && syncServerHost !== "disabled"; }, - getSyncTimeout: () => parseInt(get("syncServerTimeout")) || 120000, + // Value is stored with a time scale, convert to milliseconds for use + getSyncTimeout: () => { + const value = parseInt(get("syncServerTimeout"), 10) || 2; + const scale = parseInt(optionService.getOption("syncServerTimeoutTimeScale"), 10) || 60; + return value * scale * 1000; + }, getSyncProxy: () => get("syncProxy") }; diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 590e3436dc..08ab1ea45f 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -23,6 +23,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions