diff --git a/apps/desktop/main/worker/query/session/core.ts b/apps/desktop/main/worker/query/session/core.ts index 93e63504..dda4b3c2 100644 --- a/apps/desktop/main/worker/query/session/core.ts +++ b/apps/desktop/main/worker/query/session/core.ts @@ -4,7 +4,7 @@ */ import type Database from 'better-sqlite3' -import { getDbPath, openRawDatabase, closeDatabase } from '../../core' +import { getDbPath, openRawDatabase, closeDatabase } from '../../core/dbCore' // 重新导出 closeDatabase 供其他模块使用 export { closeDatabase } diff --git a/apps/desktop/main/worker/query/session/export.test.ts b/apps/desktop/main/worker/query/session/export.test.ts new file mode 100644 index 00000000..318f0178 --- /dev/null +++ b/apps/desktop/main/worker/query/session/export.test.ts @@ -0,0 +1,51 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' +import Database from 'better-sqlite3' +import { CHAT_DB_SCHEMA } from '@openchatlab/core' +import { BetterSqliteAdapter } from '@openchatlab/node-runtime/src/better-sqlite3-adapter' +import { exportFilterResultToFile } from './export' + +test('closes the export database after writing the file', () => { + const outputDir = fs.mkdtempSync(path.join(process.env.CHATLAB_TEST_TMPDIR ?? os.tmpdir(), 'chatlab-desktop-export-')) + const rawDb = new Database(':memory:', { + nativeBinding: path.resolve('apps/cli/native/better_sqlite3.node'), + }) + rawDb.exec(CHAT_DB_SCHEMA) + rawDb.exec(` + INSERT INTO meta (name, platform, type, imported_at) + VALUES ('Export test', 'qq', 'private', 1); + INSERT INTO member (id, platform_id, account_name) + VALUES (1, 'alice', 'Alice'); + INSERT INTO message (sender_id, ts, type, content) + VALUES (1, 1, 0, 'hello'); + `) + + let closeCount = 0 + const adapter = new BetterSqliteAdapter(rawDb) + const close = adapter.close.bind(adapter) + adapter.close = () => { + closeCount += 1 + close() + } + + try { + const result = exportFilterResultToFile( + { + sessionId: 'export-test', + sessionName: 'Export test', + outputDir, + }, + () => adapter + ) + + assert.equal(result.success, true) + assert.equal(closeCount, 1) + assert.equal(fs.existsSync(result.filePath!), true) + } finally { + if (rawDb.open) rawDb.close() + fs.rmSync(outputDir, { recursive: true, force: true }) + } +}) diff --git a/apps/desktop/main/worker/query/session/export.ts b/apps/desktop/main/worker/query/session/export.ts index ad6f1dea..a28d3ee3 100644 --- a/apps/desktop/main/worker/query/session/export.ts +++ b/apps/desktop/main/worker/query/session/export.ts @@ -7,39 +7,52 @@ import * as fs from 'fs' import * as path from 'path' -import { BetterSqliteAdapter } from '@openchatlab/node-runtime' -import { exportWithFormat } from '@openchatlab/node-runtime' -import type { ExportFormat } from '@openchatlab/node-runtime' +import type { DatabaseAdapter } from '@openchatlab/core' +import { BetterSqliteAdapter } from '@openchatlab/node-runtime/src/better-sqlite3-adapter' +import { exportWithFormat, type ExportFormat } from '@openchatlab/node-runtime/src/export/format-exporter' import { openReadonlyDatabase } from './core' -export function exportFilterResultToFile(params: { - sessionId: string - sessionName: string - outputDir: string - format?: ExportFormat - timeFilter?: { startTs: number; endTs: number } -}): { success: boolean; filePath?: string; error?: string } { +function openExportDatabase(sessionId: string): DatabaseAdapter | null { + const rawDb = openReadonlyDatabase(sessionId) + return rawDb ? new BetterSqliteAdapter(rawDb) : null +} + +export function exportFilterResultToFile( + params: { + sessionId: string + sessionName: string + outputDir: string + format?: ExportFormat + timeFilter?: { startTs: number; endTs: number } + }, + openDatabase: (sessionId: string) => DatabaseAdapter | null = openExportDatabase +): { + success: boolean + filePath?: string + error?: string +} { const format: ExportFormat = params.format || 'txt' + const openedDatabase = openDatabase(params.sessionId) + + try { + const result = exportWithFormat( + { + sessionId: params.sessionId, + sessionName: params.sessionName, + format, + timeFilter: params.timeFilter, + }, + () => openedDatabase + ) - const result = exportWithFormat( - { - sessionId: params.sessionId, - sessionName: params.sessionName, - format, - timeFilter: params.timeFilter, - }, - (sessionId) => { - const rawDb = openReadonlyDatabase(sessionId) - if (!rawDb) return null - return new BetterSqliteAdapter(rawDb) + if (!result.success) { + return { success: false, error: result.error } } - ) - if (!result.success) { - return { success: false, error: result.error } + const filePath = path.join(params.outputDir, result.filename) + fs.writeFileSync(filePath, result.content, 'utf8') + return { success: true, filePath } + } finally { + openedDatabase?.close() } - - const filePath = path.join(params.outputDir, result.filename) - fs.writeFileSync(filePath, result.content, 'utf8') - return { success: true, filePath } }