diff --git a/apps/client-standalone/src/lightweight/backup_provider.ts b/apps/client-standalone/src/lightweight/backup_provider.ts index a03502a1fe..d25b03c729 100644 --- a/apps/client-standalone/src/lightweight/backup_provider.ts +++ b/apps/client-standalone/src/lightweight/backup_provider.ts @@ -72,17 +72,7 @@ export default class StandaloneBackupService extends BackupService { } } - override getExistingBackups(): DatabaseBackup[] { - // This method is synchronous in the interface, but OPFS is async. - // Return empty array - the UI can use an async method if needed. - // A future improvement could cache the backup list. - return []; - } - - /** - * Async version of getExistingBackups for use in standalone UI. - */ - async getExistingBackupsAsync(): Promise { + override async getExistingBackups(): Promise { if (!this.isOpfsAvailable()) { return []; } @@ -100,7 +90,6 @@ export default class StandaloneBackupService extends BackupService { continue; } - // OPFS doesn't provide mtime, so we parse from filename or use current time const file = await (handle as FileSystemFileHandle).getFile(); backups.push({ fileName: name, diff --git a/apps/client-standalone/src/lightweight/log_provider.ts b/apps/client-standalone/src/lightweight/log_provider.ts index ec50b884bd..7eddc54c33 100644 --- a/apps/client-standalone/src/lightweight/log_provider.ts +++ b/apps/client-standalone/src/lightweight/log_provider.ts @@ -42,12 +42,36 @@ export default class StandaloneLogService extends FileBasedLogService { } const fileHandle = await this.logDir!.getFileHandle(fileName, { create: true }); - this.currentFile = await fileHandle.createSyncAccessHandle(); + + // Try to create sync access handle with retry logic for worker restarts + // Previous worker may have left handle open before being terminated + const maxRetries = 3; + const retryDelay = 100; + + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + this.currentFile = await fileHandle.createSyncAccessHandle(); + break; + } catch (error) { + if (attempt === maxRetries - 1) { + // Last attempt failed - fall back to console-only logging + console.warn("[LogService] Could not open log file, using console-only logging:", error); + this.currentFile = null; + this.currentFileName = ""; + return; + } + // Wait before retrying - previous handle may be released + await new Promise(resolve => setTimeout(resolve, retryDelay * (attempt + 1))); + } + } + this.currentFileName = fileName; // Seek to end for appending - const size = this.currentFile.getSize(); - this.currentFile.truncate(size); // No-op, but ensures we're at the right position + if (this.currentFile) { + const size = this.currentFile.getSize(); + this.currentFile.truncate(size); // No-op, but ensures we're at the right position + } } protected override closeLogFile(): void { diff --git a/apps/server/src/backup_provider.ts b/apps/server/src/backup_provider.ts index 0151ce81ea..cedf7e4c09 100644 --- a/apps/server/src/backup_provider.ts +++ b/apps/server/src/backup_provider.ts @@ -12,7 +12,7 @@ export default class ServerBackupService extends BackupService { super(options); } - override getExistingBackups(): DatabaseBackup[] { + override async getExistingBackups(): Promise { if (!fs.existsSync(dataDir.BACKUP_DIR)) { return []; } diff --git a/packages/trilium-core/src/routes/api/backup.ts b/packages/trilium-core/src/routes/api/backup.ts index 5e2c405369..68b3c7f7ec 100644 --- a/packages/trilium-core/src/routes/api/backup.ts +++ b/packages/trilium-core/src/routes/api/backup.ts @@ -1,7 +1,7 @@ import type { BackupDatabaseNowResponse, DatabaseBackup } from "@triliumnext/commons"; import { getBackup } from "../../services/backup.js"; -function getExistingBackups(): DatabaseBackup[] { +async function getExistingBackups(): Promise { return getBackup().getExistingBackups(); } diff --git a/packages/trilium-core/src/routes/index.ts b/packages/trilium-core/src/routes/index.ts index bca7658dd5..cd6286de44 100644 --- a/packages/trilium-core/src/routes/index.ts +++ b/packages/trilium-core/src/routes/index.ts @@ -205,7 +205,7 @@ export function buildSharedApiRoutes({ route, asyncRoute, apiRoute, asyncApiRout asyncApiRoute(GET, "/api/backend-log", backendLogRoute.getBackendLog); // Backup routes - apiRoute(GET, "/api/database/backups", backupRoute.getExistingBackups); + asyncApiRoute(GET, "/api/database/backups", backupRoute.getExistingBackups); asyncApiRoute(PST, "/api/database/backup-database", backupRoute.backupDatabase); apiRoute(GET, "/api/other/icon-usage", otherRoute.getIconUsage); diff --git a/packages/trilium-core/src/services/backup.ts b/packages/trilium-core/src/services/backup.ts index 43815b0302..5432aac0bd 100644 --- a/packages/trilium-core/src/services/backup.ts +++ b/packages/trilium-core/src/services/backup.ts @@ -39,7 +39,7 @@ export default abstract class BackupService { /** * Get list of existing backups. */ - abstract getExistingBackups(): DatabaseBackup[]; + abstract getExistingBackups(): Promise; /** * Run the scheduled backup checks for daily, weekly, and monthly backups.