Skip to content

Commit

Permalink
Added support for resuming a download whilst pausing
Browse files Browse the repository at this point in the history
  • Loading branch information
JaffaKetchup committed Jan 1, 2025
1 parent 012db03 commit 04423de
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 31 deletions.
5 changes: 1 addition & 4 deletions example/lib/src/shared/state/download_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -78,14 +77,12 @@ class DownloadingProvider extends ChangeNotifier {
Future<void> 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();
}

Expand Down
4 changes: 4 additions & 0 deletions lib/src/bulk_download/internal/instance.dart
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,6 +18,8 @@ class DownloadInstance {
final Object id;

bool isPaused = false;
Completer<bool>? resumingAfterPause;
Completer<bool> 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
Expand Down
11 changes: 4 additions & 7 deletions lib/src/bulk_download/internal/manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,12 @@ Future<void> _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<void>();
threadPausedStates.setAll(0, generateThreadPausedStates());
Expand Down
83 changes: 63 additions & 20 deletions lib/src/store/download.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<void> pause({Object instanceId = 0}) async {
/// Any buffered tiles are not flushed.
///
/// ---
///
/// {@macro fmtc.bulkDownload.numInstances}
Future<bool?> 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;
}

0 comments on commit 04423de

Please sign in to comment.