import 'dart:io'; import 'package:archive/archive.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; import 'package:uuid/uuid.dart'; import 'epub_parser.dart'; import 'reader_dao.dart'; import 'reader_models.dart'; /// EPUB 服务层 - 管理导入、解压、删除 class EpubService { final ReaderDao _dao = ReaderDao(); final EpubParser _parser = EpubParser(); static const _uuid = Uuid(); /// 导入 EPUB 文件 /// 返回 {'bookId': ..., 'title': ...} 或 null(解析失败) Future?> importBook(String sourcePath) async { final bookId = _uuid.v4(); final now = DateTime.now().toIso8601String(); final fileName = p.basename(sourcePath); // 复制 EPUB 到永久存储(FilePicker 临时文件会被清理) final appDir = await getApplicationDocumentsDirectory(); final bookDir = Directory(p.join(appDir.path, 'epub_books', bookId)); if (!await bookDir.exists()) await bookDir.create(recursive: true); final permanentPath = p.join(bookDir.path, 'book.epub'); await File(sourcePath).copy(permanentPath); // 从永久副本解析 final info = await _parser.parseFromFile( permanentPath, fileName: fileName, ); if (info == null) return null; // 解压到临时目录 final extractDir = await getExtractDir(bookId); await _extractEpub(permanentPath, extractDir); // 提取封面 String? coverPath; if (info.coverHref != null) { coverPath = await _extractCover(info, extractDir, bookId); } // 写入数据库(file_path 存永久路径) await _dao.insertReaderBook({ 'id': bookId, 'title': info.title, 'author': info.author, 'cover_path': coverPath, 'file_path': permanentPath, 'file_name': fileName, 'file_extension': 'epub', 'last_read_cfi': '', 'reading_percentage': 0.0, 'created_at': now, 'updated_at': now, 'is_deleted': 0, }); return {'bookId': bookId, 'title': info.title}; } /// 解压 EPUB 到目标目录 Future _extractEpub(String sourcePath, String targetDir) async { final dir = Directory(targetDir); if (await dir.exists()) await dir.delete(recursive: true); await dir.create(recursive: true); final bytes = await File(sourcePath).readAsBytes(); final archive = ZipDecoder().decodeBytes(bytes); for (final file in archive) { final filePath = p.join(targetDir, file.name); if (file.isFile) { final outFile = File(filePath); await outFile.parent.create(recursive: true); await outFile.writeAsBytes(file.content as List); } else { await Directory(filePath).create(recursive: true); } } } /// 提取封面图片 Future _extractCover( EpubBookInfo info, String extractDir, String bookId) async { try { final opfDir = info.opfRootPath.contains('/') ? info.opfRootPath.substring(0, info.opfRootPath.lastIndexOf('/')) : ''; final coverRelPath = opfDir.isEmpty ? info.coverHref! : '$opfDir/${info.coverHref!}'; final coverFile = File(p.join(extractDir, coverRelPath)); if (!await coverFile.exists()) return null; // 保存到 epub_books/{bookId}/ 目录下 final appDir = await getApplicationDocumentsDirectory(); final coverDir = p.join(appDir.path, 'epub_books', bookId); await Directory(coverDir).create(recursive: true); final ext = p.extension(coverFile.path).toLowerCase(); final destPath = p.join(coverDir, 'cover$ext'); await coverFile.copy(destPath); return destPath; } catch (_) { return null; } } /// 获取解压目录 Future getExtractDir(String bookId) async { final tempDir = await getTemporaryDirectory(); return p.join(tempDir.path, 'epub', bookId); } /// 确保已解压(如果临时目录被清理则重新解压) /// 返回解压目录路径,失败返回 null Future ensureExtracted(String bookId, String filePath) async { final extractDir = await getExtractDir(bookId); final dir = Directory(extractDir); if (await dir.exists()) { final files = dir.listSync(); if (files.isNotEmpty) return extractDir; } // 重新解压 if (!await File(filePath).exists()) return null; await _extractEpub(filePath, extractDir); return extractDir; } /// 删除书籍 Future deleteBook(String bookId) async { // 清理解压目录 try { final extractDir = await getExtractDir(bookId); final dir = Directory(extractDir); if (await dir.exists()) await dir.delete(recursive: true); } catch (_) {} // 清理 epub_books/{bookId}/ 目录(epub + 封面) try { final appDir = await getApplicationDocumentsDirectory(); final bookDir = Directory(p.join(appDir.path, 'epub_books', bookId)); if (await bookDir.exists()) await bookDir.delete(recursive: true); } catch (_) {} // 软删除数据库记录 await _dao.deleteReaderBook(bookId); } }