diff --git a/lib/main.dart b/lib/main.dart index 154a8a39..a21a439a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -214,7 +214,7 @@ class _MainAppState extends State with WidgetsBindingObserver { PlexApiCache.initialize(_appDatabase); _downloadManager = DownloadManagerService(database: _appDatabase, storageService: DownloadStorageService.instance); - _downloadManager.recoverInterruptedDownloads(); + _downloadManager.recoveryFuture = _downloadManager.recoverInterruptedDownloads(); _offlineWatchSyncService = OfflineWatchSyncService(database: _appDatabase, serverManager: _serverManager); @@ -398,6 +398,12 @@ class _SetupScreenState extends State { if (!mounted) return; if (result.hasConnections) { + // Resume any downloads that were interrupted by app kill + final downloadProvider = context.read(); + downloadProvider.ensureInitialized().then((_) { + downloadProvider.resumeQueuedDownloads(result.firstClient!); + }); + Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => MainScreen(client: result.firstClient!)), diff --git a/lib/providers/download_provider.dart b/lib/providers/download_provider.dart index 059eb2ba..11139d4f 100644 --- a/lib/providers/download_provider.dart +++ b/lib/providers/download_provider.dart @@ -71,6 +71,10 @@ class DownloadProvider extends ChangeNotifier { /// Load all persisted downloads and metadata from the database/cache Future _loadPersistedDownloads() async { try { + // Wait for recovery to finish before loading state so that + // interrupted "downloading" rows have been transitioned to "queued" + await _downloadManager.recoveryFuture; + // Clear existing data to prevent stale entries after deletions _downloads.clear(); _artworkPaths.clear(); @@ -987,6 +991,12 @@ class DownloadProvider extends ChangeNotifier { await _loadPersistedDownloads(); } + /// Resume queued downloads that were interrupted by app kill. + /// Call after a PlexClient becomes available (e.g. after server connect on launch). + void resumeQueuedDownloads(PlexClient client) { + _downloadManager.resumeQueuedDownloads(client); + } + /// Refresh only metadata from API cache (after watch state sync). /// /// This is more lightweight than full refresh() - only updates metadata diff --git a/lib/services/download_manager_service.dart b/lib/services/download_manager_service.dart index 45b49b85..3934a6fa 100644 --- a/lib/services/download_manager_service.dart +++ b/lib/services/download_manager_service.dart @@ -244,6 +244,10 @@ class DownloadManagerService { !connectivity.contains(ConnectivityResult.ethernet); } + /// Future that completes when interrupted download recovery finishes. + /// Await this before reading download state from the DB to avoid races. + late final Future recoveryFuture; + DownloadManagerService({required AppDatabase database, required DownloadStorageService storageService, Dio? dio}) : _database = database, _storageService = storageService, @@ -271,6 +275,17 @@ class DownloadManagerService { } } + /// Resume queued downloads that have no active processing. + /// Call after a PlexClient becomes available (e.g. after server connect on launch). + void resumeQueuedDownloads(PlexClient client) { + _database.getNextQueueItem().then((item) { + if (item != null && !_isProcessingQueue) { + appLogger.i('Resuming queued downloads after app restart'); + _processQueue(client); + } + }); + } + /// Delete a file if it exists and log the deletion /// Returns true if file was deleted, false otherwise Future _deleteFileIfExists(File file, String description) async {