worldhopper/lib/repositories/publication_repository.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

113 lines
3.3 KiB
Dart

import 'package:sqflite/sqflite.dart';
import 'package:worldhopper/database/database.dart';
import 'package:worldhopper/database/tables/publications_table.dart';
import 'package:worldhopper/models/publication.dart';
/// Repository for publication cache operations
class PublicationRepository {
final DatabaseHelper _dbHelper;
PublicationRepository(this._dbHelper);
/// Get publication by server and entry ID
Future<Publication?> getPublication(String serverId, String entryId) async {
final db = await _dbHelper.database;
final results = await db.query(
PublicationsTable.tableName,
where: 'server_id = ? AND entry_id = ?',
whereArgs: [serverId, entryId],
);
if (results.isEmpty) return null;
return Publication.fromDatabase(results.first);
}
/// Get publication by cache ID
Future<Publication?> getPublicationById(String id) async {
final db = await _dbHelper.database;
final results = await db.query(
PublicationsTable.tableName,
where: 'id = ?',
whereArgs: [id],
);
if (results.isEmpty) return null;
return Publication.fromDatabase(results.first);
}
/// Save or update publication
Future<void> savePublication(Publication publication) async {
final db = await _dbHelper.database;
await db.insert(
PublicationsTable.tableName,
publication.toDatabase(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
/// Update last accessed timestamp
Future<void> updateLastAccessed(String id) async {
final db = await _dbHelper.database;
await db.update(
PublicationsTable.tableName,
{'last_accessed_at': DateTime.now().millisecondsSinceEpoch},
where: 'id = ?',
whereArgs: [id],
);
}
/// Get publications for a server
Future<List<Publication>> getPublicationsByServer(String serverId) async {
final db = await _dbHelper.database;
final results = await db.query(
PublicationsTable.tableName,
where: 'server_id = ?',
whereArgs: [serverId],
orderBy: 'last_accessed_at DESC',
);
return results.map((row) => Publication.fromDatabase(row)).toList();
}
/// Get stale publications (not accessed recently and old)
Future<List<Publication>> getStalePublications({
int notAccessedDays = 60,
int olderThanDays = 30,
}) async {
final db = await _dbHelper.database;
final notAccessedDate = DateTime.now()
.subtract(Duration(days: notAccessedDays))
.millisecondsSinceEpoch;
final olderThanDate = DateTime.now()
.subtract(Duration(days: olderThanDays))
.millisecondsSinceEpoch;
final results = await db.query(
PublicationsTable.tableName,
where: 'last_accessed_at < ? AND last_cached_at < ?',
whereArgs: [notAccessedDate, olderThanDate],
);
return results.map((row) => Publication.fromDatabase(row)).toList();
}
/// Delete publication
Future<void> deletePublication(String id) async {
final db = await _dbHelper.database;
await db.delete(
PublicationsTable.tableName,
where: 'id = ?',
whereArgs: [id],
);
}
/// Delete publications for a server
Future<void> deletePublicationsByServer(String serverId) async {
final db = await _dbHelper.database;
await db.delete(
PublicationsTable.tableName,
where: 'server_id = ?',
whereArgs: [serverId],
);
}
}