worldhopper/lib/services/offline_content_checker.dart
Felipe M. 58a562abe8
feat: refactor server software abstraction with Kavita REST API integration
Rename all OPDS-prefixed models to generic names (Server, Feed, Entry,
Link, StreamLink), expand the ServerSoftware interface to cover all
server interactions, and implement a full Kavita REST API client that
replaces the OPDS delegation.

- Rename OPDS* models to generic names across ~60 files
- Add database migrations 13 (opds_id → entry_id) and 14 (unify credentials)
- Create OPDSServerSoftware wrapping existing OPDS services
- Create KavitaApiClient for direct Kavita REST API calls
- Create KavitaFeedMapper to convert Kavita JSON to Feed/Entry models
- Rewrite KavitaServerSoftware to use native API (no OPDS delegation)
- Unify server credentials (remove softwareUsername/softwarePassword)
- Simplify add/edit server UI to single auth section
- Add test connection button to server add/edit screens
- Add progress indicator to PublicationCard using local and server data
- Eliminate softwareType branching in reader screens
- Add EntryProgress and fetchEntryProgress to ServerSoftware interface
- Fix Entry.acquisitionLink crash on empty links
- Add 59 new tests covering models, services, and providers
2026-04-06 17:46:48 +02:00

93 lines
3.1 KiB
Dart

import 'dart:io';
import 'package:worldhopper/models/downloaded_series.dart';
import 'package:worldhopper/models/entry.dart';
import 'package:worldhopper/repositories/downloaded_chapters_repository.dart';
import 'package:worldhopper/services/epub_download_service.dart';
/// Service for checking if content is available offline
///
/// Determines whether a given OPDS entry can be accessed without
/// an internet connection.
class OfflineContentChecker {
final EpubDownloadService _epubService;
final DownloadedChaptersRepository? _chaptersRepo;
OfflineContentChecker({
required EpubDownloadService epubService,
DownloadedChaptersRepository? chaptersRepo,
}) : _epubService = epubService,
_chaptersRepo = chaptersRepo;
/// Check if the given content is available offline
///
/// Returns true if:
/// - The entry is in the downloaded chapters DB with a valid file path
/// - For EPUBs: Also checks if the file is cached via flutter_cache_manager
///
/// Returns false otherwise.
Future<bool> isContentAvailableOffline(
Entry entry, {
String? serverId,
}) async {
// Check downloaded chapters first (works for both EPUBs and image streams)
final chaptersRepo = _chaptersRepo;
if (chaptersRepo != null && serverId != null) {
final chapter = await chaptersRepo.getByEntryId(serverId, entry.id);
if (chapter != null &&
chapter.status == DownloadStatus.complete &&
chapter.filePath != null) {
// Verify the file/directory actually exists on disk
if (chapter.contentType == ChapterContentType.epub) {
if (await File(chapter.filePath!).exists()) return true;
} else {
if (await Directory(chapter.filePath!).exists()) return true;
}
}
}
// Fall back to EPUB cache check
if (entry.isEpub) {
return await _epubService.isEpubCached(entry);
}
// Image streams without downloads require server connection
if (entry.isImageStream) {
return false;
}
// Default to false for unknown content types
return false;
}
/// Get the local file path for a downloaded chapter
Future<String?> getLocalPath(String serverId, String entryId) async {
final chaptersRepo = _chaptersRepo;
if (chaptersRepo == null) return null;
final chapter = await chaptersRepo.getByEntryId(serverId, entryId);
if (chapter != null &&
chapter.status == DownloadStatus.complete &&
chapter.filePath != null) {
return chapter.filePath;
}
return null;
}
/// Get a detailed status message about offline availability
Future<String> getOfflineStatusMessage(Entry entry) async {
if (entry.isEpub) {
final isCached = await _epubService.isEpubCached(entry);
if (isCached) {
return 'This EPUB is cached and available offline.';
} else {
return 'This EPUB is not cached. Connect to internet to download.';
}
}
if (entry.isImageStream) {
return 'Image streams require an internet connection. Pages may not load offline.';
}
return 'This content requires an internet connection.';
}
}