mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix(electron): close export database handles
Close the uncached readonly adapter after every Desktop export so Windows can delete the session without restarting. Add regression coverage for the successful export lifecycle.
This commit is contained in:
1 parent
1a774592ff
commit
78e90abb3f
3 files changed
+93
-29
No files matched your search
@@ -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 }
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
})
|
||||
@@ -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 }
|
||||
}
|
||||
Reference in new issue
Block a user