Files
MookNote/lib/services/epub/epub_service.dart
2026-07-05 13:58:37 +08:00

156 lines
5.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 '../../data/epub/reader_dao.dart';
import '../../data/epub/reader_models.dart';
/// EPUB 服务层 - 管理导入、解压、删除
class EpubService {
final ReaderDao _dao = ReaderDao();
final EpubParser _parser = EpubParser();
static const _uuid = Uuid();
/// 导入 EPUB 文件
/// 返回 {'bookId': ..., 'title': ...} 或 null解析失败
Future<Map<String, dynamic>?> 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<void> _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<int>);
} else {
await Directory(filePath).create(recursive: true);
}
}
}
/// 提取封面图片
Future<String?> _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<String> getExtractDir(String bookId) async {
final tempDir = await getTemporaryDirectory();
return p.join(tempDir.path, 'epub', bookId);
}
/// 确保已解压(如果临时目录被清理则重新解压)
/// 返回解压目录路径,失败返回 null
Future<String?> 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<void> 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);
}
}