diff --git a/example/lib/src/shared/state/download_provider.dart b/example/lib/src/shared/state/download_provider.dart index 281a8516..e19c2307 100644 --- a/example/lib/src/shared/state/download_provider.dart +++ b/example/lib/src/shared/state/download_provider.dart @@ -7,8 +7,7 @@ class DownloadingProvider extends ChangeNotifier { bool _isFocused = false; bool get isFocused => _isFocused; - bool _isPaused = false; - bool get isPaused => _isPaused; + bool get isPaused => FMTCStore(storeName!).download.isPaused(); bool _isComplete = false; bool get isComplete => _isComplete; @@ -78,14 +77,12 @@ class DownloadingProvider extends ChangeNotifier { Future pause() async { assert(_storeName != null, 'Download not in progress'); await FMTCStore(_storeName!).download.pause(); - _isPaused = true; notifyListeners(); } void resume() { assert(_storeName != null, 'Download not in progress'); FMTCStore(_storeName!).download.resume(); - _isPaused = false; notifyListeners(); } diff --git a/lib/src/bulk_download/internal/instance.dart b/lib/src/bulk_download/internal/instance.dart index e6bcef87..6a0bc21f 100644 --- a/lib/src/bulk_download/internal/instance.dart +++ b/lib/src/bulk_download/internal/instance.dart @@ -1,6 +1,8 @@ // Copyright © Luka S (JaffaKetchup) under GPL-v3 // A full license can be found at .\LICENSE +import 'dart:async'; + import 'package:meta/meta.dart'; @internal @@ -16,6 +18,8 @@ class DownloadInstance { final Object id; bool isPaused = false; + Completer? resumingAfterPause; + Completer pausingCompleter = Completer()..complete(true); // The following callbacks are defined by the `StoreDownload.startForeground` // method, when a download is started, and are tied to that download operation diff --git a/lib/src/bulk_download/internal/manager.dart b/lib/src/bulk_download/internal/manager.dart index 6687c95d..62dd3e69 100644 --- a/lib/src/bulk_download/internal/manager.dart +++ b/lib/src/bulk_download/internal/manager.dart @@ -216,15 +216,12 @@ Future _downloadManager( switch (cmd) { case _DownloadManagerControlCmd.cancel: + // We might recieve it more than once if the root requests + // cancellation whilst we already are cancelling it if (!cancelSignal.isCompleted) cancelSignal.complete(); - // We might recieve it more than once if the root requests cancellation - // whilst we already are cancelling it case _DownloadManagerControlCmd.pause: - if (!pauseResumeSignal.isCompleted) { - // We might recieve it more than once if the root requests pausing - // whilst we already are pausing it - break; - } + // We are already pausing or paused + if (!pauseResumeSignal.isCompleted) return; pauseResumeSignal = Completer(); threadPausedStates.setAll(0, generateThreadPausedStates()); diff --git a/lib/src/store/download.dart b/lib/src/store/download.dart index 3486ad43..376f7e7c 100644 --- a/lib/src/store/download.dart +++ b/lib/src/store/download.dart @@ -284,9 +284,8 @@ class StoreDownload { } ..requestPause = () { sp.send(_DownloadManagerControlCmd.pause); - // Completed by handler above - return (pauseCompleter = Completer()).future - ..then((_) => instance.isPaused = true); + // Completed by handler below + return (pauseCompleter = Completer()).future; } ..requestResume = () { sp.send(_DownloadManagerControlCmd.resume); @@ -417,38 +416,82 @@ class StoreDownload { /// Use [resume] to resume the download. It is also safe to use [cancel] /// without resuming first. /// - /// Will return once the pause operation is complete. Note that all running - /// parallel download threads will be allowed to finish their *current* tile - /// download. Any buffered tiles are not written. + /// Note that all running parallel download threads will be allowed to finish + /// their *current* tile download before pausing. /// - /// {@macro fmtc.bulkDownload.numInstances} + /// It is not usually necessary to use the result. Returns `null` if there is + /// no ongoing download or the download is already paused or pausing. + /// Otherwise returns whether the download was paused (`false` if [resume] is + /// called whilst the download is being paused). /// - /// Does nothing (returns immediately) if there is no ongoing download or the - /// download is already paused. - Future pause({Object instanceId = 0}) async { + /// Any buffered tiles are not flushed. + /// + /// --- + /// + /// {@macro fmtc.bulkDownload.numInstances} + Future pause({Object instanceId = 0}) { final instance = DownloadInstance.get(instanceId); - if (instance == null || instance.isPaused) return; - await instance.requestPause!.call(); + if (instance == null || + instance.isPaused || + !instance.pausingCompleter.isCompleted || + instance.requestPause == null) { + return SynchronousFuture(null); + } + + instance + ..pausingCompleter = Completer() + ..resumingAfterPause = Completer(); + + instance.requestPause!().then((_) { + instance.pausingCompleter.complete(true); + if (!instance.resumingAfterPause!.isCompleted) instance.isPaused = true; + instance.resumingAfterPause = null; + }); + + return Future.any( + [instance.resumingAfterPause!.future, instance.pausingCompleter.future], + ); } /// Resume (after a [pause]) the ongoing foreground download /// - /// {@macro fmtc.bulkDownload.numInstances} + /// It is not usually necessary to use the result. Returns `null` if there is + /// no ongoing download or the download is already running. Returns `true` if + /// the download was paused. Returns `false` if the download was paus*ing* ( + /// in which case the download will not be paused). + /// + /// --- /// - /// Does nothing if there is no ongoing download or the download is already - /// running. - void resume({Object instanceId = 0}) { + /// {@macro fmtc.bulkDownload.numInstances} + bool? resume({Object instanceId = 0}) { final instance = DownloadInstance.get(instanceId); - if (instance == null || !instance.isPaused) return; - instance.requestResume!.call(); + if (instance == null || + (!instance.isPaused && instance.resumingAfterPause == null) || + instance.requestResume == null) { + return null; + } + + if (instance.pausingCompleter.isCompleted) { + instance.requestResume!(); + return true; + } + + if (!instance.resumingAfterPause!.isCompleted) { + instance + ..resumingAfterPause!.complete(false) + ..pausingCompleter.future.then((_) => instance.requestResume!()); + } + return false; } /// Whether the ongoing foreground download is currently paused after a call /// to [pause] (and prior to [resume]) /// - /// {@macro fmtc.bulkDownload.numInstances} - /// /// Also returns `false` if there is no ongoing download. + /// + /// --- + /// + /// {@macro fmtc.bulkDownload.numInstances} bool isPaused({Object instanceId = 0}) => DownloadInstance.get(instanceId)?.isPaused ?? false; }