From 33db0cf297ea588395ff490194b7fb2b6c1d14d9 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 11 Aug 2026 15:23:21 +0700 Subject: [PATCH] fix(queue): restore user-paused downloads #511 --- lib/providers/download_queue_provider.dart | 44 +++++++++++++++++-- .../download_queue_provider_connectivity.dart | 2 +- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index a072a46e..b073d960 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -147,6 +147,7 @@ final _explicitQualityFilenameTokenPattern = RegExp( class DownloadQueueNotifier extends Notifier { Timer? _queuePersistDebounce; Future _queuePersistenceWrite = Future.value(); + Future _queuePausePersistenceWrite = Future.value(); final Map _persistedQueueJsonById = {}; StreamSubscription>? _connectivitySub; int _downloadCount = 0; @@ -158,6 +159,7 @@ class DownloadQueueNotifier extends Notifier { static const _queuePersistDebounceDuration = Duration(milliseconds: 350); static const _nativeWorkerRunIdPrefsKey = 'download_queue_native_worker_run_id'; + static const _userPausedQueuePrefsKey = 'download_queue_user_paused_v1'; static const _bytesUiStep = 104857; // ~0.1 MiB, matches one-decimal MB UI. static const _serviceProgressStepPercent = 2; static const _decryptStageSafAccess = 'safAccess'; @@ -291,6 +293,26 @@ class DownloadQueueNotifier extends Notifier { await _flushQueueToStorage(); } await _queuePersistenceWrite; + await _queuePausePersistenceWrite; + } + + void _persistUserPausedQueue(bool paused) { + final operation = _queuePausePersistenceWrite.then((_) async { + final prefs = await SharedPreferences.getInstance(); + if (paused) { + await prefs.setBool(_userPausedQueuePrefsKey, true); + } else { + await prefs.remove(_userPausedQueuePrefsKey); + } + }); + _queuePausePersistenceWrite = operation.catchError((Object error) { + _log.w('Failed to persist queue pause state: $error'); + }); + } + + Future _loadUserPausedQueue() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getBool(_userPausedQueuePrefsKey) == true; } /// Restarts a queue that was deliberately left pending because Android did @@ -319,6 +341,7 @@ class DownloadQueueNotifier extends Notifier { try { await _appStateDb.migrateQueueFromSharedPreferences(); + final restorePaused = await _loadUserPausedQueue(); final rows = await _appStateDb.getPendingDownloadQueueRows(); _persistedQueueJsonById ..clear() @@ -333,6 +356,7 @@ class DownloadQueueNotifier extends Notifier { .where((entry) => entry.key.isNotEmpty), ); if (rows.isEmpty) { + if (restorePaused) _persistUserPausedQueue(false); _log.d('No queue found in storage'); return; } @@ -364,6 +388,7 @@ class DownloadQueueNotifier extends Notifier { } if (pendingItems.isEmpty) { + if (restorePaused) _persistUserPausedQueue(false); _log.d('No pending items to restore'); await _appStateDb.replacePendingDownloadQueueRows(const []); _persistedQueueJsonById.clear(); @@ -371,7 +396,10 @@ class DownloadQueueNotifier extends Notifier { } final normalizedPendingItems = _normalizeRestoredQueueIds(pendingItems); - state = state.copyWith(items: normalizedPendingItems); + state = state.copyWith( + items: normalizedPendingItems, + isPaused: restorePaused, + ); _saveQueueToStorage(); _log.i( 'Restored ${normalizedPendingItems.length} pending items from storage', @@ -379,7 +407,11 @@ class DownloadQueueNotifier extends Notifier { if (await _tryAdoptAndroidNativeWorkerSnapshot(normalizedPendingItems)) { return; } - Future.microtask(() => _processQueue()); + if (!restorePaused) { + Future.microtask(() => _processQueue()); + } else { + _log.i('Restored queue in user-paused state'); + } } catch (e) { _log.e('Failed to load queue from storage: $e'); } @@ -929,6 +961,7 @@ class DownloadQueueNotifier extends Notifier { } state = state.copyWith(items: [], isPaused: false, currentDownload: null); + _persistUserPausedQueue(false); if (_hasActiveAndroidNativeWorker) { PlatformBridge.cancelNativeDownloadWorker().catchError((_) {}); } @@ -941,7 +974,7 @@ class DownloadQueueNotifier extends Notifier { _pausePendingItemIds.clear(); } - void pauseQueue() { + void pauseQueue({bool persistAcrossRestarts = true}) { if (state.isProcessing && !state.isPaused) { if (_hasActiveAndroidNativeWorker) { PlatformBridge.pauseNativeDownloadWorker().catchError((_) {}); @@ -964,6 +997,9 @@ class DownloadQueueNotifier extends Notifier { } state = state.copyWith(isPaused: true, currentDownload: null); + if (persistAcrossRestarts) { + _persistUserPausedQueue(true); + } _notificationService.cancelDownloadNotification(); _log.i('Queue paused'); } @@ -975,6 +1011,7 @@ class DownloadQueueNotifier extends Notifier { PlatformBridge.resumeNativeDownloadWorker().catchError((_) {}); } state = state.copyWith(isPaused: false); + _persistUserPausedQueue(false); _log.i('Queue resumed'); if (state.queuedCount > 0 && !state.isProcessing) { Future.microtask(() => _processQueue()); @@ -1087,6 +1124,7 @@ class DownloadQueueNotifier extends Notifier { .toList(growable: false); state = state.copyWith(items: items, isPaused: false); + _persistUserPausedQueue(false); _saveQueueToStorage(); if (!state.isProcessing) { diff --git a/lib/providers/download_queue_provider_connectivity.dart b/lib/providers/download_queue_provider_connectivity.dart index 7c71ef6f..aa9fa51d 100644 --- a/lib/providers/download_queue_provider_connectivity.dart +++ b/lib/providers/download_queue_provider_connectivity.dart @@ -144,7 +144,7 @@ extension _DownloadQueueConnectivity on DownloadQueueNotifier { if (state.isProcessing && !state.isPaused) { _networkPausedByWifiOnly = true; _log.w('WiFi connection lost, pausing active queue'); - pauseQueue(); + pauseQueue(persistAcrossRestarts: false); } } }