mirror of
https://github.com/zadam/trilium.git
synced 2026-05-06 15:26:59 +02:00
refactor(backup): constructor-based dependency injection for options
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { DatabaseBackup } from "@triliumnext/commons";
|
||||
import { BackupService, getSql } from "@triliumnext/core";
|
||||
import { BackupOptionsService, BackupService, getSql } from "@triliumnext/core";
|
||||
|
||||
const BACKUP_DIR_NAME = "backups";
|
||||
const BACKUP_FILE_PATTERN = /^backup-.*\.db$/;
|
||||
@@ -13,6 +13,10 @@ export default class StandaloneBackupService extends BackupService {
|
||||
private backupDir: FileSystemDirectoryHandle | null = null;
|
||||
private opfsAvailable: boolean | null = null;
|
||||
|
||||
constructor(getOptions: () => BackupOptionsService) {
|
||||
super(getOptions);
|
||||
}
|
||||
|
||||
private isOpfsAvailable(): boolean {
|
||||
if (this.opfsAvailable === null) {
|
||||
this.opfsAvailable = typeof navigator !== "undefined"
|
||||
|
||||
@@ -176,7 +176,7 @@ async function initialize(): Promise<void> {
|
||||
request: new FetchRequestProvider(),
|
||||
platform: new StandalonePlatformProvider(queryString),
|
||||
log: logService,
|
||||
backup: new StandaloneBackupService(),
|
||||
backup: new StandaloneBackupService(() => coreModule!.options),
|
||||
translations: translationProvider,
|
||||
schema: schemaModule.default,
|
||||
getDemoArchive: async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { initializeCore } from "@triliumnext/core";
|
||||
import { initializeCore, options } from "@triliumnext/core";
|
||||
import schemaSql from "@triliumnext/core/src/assets/schema.sql?raw";
|
||||
import HappyDomHtmlParser from "happy-dom/lib/html-parser/HTMLParser.js";
|
||||
import serverEnTranslations from "../../server/src/assets/translations/en/server.json";
|
||||
@@ -130,7 +130,7 @@ beforeAll(async () => {
|
||||
});
|
||||
},
|
||||
platform: new StandalonePlatformProvider(""),
|
||||
backup: new StandaloneBackupService(),
|
||||
backup: new StandaloneBackupService(() => options),
|
||||
schema: schemaSql,
|
||||
dbConfig: {
|
||||
provider: sqlProvider,
|
||||
|
||||
@@ -4,7 +4,7 @@ import NodejsCryptoProvider from "@triliumnext/server/src/crypto_provider.js";
|
||||
import { loadCoreSchema } from "@triliumnext/server/src/core_assets.js";
|
||||
import NodejsInAppHelpProvider from "@triliumnext/server/src/in_app_help_provider.js";
|
||||
import dataDirs from "@triliumnext/server/src/services/data_dir.js";
|
||||
import options from "@triliumnext/server/src/services/options.js";
|
||||
import { options } from "@triliumnext/core";
|
||||
import port from "@triliumnext/server/src/services/port.js";
|
||||
import NodeRequestProvider from "@triliumnext/server/src/services/request.js";
|
||||
import { RESOURCE_DIR } from "@triliumnext/server/src/services/resource_dir.js";
|
||||
@@ -151,7 +151,7 @@ async function main() {
|
||||
// both source and bundled-production modes.
|
||||
getDemoArchive: async () => fs.readFileSync(path.join(RESOURCE_DIR, "db", "demo.zip")),
|
||||
inAppHelp: new NodejsInAppHelpProvider(),
|
||||
backup: new ServerBackupService(),
|
||||
backup: new ServerBackupService(() => options),
|
||||
image: (await import("@triliumnext/server/src/services/image_provider.js")).serverImageProvider,
|
||||
extraAppInfo: {
|
||||
nodeVersion: process.version,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeAll } from "vitest";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { initializeCore } from "@triliumnext/core";
|
||||
import { initializeCore, options } from "@triliumnext/core";
|
||||
import { serverZipExportProviderFactory } from "../src/services/export/zip/factory.js";
|
||||
import ServerBackupService from "../src/backup_provider.js";
|
||||
import ClsHookedExecutionContext from "../src/cls_provider.js";
|
||||
@@ -44,6 +44,6 @@ beforeAll(async () => {
|
||||
platform: new ServerPlatformProvider(),
|
||||
translations: initializeTranslationsWithParams,
|
||||
inAppHelp: new NodejsInAppHelpProvider(),
|
||||
backup: new ServerBackupService()
|
||||
backup: new ServerBackupService(() => options)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DatabaseBackup } from "@triliumnext/commons";
|
||||
import { BackupService, sync_mutex as syncMutexService } from "@triliumnext/core";
|
||||
import { BackupOptionsService, BackupService, sync_mutex as syncMutexService } from "@triliumnext/core";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
@@ -8,6 +8,10 @@ import log from "./services/log.js";
|
||||
import sql from "./services/sql.js";
|
||||
|
||||
export default class ServerBackupService extends BackupService {
|
||||
constructor(getOptions: () => BackupOptionsService) {
|
||||
super(getOptions);
|
||||
}
|
||||
|
||||
override getExistingBackups(): DatabaseBackup[] {
|
||||
if (!fs.existsSync(dataDir.BACKUP_DIR)) {
|
||||
return [];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* are loaded later and will result in an empty string.
|
||||
*/
|
||||
|
||||
import { getLog, initializeCore, sql_init } from "@triliumnext/core";
|
||||
import { getLog, initializeCore, options, sql_init } from "@triliumnext/core";
|
||||
import fs from "fs";
|
||||
import { t } from "i18next";
|
||||
import path from "path";
|
||||
@@ -81,7 +81,7 @@ async function startApplication() {
|
||||
// both source and bundled-production modes.
|
||||
getDemoArchive: async () => fs.readFileSync(path.join(RESOURCE_DIR, "db", "demo.zip")),
|
||||
inAppHelp: new NodejsInAppHelpProvider(),
|
||||
backup: new ServerBackupService(),
|
||||
backup: new ServerBackupService(() => options),
|
||||
image: (await import("./services/image_provider.js")).serverImageProvider,
|
||||
extraAppInfo: {
|
||||
nodeVersion: process.version,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ExecutionContext, initContext } from "./services/context";
|
||||
import { CryptoProvider, initCrypto } from "./services/encryption/crypto";
|
||||
import LogService, { getLog, initLog } from "./services/log";
|
||||
import BackupService, { initBackup } from "./services/backup";
|
||||
import BackupService, { initBackup, type BackupOptionsService } from "./services/backup";
|
||||
import { initSql } from "./services/sql/index";
|
||||
import { SqlService, SqlServiceParams } from "./services/sql/sql";
|
||||
import { initMessaging, MessagingProvider } from "./services/messaging/index";
|
||||
@@ -17,7 +17,7 @@ import { type ImageProvider, initImageProvider } from "./services/image_provider
|
||||
|
||||
export { default as LogService, getLog } from "./services/log";
|
||||
export { default as FileBasedLogService, type LogFileInfo } from "./services/file_based_log";
|
||||
export { default as BackupService, getBackup, initBackup } from "./services/backup";
|
||||
export { default as BackupService, getBackup, initBackup, type BackupOptionsService } from "./services/backup";
|
||||
export type * from "./services/sql/types";
|
||||
export * from "./services/sql/index";
|
||||
export { default as sql_init } from "./services/sql_init";
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import type { DatabaseBackup, OptionNames } from "@triliumnext/commons";
|
||||
import type { DatabaseBackup, FilterOptionsByType, OptionNames } from "@triliumnext/commons";
|
||||
import { getContext } from "./context.js";
|
||||
import dateUtils from "./utils/date.js";
|
||||
|
||||
type BackupType = "daily" | "weekly" | "monthly";
|
||||
|
||||
// Lazy-loaded to avoid circular dependency (options -> becca -> entities)
|
||||
let optionsModule: Awaited<typeof import("./options.js")>["default"] | null = null;
|
||||
|
||||
async function getOptions() {
|
||||
if (!optionsModule) {
|
||||
optionsModule = (await import("./options.js")).default;
|
||||
}
|
||||
return optionsModule!;
|
||||
export interface BackupOptionsService {
|
||||
getOption(name: OptionNames): string;
|
||||
getOptionBool(name: FilterOptionsByType<boolean>): boolean;
|
||||
setOption(name: OptionNames, value: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,6 +15,8 @@ async function getOptions() {
|
||||
* Platform-specific implementations must extend this class.
|
||||
*/
|
||||
export default abstract class BackupService {
|
||||
constructor(protected readonly getOptions: () => BackupOptionsService) {}
|
||||
|
||||
/**
|
||||
* Create a backup with the given name.
|
||||
* Returns the backup file path/name.
|
||||
@@ -32,7 +30,6 @@ export default abstract class BackupService {
|
||||
*/
|
||||
regularBackup(): void {
|
||||
getContext().init(() => {
|
||||
// Fire and forget - the async work runs in background
|
||||
this.runScheduledBackups().catch(err => {
|
||||
console.error("[Backup] Error running scheduled backups:", err);
|
||||
});
|
||||
@@ -46,7 +43,6 @@ export default abstract class BackupService {
|
||||
|
||||
/**
|
||||
* Run the scheduled backup checks for daily, weekly, and monthly backups.
|
||||
* Can be overridden by subclasses if they need custom behavior.
|
||||
*/
|
||||
protected async runScheduledBackups(): Promise<void> {
|
||||
await this.periodBackup("lastDailyBackupDate", "daily", 24 * 3600);
|
||||
@@ -57,22 +53,13 @@ export default abstract class BackupService {
|
||||
/**
|
||||
* Check if a specific backup type is enabled via options.
|
||||
*/
|
||||
protected async isBackupEnabled(backupType: BackupType): Promise<boolean> {
|
||||
const options = await getOptions();
|
||||
let optionName: OptionNames;
|
||||
switch (backupType) {
|
||||
case "daily":
|
||||
optionName = "dailyBackupEnabled";
|
||||
break;
|
||||
case "weekly":
|
||||
optionName = "weeklyBackupEnabled";
|
||||
break;
|
||||
case "monthly":
|
||||
optionName = "monthlyBackupEnabled";
|
||||
break;
|
||||
}
|
||||
protected isBackupEnabled(backupType: BackupType): boolean {
|
||||
const optionName: FilterOptionsByType<boolean> =
|
||||
backupType === "daily" ? "dailyBackupEnabled" :
|
||||
backupType === "weekly" ? "weeklyBackupEnabled" :
|
||||
"monthlyBackupEnabled";
|
||||
|
||||
return options.getOptionBool(optionName);
|
||||
return this.getOptions().getOptionBool(optionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,12 +70,11 @@ export default abstract class BackupService {
|
||||
backupType: BackupType,
|
||||
periodInSeconds: number
|
||||
): Promise<void> {
|
||||
if (!(await this.isBackupEnabled(backupType))) {
|
||||
if (!this.isBackupEnabled(backupType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = await getOptions();
|
||||
|
||||
const options = this.getOptions();
|
||||
const now = new Date();
|
||||
const lastBackupDate = dateUtils.parseDateTime(options.getOption(optionName));
|
||||
|
||||
@@ -103,7 +89,6 @@ let backupService: BackupService | undefined;
|
||||
|
||||
/**
|
||||
* Get the current backup service instance.
|
||||
* Throws if no provider has been initialized.
|
||||
*/
|
||||
export function getBackup(): BackupService {
|
||||
if (!backupService) {
|
||||
|
||||
Reference in New Issue
Block a user