import 'dart:io'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import 'package:worldhopper/models/entry.dart'; import 'package:worldhopper/models/server.dart'; import 'package:worldhopper/services/auth_service.dart'; /// Service for downloading and caching EPUB files class EpubDownloadService { final AuthService _authService; final CacheManager _cacheManager; EpubDownloadService({ AuthService? authService, CacheManager? cacheManager, }) : _authService = authService ?? AuthService(), _cacheManager = cacheManager ?? DefaultCacheManager(); /// Download EPUB file from server with authentication /// Returns the local file path of the downloaded EPUB Future downloadEpub( Entry entry, Server server, { void Function(double progress)? onProgress, }) async { final acquisitionLink = entry.acquisitionLink; if (acquisitionLink == null) { throw Exception('No acquisition link found for entry: ${entry.title}'); } final url = acquisitionLink.href; if (url.isEmpty) { throw Exception('Invalid acquisition link URL'); } // Check if file is already cached final fileInfo = await _cacheManager.getFileFromCache(url); if (fileInfo != null && await fileInfo.file.exists()) { return fileInfo.file; } // Create authenticated Dio client final dio = _authService.createAuthenticatedClient(server); // Download file final cacheDir = await getTemporaryDirectory(); final fileName = '${entry.id}.epub'; final filePath = path.join(cacheDir.path, fileName); final file = File(filePath); await dio.download( url, filePath, onReceiveProgress: (received, total) { if (total != -1 && onProgress != null) { final progress = received / total; onProgress(progress); } }, ); // Store in cache manager await _cacheManager.putFile( url, await file.readAsBytes(), fileExtension: 'epub', ); return file; } /// Get cached EPUB file if it exists Future getCachedEpub(Entry entry) async { final acquisitionLink = entry.acquisitionLink; if (acquisitionLink == null) return null; final url = acquisitionLink.href; if (url.isEmpty) return null; final fileInfo = await _cacheManager.getFileFromCache(url); if (fileInfo != null && await fileInfo.file.exists()) { return fileInfo.file; } return null; } /// Remove cached EPUB file Future removeCachedEpub(Entry entry) async { final acquisitionLink = entry.acquisitionLink; if (acquisitionLink == null) return; final url = acquisitionLink.href; if (url.isEmpty) return; await _cacheManager.removeFile(url); } /// Check if EPUB is cached Future isEpubCached(Entry entry) async { final acquisitionLink = entry.acquisitionLink; if (acquisitionLink == null) return false; final url = acquisitionLink.href; if (url.isEmpty) return false; final fileInfo = await _cacheManager.getFileFromCache(url); return fileInfo != null && await fileInfo.file.exists(); } /// Get download progress for an EPUB Stream getDownloadProgress(Entry entry, Server server) async* { final acquisitionLink = entry.acquisitionLink; if (acquisitionLink == null) { throw Exception('No acquisition link found for entry: ${entry.title}'); } final url = acquisitionLink.href; if (url.isEmpty) { throw Exception('Invalid acquisition link URL'); } double progress = 0.0; yield progress; await downloadEpub( entry, server, onProgress: (p) { progress = p; }, ); yield progress; } }