diff --git a/src/main/db/interruptedIndexing.ts b/src/main/db/interruptedIndexing.ts new file mode 100644 index 0000000..9297992 --- /dev/null +++ b/src/main/db/interruptedIndexing.ts @@ -0,0 +1,23 @@ +export const INTERRUPTED_INDEXING_ERROR = + 'Indexing was interrupted because KnowNote exited. Retry indexing to continue.' + +interface RecoveryDatabase { + prepare(sql: string): { + run(error: string, updatedAt: number): { changes: number | bigint } + } +} + +/** Run only at desktop startup, before any ingestion jobs or folder watchers start. */ +export function markInterruptedIndexingFailed( + database: RecoveryDatabase, + now = new Date() +): number { + const result = database + .prepare( + `UPDATE documents + SET status = 'failed', error_message = ?, updated_at = ? + WHERE status IN ('pending', 'processing')` + ) + .run(INTERRUPTED_INDEXING_ERROR, Math.floor(now.getTime() / 1000)) + return Number(result.changes) +} diff --git a/src/main/index.ts b/src/main/index.ts index c3b1497..cb352ba 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -6,8 +6,10 @@ import { runMigrations, initVectorStore, closeDatabase, - executeCheckpoint + executeCheckpoint, + getSqlite } from './db' +import { markInterruptedIndexingFailed } from './db/interruptedIndexing' import { ConnectionManager } from './models/ConnectionManager' import { EmbeddingService } from './services/EmbeddingService' import { SessionAutoSwitchService } from './services/SessionAutoSwitchService' @@ -93,6 +95,14 @@ app.whenReady().then(async () => { Logger.info('Main', 'Initializing database...') initDatabase() runMigrations() + // The ingestion queue is in memory. Leftover live statuses belong to the previous + // process; make them retryable before folder reconciliation can enqueue new jobs. + const sqlite = getSqlite() + if (!sqlite) throw new Error('Database unavailable during indexing recovery') + const interrupted = markInterruptedIndexingFailed(sqlite) + if (interrupted > 0) { + Logger.info('Main', `Marked ${interrupted} interrupted indexing task(s) as failed`) + } initVectorStore() // The resolver is lazy on purpose: `knowledgeService` is constructed further down, and // the protocol only ever answers requests from a renderer that loads after startup. diff --git a/src/renderer/src/components/notebook/anki/AnkiConfigDialog.tsx b/src/renderer/src/components/notebook/anki/AnkiConfigDialog.tsx index 94be51e..e86b481 100644 --- a/src/renderer/src/components/notebook/anki/AnkiConfigDialog.tsx +++ b/src/renderer/src/components/notebook/anki/AnkiConfigDialog.tsx @@ -136,7 +136,7 @@ export default function AnkiConfigDialog({ - diff --git a/src/renderer/src/components/notebook/quiz/QuizStartDialog.tsx b/src/renderer/src/components/notebook/quiz/QuizStartDialog.tsx index fd41f02..867c5a3 100644 --- a/src/renderer/src/components/notebook/quiz/QuizStartDialog.tsx +++ b/src/renderer/src/components/notebook/quiz/QuizStartDialog.tsx @@ -131,7 +131,7 @@ export default function QuizStartDialog({ -