All checks were successful
ci/woodpecker/tag/release Pipeline was successful
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>
131 lines
4.1 KiB
Dart
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();
|
|
}
|
|
}
|