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
93 lines
3.1 KiB
Dart
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.';
|
|
}
|
|
}
|