worldhopper/lib/repositories/downloaded_chapters_repository.dart
Felipe M. d2497f8aed
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
feat: add background download service with error handling improvements
Add Android foreground service support for downloads to prevent OS
process killing during long operations. Fix multiple error handling
issues: foreground service leak on exceptions, polling stuck forever
on failure, double-counting chapters on retry, silent retry failures
without user feedback, and app crash if background service init fails.
Replace inaccurate failed chapter count heuristic with actual data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 11:44:37 +01:00

131 lines
4.1 KiB
Dart

import 'package:sqflite/sqflite.dart';
import 'package:worldhopper/database/database.dart';
import 'package:worldhopper/database/tables/downloaded_chapters_table.dart';
import 'package:worldhopper/models/downloaded_chapter.dart';
import 'package:worldhopper/models/downloaded_series.dart';
/// Repository for downloaded chapters operations
class DownloadedChaptersRepository {
final DatabaseHelper _dbHelper;
DownloadedChaptersRepository(this._dbHelper);
/// Get all chapters for a series, ordered by sort_order
Future<List<DownloadedChapter>> getChaptersForSeries(String seriesId) async {
final db = await _dbHelper.database;
final results = await db.query(
DownloadedChaptersTable.tableName,
where: 'series_id = ?',
whereArgs: [seriesId],
orderBy: 'sort_order ASC',
);
return results.map((row) => DownloadedChapter.fromDatabase(row)).toList();
}
/// Get a chapter by server ID and entry ID
Future<DownloadedChapter?> getByEntryId(
String serverId, String entryId) async {
final db = await _dbHelper.database;
final results = await db.query(
DownloadedChaptersTable.tableName,
where: 'server_id = ? AND entry_id = ?',
whereArgs: [serverId, entryId],
);
if (results.isEmpty) return null;
return DownloadedChapter.fromDatabase(results.first);
}
/// Get a chapter by ID
Future<DownloadedChapter?> getById(String id) async {
final db = await _dbHelper.database;
final results = await db.query(
DownloadedChaptersTable.tableName,
where: 'id = ?',
whereArgs: [id],
);
if (results.isEmpty) return null;
return DownloadedChapter.fromDatabase(results.first);
}
/// Save or update a downloaded chapter
Future<void> save(DownloadedChapter chapter) async {
final db = await _dbHelper.database;
await db.insert(
DownloadedChaptersTable.tableName,
chapter.toDatabase(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
/// Update the status of a downloaded chapter
Future<void> updateStatus(String id, DownloadStatus status,
{String? filePath, String? errorMessage}) async {
final db = await _dbHelper.database;
final updates = <String, dynamic>{
'status': status.name,
'updated_at': DateTime.now().millisecondsSinceEpoch,
};
if (filePath != null) {
updates['file_path'] = filePath;
}
if (errorMessage != null) {
updates['error_message'] = errorMessage;
}
if (status == DownloadStatus.complete) {
updates['downloaded_at'] = DateTime.now().millisecondsSinceEpoch;
updates['error_message'] = null;
}
await db.update(
DownloadedChaptersTable.tableName,
updates,
where: 'id = ?',
whereArgs: [id],
);
}
/// Update downloaded pages count for an image stream chapter
Future<void> updateDownloadedPages(String id, int downloadedPages) async {
final db = await _dbHelper.database;
await db.update(
DownloadedChaptersTable.tableName,
{
'downloaded_pages': downloadedPages,
'updated_at': DateTime.now().millisecondsSinceEpoch,
},
where: 'id = ?',
whereArgs: [id],
);
}
/// Delete a chapter by ID
Future<void> delete(String id) async {
final db = await _dbHelper.database;
await db.delete(
DownloadedChaptersTable.tableName,
where: 'id = ?',
whereArgs: [id],
);
}
/// Delete all chapters for a series
Future<void> deleteForSeries(String seriesId) async {
final db = await _dbHelper.database;
await db.delete(
DownloadedChaptersTable.tableName,
where: 'series_id = ?',
whereArgs: [seriesId],
);
}
/// Get pending chapters for a series (for resume)
Future<List<DownloadedChapter>> getPendingChapters(String seriesId) async {
final db = await _dbHelper.database;
final results = await db.query(
DownloadedChaptersTable.tableName,
where: 'series_id = ? AND status IN (?, ?)',
whereArgs: [seriesId, 'pending', 'failed'],
orderBy: 'sort_order ASC',
);
return results.map((row) => DownloadedChapter.fromDatabase(row)).toList();
}
}