import 'dart:io'; import 'dart:typed_data'; import 'package:archive/archive.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; class EpubStreamService { String? _currentBookPath; String? _pendingBookPath; Future? _openBookFuture; /// Cached decoded archive for the current book. Archive? _cachedArchive; Future warmUp() async {} Future openBook(String epubPath) { if (_currentBookPath == epubPath && _cachedArchive != null) { return Future.value(); } if (_pendingBookPath == epubPath && _openBookFuture != null) { return _openBookFuture!; } _pendingBookPath = epubPath; _openBookFuture = _doOpenBook(epubPath); return _openBookFuture!; } Future _doOpenBook(String epubPath) async { try { final bytes = await File(epubPath).readAsBytes(); _cachedArchive = ZipDecoder().decodeBytes(bytes); _currentBookPath = epubPath; } catch (e) { _currentBookPath = null; _cachedArchive = null; rethrow; } finally { if (_pendingBookPath == epubPath) { _pendingBookPath = null; _openBookFuture = null; } } } /// Read a single file from the currently open EPUB archive. /// Returns the file bytes, or null if not found / no book loaded. Future readFileFromEpub({ required String targetFilePath, String? epubPath, }) async { if (epubPath != null && epubPath != _currentBookPath) { await openBook(epubPath); } if (_currentBookPath == null || _cachedArchive == null) { return null; } final file = _cachedArchive!.findFile(targetFilePath); if (file == null || file.content == null) { return null; } return file.content is Uint8List ? file.content as Uint8List : Uint8List.fromList(file.content as List); } void dispose() { _cachedArchive = null; _currentBookPath = null; _pendingBookPath = null; _openBookFuture = null; } String getMimeType(String filePath) { final ext = p.extension(filePath).toLowerCase().replaceAll('.', ''); return _mimeTypeMap[ext] ?? 'application/octet-stream'; } static const _mimeTypeMap = { 'html': 'text/html', 'htm': 'text/html', 'xhtml': 'application/xhtml+xml', 'xml': 'application/xml', 'css': 'text/css', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'svg': 'image/svg+xml', 'webp': 'image/webp', 'ttf': 'font/ttf', 'otf': 'font/otf', 'woff': 'font/woff', 'woff2': 'font/woff2', 'js': 'application/javascript', }; }