feat(w2): add PackService for .novel and .novel-all export/import
Introduce pack-bootstrap chunk, ZipArchive-based PackService with progress IPC, book registry import helpers, and IT-PACK-01 / IT-ALL-02 integration tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { dialog, ipcMain } from 'electron'
|
||||
import { IPC } from '../../../shared/ipc-channels'
|
||||
import type { PackImportStrategy } from '../../../shared/novel-pack'
|
||||
import { PackService } from '../../services/pack.service'
|
||||
import type { BookRegistryService } from '../../services/book-registry'
|
||||
import type { GlobalSettingsService } from '../../services/global-settings'
|
||||
import { wrap } from '../result'
|
||||
|
||||
let packService: PackService | null = null
|
||||
|
||||
function svc(registry: BookRegistryService, settings: GlobalSettingsService): PackService {
|
||||
if (!packService) {
|
||||
packService = new PackService(registry, settings, registry.getUserDataDir())
|
||||
}
|
||||
return packService
|
||||
}
|
||||
|
||||
export function registerPackHandlers(
|
||||
registry: BookRegistryService,
|
||||
settings: GlobalSettingsService
|
||||
): void {
|
||||
const service = () => svc(registry, settings)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.PACK_EXPORT_BOOK,
|
||||
(_e, { bookId, destPath }: { bookId: string; destPath: string }) =>
|
||||
wrap(() => service().exportBook(bookId, destPath))
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.PACK_IMPORT_BOOK, (_e, { filePath }: { filePath: string }) =>
|
||||
wrap(() => service().importBook(filePath))
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.PACK_EXPORT_ALL, (_e, { destPath }: { destPath: string }) =>
|
||||
wrap(() => service().exportAll(destPath))
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
IPC.PACK_IMPORT_ALL,
|
||||
(_e, { filePath, strategy }: { filePath: string; strategy: PackImportStrategy }) =>
|
||||
wrap(() => service().importAll(filePath, strategy))
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.PACK_ESTIMATE_EXPORT_ALL, () =>
|
||||
wrap(() => ({
|
||||
bytes: service().estimateExportAllBytes(),
|
||||
needsConfirm: service().needsExportAllConfirm()
|
||||
}))
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.PACK_NEEDS_CONFIRM, () =>
|
||||
wrap(() => service().needsExportAllConfirm())
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.PACK_CANCEL, () =>
|
||||
wrap(() => {
|
||||
service().cancel()
|
||||
})
|
||||
)
|
||||
|
||||
ipcMain.handle(IPC.PACK_PICK_FILE, (_e, { mode }: { mode: 'novel' | 'novel-all' | 'save-novel' | 'save-all' }) =>
|
||||
wrap(async () => {
|
||||
if (mode === 'save-novel' || mode === 'save-all') {
|
||||
const ext = mode === 'save-all' ? 'novel-all' : 'novel'
|
||||
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||
filters: [{ name: 'Bilin Pack', extensions: [ext] }],
|
||||
defaultPath: mode === 'save-all' ? 'bilin-backup.novel-all' : 'book.novel'
|
||||
})
|
||||
if (canceled || !filePath) return null
|
||||
return filePath
|
||||
}
|
||||
const extensions = mode === 'novel-all' ? ['novel-all'] : ['novel', 'novel-all']
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: 'Bilin Pack', extensions }]
|
||||
})
|
||||
if (canceled || filePaths.length === 0) return null
|
||||
return filePaths[0]
|
||||
})
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user