fix(queue): keep cancelled downloads retryable across every cancel path and app restarts

This commit is contained in:
zarzet
2026-07-27 01:13:23 +07:00
parent 23f18ab276
commit 8fe2662c0a
16 changed files with 114 additions and 8 deletions
+12
View File
@@ -5948,6 +5948,18 @@ abstract class AppLocalizations {
/// **'Keep'**
String get cancelDownloadKeep;
/// Dialog title when opening a cancelled queue item
///
/// In en, this message translates to:
/// **'Download cancelled'**
String get queueCancelledTitle;
/// Dialog body when opening a cancelled queue item
///
/// In en, this message translates to:
/// **'This download was cancelled. Retry it or remove it from the queue.'**
String get queueCancelledMessage;
/// Snackbar error when FFmpeg fails to write metadata
///
/// In en, this message translates to:
+7
View File
@@ -3590,6 +3590,13 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Behalten';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg =>
'Fehler beim Speichern der Metadaten über FFmpeg';
+7
View File
@@ -3547,6 +3547,13 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Keep';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg';
+7
View File
@@ -3547,6 +3547,13 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Keep';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg';
+7
View File
@@ -3641,6 +3641,13 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Conserver';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg =>
'Échec de l\'enregistrement des métadonnées via FFmpeg';
+7
View File
@@ -3550,6 +3550,13 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Keep';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg';
+7
View File
@@ -3536,6 +3536,13 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Keep';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg';
+7
View File
@@ -3443,6 +3443,13 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get cancelDownloadKeep => '유지';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg => 'FFmpeg를 통해 메타데이터 저장 실패';
+7
View File
@@ -3547,6 +3547,13 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Keep';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg';
+7
View File
@@ -3576,6 +3576,13 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Удерживать';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg =>
'Не удалось сохранить метаданные через FFmpeg';
+7
View File
@@ -3577,6 +3577,13 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Keep';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg';
+7
View File
@@ -3594,6 +3594,13 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get cancelDownloadKeep => 'Зберегти';
@override
String get queueCancelledTitle => 'Download cancelled';
@override
String get queueCancelledMessage =>
'This download was cancelled. Retry it or remove it from the queue.';
@override
String get metadataSaveFailedFfmpeg =>
'Не вдалося зберегти метадані через FFmpeg';
+8
View File
@@ -4577,6 +4577,14 @@
"@cancelDownloadKeep": {
"description": "Dialog button - keep the active download (do not cancel)"
},
"queueCancelledTitle": "Download cancelled",
"@queueCancelledTitle": {
"description": "Dialog title when opening a cancelled queue item"
},
"queueCancelledMessage": "This download was cancelled. Retry it or remove it from the queue.",
"@queueCancelledMessage": {
"description": "Dialog body when opening a cancelled queue item"
},
"metadataSaveFailedFfmpeg": "Failed to save metadata via FFmpeg",
"@metadataSaveFailedFfmpeg": {
"description": "Snackbar error when FFmpeg fails to write metadata"
+6 -2
View File
@@ -261,7 +261,8 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
item.status == DownloadStatus.finalizing) {
item = item.copyWith(status: DownloadStatus.queued, progress: 0);
}
if (item.status == DownloadStatus.queued) {
if (item.status == DownloadStatus.queued ||
item.status == DownloadStatus.skipped) {
pendingItems.add(item);
}
} catch (_) {
@@ -306,12 +307,15 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
Future<void> _writeQueueChanges() async {
try {
// skipped (user-cancelled) rows persist too, so a cancelled download can
// still be retried after an app restart instead of being re-searched.
final pendingItems = state.items
.where(
(item) =>
item.status == DownloadStatus.queued ||
item.status == DownloadStatus.downloading ||
item.status == DownloadStatus.finalizing,
item.status == DownloadStatus.finalizing ||
item.status == DownloadStatus.skipped,
)
.toList(growable: false);
final nowIso = DateTime.now().toIso8601String();
+10 -3
View File
@@ -1624,7 +1624,9 @@ class _QueueTabState extends ConsumerState<QueueTab> {
),
);
if (confirmed == true) {
ref.read(downloadQueueProvider.notifier).dismissItem(item.id);
// cancelItem (not dismissItem): the item stays in the queue as
// cancelled so it can be retried without re-searching the track.
ref.read(downloadQueueProvider.notifier).cancelItem(item.id);
}
}
@@ -1634,13 +1636,18 @@ class _QueueTabState extends ConsumerState<QueueTab> {
) async {
final colorScheme = Theme.of(context).colorScheme;
final isRateLimit = item.errorType == DownloadErrorType.rateLimit;
final isCancelled = item.status == DownloadStatus.skipped;
final isFolderAccessLost =
item.errorMessage == safPermissionLostErrorMessage ||
item.errorMessage == downloadFolderAccessLostErrorMessage;
final title = isRateLimit
final title = isCancelled
? context.l10n.queueCancelledTitle
: isRateLimit
? context.l10n.queueRateLimitTitle
: context.l10n.updateDownloadFailed;
final message = isRateLimit
final message = isCancelled
? context.l10n.queueCancelledMessage
: isRateLimit
? context.l10n.queueRateLimitMessage
: (item.errorMessage.trim().isNotEmpty
? _localizedDownloadError(context, item.errorMessage)
+1 -3
View File
@@ -27,10 +27,8 @@ extension _QueueTabCollectionItemWidgets on _QueueTabState {
child: Icon(Icons.music_note, color: colorScheme.onSurfaceVariant),
);
final onTap = isFailed
final onTap = isFailed || item.status == DownloadStatus.skipped
? () => _showDownloadErrorDialog(context, item)
: item.status == DownloadStatus.skipped
? () => ref.read(downloadQueueProvider.notifier).removeItem(item.id)
: () => _confirmCancelDownload(context, item);
return SmoothedProgressScope(