优化项目结构

This commit is contained in:
DelLevin-Home
2026-07-05 13:58:25 +08:00
parent 82aca12402
commit de180eee47
41 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
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<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);
}
}