import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:uuid/uuid.dart'; import 'package:worldhopper/database/database.dart'; import 'package:worldhopper/models/enhanced_metadata.dart'; import 'package:worldhopper/models/entry.dart'; import 'package:worldhopper/models/publication.dart'; import 'package:worldhopper/repositories/publication_repository.dart'; import 'package:worldhopper/repositories/reading_progress_repository.dart'; import 'package:worldhopper/services/image_cache_service.dart'; part 'publication_cache_provider.g.dart'; /// Provider for PublicationRepository instance @riverpod PublicationRepository publicationRepository(PublicationRepositoryRef ref) { return PublicationRepository(DatabaseHelper.instance); } /// Provider for ImageCacheService instance @riverpod Future imageCacheService(ImageCacheServiceRef ref) async { return await ImageCacheService.create(); } /// Provider for ReadingProgressRepository instance (if not already provided elsewhere) @riverpod ReadingProgressRepository readingProgressRepository( ReadingProgressRepositoryRef ref) { return ReadingProgressRepository(); } /// Provider for publication cache operations @riverpod class PublicationCache extends _$PublicationCache { @override FutureOr build() {} /// Cache a publication from OPDS entry Future cachePublication({ required String serverId, required Entry entry, required EnhancedMetadata metadata, bool downloadImages = true, }) async { final repository = ref.read(publicationRepositoryProvider); final imageService = await ref.read(imageCacheServiceProvider.future); // Check if already cached final existing = await repository.getPublication(serverId, entry.id); String? coverPath = existing?.coverPath; String? thumbnailPath = existing?.thumbnailPath; // Download images if needed if (downloadImages) { final publicationId = existing?.id ?? const Uuid().v4(); if (entry.coverUrl != null && coverPath == null) { coverPath = await imageService.cacheCoverImage( publicationId, entry.coverUrl!, ); } if (entry.thumbnailUrl != null && thumbnailPath == null) { thumbnailPath = await imageService.cacheThumbnailImage( publicationId, entry.thumbnailUrl!, ); } } // Create or update publication final publication = existing != null ? existing.copyWith( title: entry.title, authors: entry.authors, summary: entry.summary, content: entry.content, coverPath: coverPath ?? existing.coverPath, thumbnailPath: thumbnailPath ?? existing.thumbnailPath, coverUrl: entry.coverUrl, thumbnailUrl: entry.thumbnailUrl, series: metadata.series, seriesPosition: metadata.seriesPosition, publisher: metadata.publisher, language: metadata.language, isbn: metadata.isbn, categories: entry.categories, links: entry.links, streamLink: entry.streamLink, published: entry.published, updated: entry.updated, lastCachedAt: DateTime.now(), lastAccessedAt: DateTime.now(), ) : Publication.fromEntry( id: const Uuid().v4(), serverId: serverId, entry: entry, metadata: metadata, coverPath: coverPath, thumbnailPath: thumbnailPath, ); await repository.savePublication(publication); return publication; } /// Update last accessed timestamp Future touchPublication(String publicationId) async { final repository = ref.read(publicationRepositoryProvider); await repository.updateLastAccessed(publicationId); } /// Clean up stale cache entries Future cleanupStaleCache() async { final repository = ref.read(publicationRepositoryProvider); final imageService = await ref.read(imageCacheServiceProvider.future); final progressRepo = ref.read(readingProgressRepositoryProvider); final stalePublications = await repository.getStalePublications(); int deletedCount = 0; for (final pub in stalePublications) { // Check if linked to reading progress final hasProgress = await progressRepo.getProgress(pub.entryId, pub.serverId); if (hasProgress == null) { // Safe to delete await imageService.deleteCachedImages(pub.id); await repository.deletePublication(pub.id); deletedCount++; } } return deletedCount; } } /// Provider for single publication lookup @riverpod Future cachedPublication( CachedPublicationRef ref, String serverId, String entryId, ) async { final repository = ref.watch(publicationRepositoryProvider); return repository.getPublication(serverId, entryId); }