mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 06:46:22 +01:00
Handle download in dart to support customized request
This commit is contained in:
parent
15523c8bda
commit
50a80fe2a3
9 changed files with 141 additions and 353 deletions
|
@ -1,109 +0,0 @@
|
|||
package com.nkming.nc_photos
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
/**
|
||||
* Download a file
|
||||
*
|
||||
* Methods:
|
||||
* Download a file at @a url. If @a shouldNotify is false, no progress
|
||||
* notifications would be shown
|
||||
* fun downloadUrl(url: String, headers: Map<String, String>?,
|
||||
* mimeType: String?, filename: String, shouldNotify: Boolean?): String
|
||||
*/
|
||||
class DownloadChannelHandler(activity: Activity) :
|
||||
MethodChannel.MethodCallHandler {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val CHANNEL = "com.nkming.nc_photos/download"
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"downloadUrl" -> {
|
||||
try {
|
||||
downloadUrl(
|
||||
call.argument("url")!!,
|
||||
call.argument("headers"),
|
||||
call.argument("mimeType"),
|
||||
call.argument("filename")!!,
|
||||
call.argument("shouldNotify"),
|
||||
result
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.toString(), null)
|
||||
}
|
||||
}
|
||||
"cancel" -> {
|
||||
try {
|
||||
cancel(
|
||||
call.argument("id")!!, result
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.toString(), null)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadUrl(
|
||||
url: String,
|
||||
headers: Map<String, String>?,
|
||||
mimeType: String?,
|
||||
filename: String,
|
||||
shouldNotify: Boolean?,
|
||||
result: MethodChannel.Result
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
if (!PermissionHandler.ensureWriteExternalStorage(_activity)) {
|
||||
result.error("permissionError", "Permission not granted", null)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val uri = Uri.parse(url)
|
||||
val req = DownloadManager.Request(uri).apply {
|
||||
setDestinationInExternalPublicDir(
|
||||
Environment.DIRECTORY_DOWNLOADS, filename
|
||||
)
|
||||
for (h in headers ?: mapOf()) {
|
||||
addRequestHeader(h.key, h.value)
|
||||
}
|
||||
if (mimeType != null) {
|
||||
setMimeType(mimeType)
|
||||
}
|
||||
setVisibleInDownloadsUi(true)
|
||||
setNotificationVisibility(
|
||||
if (shouldNotify == false) DownloadManager.Request.VISIBILITY_HIDDEN
|
||||
else DownloadManager.Request.VISIBILITY_VISIBLE
|
||||
)
|
||||
allowScanningByMediaScanner()
|
||||
}
|
||||
|
||||
val id = _downloadManager.enqueue(req)
|
||||
result.success(id)
|
||||
}
|
||||
|
||||
private fun cancel(
|
||||
id: Long, result: MethodChannel.Result
|
||||
) {
|
||||
val count = _downloadManager.remove(id)
|
||||
result.success(count > 0)
|
||||
}
|
||||
|
||||
private val _activity = activity
|
||||
private val _context get() = _activity
|
||||
private val _downloadManager by lazy {
|
||||
_context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
}
|
||||
}
|
|
@ -1,99 +1,10 @@
|
|||
package com.nkming.nc_photos
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import io.flutter.Log
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Send DownloadManager ACTION_DOWNLOAD_COMPLETE events to flutter
|
||||
*/
|
||||
class DownloadEventCompleteChannelHandler(context: Context) :
|
||||
BroadcastReceiver(), EventChannel.StreamHandler {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val CHANNEL =
|
||||
"com.nkming.nc_photos/download_event/action_download_complete"
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action != DownloadManager.ACTION_DOWNLOAD_COMPLETE || !intent.hasExtra(
|
||||
DownloadManager.EXTRA_DOWNLOAD_ID
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
val downloadId =
|
||||
intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)
|
||||
// check the status of the job and retrieve the local URI
|
||||
val c = _downloadManager.query(
|
||||
DownloadManager.Query().setFilterById(downloadId)
|
||||
)
|
||||
if (c.moveToFirst()) {
|
||||
val status =
|
||||
c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
||||
if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
val uri =
|
||||
c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
|
||||
val contentUri = FileProvider.getUriForFile(
|
||||
_context,
|
||||
"${BuildConfig.APPLICATION_ID}.fileprovider",
|
||||
File(Uri.parse(uri).path!!)
|
||||
)
|
||||
_eventSink?.success(
|
||||
mapOf(
|
||||
"downloadId" to downloadId,
|
||||
"uri" to contentUri.toString()
|
||||
)
|
||||
)
|
||||
} else if (status == DownloadManager.STATUS_FAILED) {
|
||||
val reason =
|
||||
c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON))
|
||||
_eventSink?.error(
|
||||
"downloadError",
|
||||
"Download #$downloadId was not successful, status: $status, reason: $reason",
|
||||
mapOf(
|
||||
"downloadId" to downloadId
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Log.i(
|
||||
"DownloadEventCompleteChannelHandler.onReceive",
|
||||
"ID #$downloadId not found, user canceled the job?"
|
||||
)
|
||||
_eventSink?.error(
|
||||
"userCanceled", "Download #$downloadId was canceled", mapOf(
|
||||
"downloadId" to downloadId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||
_context.registerReceiver(
|
||||
this, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||
)
|
||||
_eventSink = events
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
_context.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
private val _context = context
|
||||
private var _eventSink: EventChannel.EventSink? = null
|
||||
private val _downloadManager by lazy {
|
||||
_context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadEventCancelChannelHandler(context: Context) : BroadcastReceiver(),
|
||||
EventChannel.StreamHandler {
|
||||
|
|
|
@ -9,9 +9,6 @@ import io.flutter.plugin.common.MethodChannel
|
|||
class MainActivity : FlutterActivity() {
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
|
||||
DownloadChannelHandler.CHANNEL).setMethodCallHandler(
|
||||
DownloadChannelHandler(this))
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
|
||||
MediaStoreChannelHandler.CHANNEL).setMethodCallHandler(
|
||||
MediaStoreChannelHandler(this))
|
||||
|
@ -25,9 +22,6 @@ class MainActivity : FlutterActivity() {
|
|||
ShareChannelHandler.CHANNEL).setMethodCallHandler(
|
||||
ShareChannelHandler(this))
|
||||
|
||||
EventChannel(flutterEngine.dartExecutor.binaryMessenger,
|
||||
DownloadEventCompleteChannelHandler.CHANNEL).setStreamHandler(
|
||||
DownloadEventCompleteChannelHandler(this))
|
||||
EventChannel(flutterEngine.dartExecutor.binaryMessenger,
|
||||
DownloadEventCancelChannelHandler.CHANNEL).setStreamHandler(
|
||||
DownloadEventCancelChannelHandler(this))
|
||||
|
|
|
@ -11,9 +11,7 @@ import androidx.annotation.RequiresApi
|
|||
import androidx.core.content.FileProvider
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.*
|
||||
|
||||
/*
|
||||
* Save downloaded item on device
|
||||
|
@ -31,8 +29,8 @@ class MediaStoreChannelHandler(activity: Activity) :
|
|||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
if (call.method == "saveFileToDownload") {
|
||||
try {
|
||||
when (call.method) {
|
||||
"saveFileToDownload" -> try {
|
||||
saveFileToDownload(
|
||||
call.argument("fileName")!!,
|
||||
call.argument("content")!!,
|
||||
|
@ -41,24 +39,47 @@ class MediaStoreChannelHandler(activity: Activity) :
|
|||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.message, null)
|
||||
}
|
||||
} else {
|
||||
result.notImplemented()
|
||||
"copyFileToDownload" -> try {
|
||||
copyFileToDownload(
|
||||
call.argument("toFileName")!!,
|
||||
call.argument("fromFilePath")!!,
|
||||
result
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
result.error("systemException", e.message, null)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveFileToDownload(
|
||||
fileName: String, content: ByteArray, result: MethodChannel.Result
|
||||
) {
|
||||
val stream = ByteArrayInputStream(content)
|
||||
writeFileToDownload(fileName, stream, result)
|
||||
}
|
||||
|
||||
private fun copyFileToDownload(
|
||||
toFileName: String, fromFilePath: String, result: MethodChannel.Result
|
||||
) {
|
||||
val file = File(fromFilePath)
|
||||
val stream = file.inputStream()
|
||||
writeFileToDownload(toFileName, stream, result)
|
||||
}
|
||||
|
||||
private fun writeFileToDownload(
|
||||
fileName: String, data: InputStream, result: MethodChannel.Result
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
saveFileToDownload29(fileName, content, result)
|
||||
writeFileToDownload29(fileName, data, result)
|
||||
} else {
|
||||
saveFileToDownload0(fileName, content, result)
|
||||
writeFileToDownload0(fileName, data, result)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun saveFileToDownload29(
|
||||
fileName: String, content: ByteArray, result: MethodChannel.Result
|
||||
private fun writeFileToDownload29(
|
||||
fileName: String, data: InputStream, result: MethodChannel.Result
|
||||
) {
|
||||
// Add a media item that other apps shouldn't see until the item is
|
||||
// fully written to the media store.
|
||||
|
@ -77,14 +98,14 @@ class MediaStoreChannelHandler(activity: Activity) :
|
|||
resolver.openFileDescriptor(contentUri!!, "w", null).use { pfd ->
|
||||
// Write data into the pending audio file.
|
||||
BufferedOutputStream(FileOutputStream(pfd!!.fileDescriptor)).use { stream ->
|
||||
stream.write(content)
|
||||
data.copyTo(stream)
|
||||
}
|
||||
}
|
||||
result.success(contentUri.toString())
|
||||
}
|
||||
|
||||
private fun saveFileToDownload0(
|
||||
fileName: String, content: ByteArray, result: MethodChannel.Result
|
||||
private fun writeFileToDownload0(
|
||||
fileName: String, data: InputStream, result: MethodChannel.Result
|
||||
) {
|
||||
if (!PermissionHandler.ensureWriteExternalStorage(_activity)) {
|
||||
result.error("permissionError", "Permission not granted", null)
|
||||
|
@ -103,7 +124,7 @@ class MediaStoreChannelHandler(activity: Activity) :
|
|||
++count
|
||||
}
|
||||
BufferedOutputStream(FileOutputStream(file)).use { stream ->
|
||||
stream.write(content)
|
||||
data.copyTo(stream)
|
||||
}
|
||||
|
||||
val fileUri = Uri.fromFile(file)
|
||||
|
|
|
@ -2,66 +2,13 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class Download {
|
||||
static Future<int> downloadUrl({
|
||||
required String url,
|
||||
Map<String, String>? headers,
|
||||
String? mimeType,
|
||||
required String filename,
|
||||
bool? shouldNotify,
|
||||
}) async {
|
||||
return (await _channel.invokeMethod<int>("downloadUrl", <String, dynamic>{
|
||||
"url": url,
|
||||
"headers": headers,
|
||||
"mimeType": mimeType,
|
||||
"filename": filename,
|
||||
"shouldNotify": shouldNotify,
|
||||
}))!;
|
||||
}
|
||||
|
||||
static Future<bool> cancel({
|
||||
required int id,
|
||||
}) async {
|
||||
return (await _channel.invokeMethod<bool>("cancel", <String, dynamic>{
|
||||
"id": id,
|
||||
}))!;
|
||||
}
|
||||
|
||||
/// The download job has failed
|
||||
static const exceptionCodeDownloadError = "downloadError";
|
||||
|
||||
static const _channel = MethodChannel("com.nkming.nc_photos/download");
|
||||
}
|
||||
|
||||
class DownloadEvent {
|
||||
static StreamSubscription<DownloadCompleteEvent> listenDownloadComplete() =>
|
||||
_completeStream.listen(null);
|
||||
|
||||
static StreamSubscription<DownloadCancelEvent> listenDownloadCancel() =>
|
||||
_cancelStream.listen(null);
|
||||
|
||||
/// User canceled the download job
|
||||
static const exceptionCodeUserCanceled = "userCanceled";
|
||||
|
||||
static const _downloadCompleteChannel = EventChannel(
|
||||
"com.nkming.nc_photos/download_event/action_download_complete");
|
||||
|
||||
static late final _completeStream = _downloadCompleteChannel
|
||||
.receiveBroadcastStream()
|
||||
.map((data) => DownloadCompleteEvent(
|
||||
data["downloadId"],
|
||||
data["uri"],
|
||||
))
|
||||
.handleError(
|
||||
(e, stackTrace) {
|
||||
throw AndroidDownloadError(e.details["downloadId"], e, stackTrace);
|
||||
},
|
||||
test: (e) =>
|
||||
e is PlatformException &&
|
||||
e.details is Map &&
|
||||
e.details["downloadId"] is int,
|
||||
);
|
||||
|
||||
static const _downloadCancelChannel = EventChannel(
|
||||
"com.nkming.nc_photos/download_event/action_download_cancel");
|
||||
|
||||
|
@ -72,21 +19,6 @@ class DownloadEvent {
|
|||
));
|
||||
}
|
||||
|
||||
class DownloadCompleteEvent {
|
||||
const DownloadCompleteEvent(this.downloadId, this.uri);
|
||||
|
||||
final int downloadId;
|
||||
final String uri;
|
||||
}
|
||||
|
||||
class AndroidDownloadError implements Exception {
|
||||
const AndroidDownloadError(this.downloadId, this.error, this.stackTrace);
|
||||
|
||||
final int downloadId;
|
||||
final dynamic error;
|
||||
final StackTrace stackTrace;
|
||||
}
|
||||
|
||||
class DownloadCancelEvent {
|
||||
const DownloadCancelEvent(this.notificationId);
|
||||
|
||||
|
|
|
@ -14,5 +14,14 @@ class MediaStore {
|
|||
}))!;
|
||||
}
|
||||
|
||||
static Future<String> copyFileToDownload(
|
||||
String toFileName, String fromFilePath) async {
|
||||
return (await _channel
|
||||
.invokeMethod<String>("copyFileToDownload", <String, dynamic>{
|
||||
"toFileName": toFileName,
|
||||
"fromFilePath": fromFilePath,
|
||||
}))!;
|
||||
}
|
||||
|
||||
static const _channel = MethodChannel("com.nkming.nc_photos/media_store");
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/exception.dart';
|
||||
import 'package:nc_photos/mobile/android/download.dart' as android;
|
||||
import 'package:nc_photos/mobile/android/media_store.dart';
|
||||
import 'package:nc_photos/platform/download.dart' as itf;
|
||||
import 'package:nc_photos/platform/k.dart' as platform_k;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class DownloadBuilder extends itf.DownloadBuilder {
|
||||
@override
|
||||
|
@ -45,77 +47,99 @@ class _AndroidDownload extends itf.Download {
|
|||
|
||||
@override
|
||||
call() async {
|
||||
final String path;
|
||||
if (parentDir?.isNotEmpty == true) {
|
||||
path = "$parentDir/$filename";
|
||||
} else {
|
||||
path = filename;
|
||||
if (_isInitialDownload) {
|
||||
await _cleanUp();
|
||||
_isInitialDownload = false;
|
||||
}
|
||||
|
||||
final file = await _createTempFile();
|
||||
try {
|
||||
_log.info("[call] Start downloading '$url'");
|
||||
_downloadId = await android.Download.downloadUrl(
|
||||
url: url,
|
||||
headers: headers,
|
||||
mimeType: mimeType,
|
||||
filename: path,
|
||||
shouldNotify: shouldNotify,
|
||||
);
|
||||
_log.info("[call] #$_downloadId -> '$url'");
|
||||
late final String uri;
|
||||
final completer = Completer();
|
||||
onDownloadComplete(android.DownloadCompleteEvent ev) {
|
||||
if (ev.downloadId == _downloadId) {
|
||||
_log.info("[call] Finished downloading '$url' to '${ev.uri}'");
|
||||
uri = ev.uri;
|
||||
completer.complete();
|
||||
}
|
||||
}
|
||||
|
||||
StreamSubscription<android.DownloadCompleteEvent>? subscription;
|
||||
// download file to a temp dir
|
||||
final fileWrite = file.openWrite();
|
||||
try {
|
||||
subscription = android.DownloadEvent.listenDownloadComplete()
|
||||
..onData(onDownloadComplete)
|
||||
..onError((e, stackTrace) {
|
||||
if (e is android.AndroidDownloadError) {
|
||||
if (e.downloadId != _downloadId) {
|
||||
// not us, ignore
|
||||
return;
|
||||
}
|
||||
completer.completeError(e.error, e.stackTrace);
|
||||
} else {
|
||||
completer.completeError(e, stackTrace);
|
||||
}
|
||||
});
|
||||
await completer.future;
|
||||
final uri = Uri.parse(url);
|
||||
final req = http.Request("GET", uri)..headers.addAll(headers ?? {});
|
||||
final response = await http.Client().send(req);
|
||||
bool isEnd = false;
|
||||
Object? error;
|
||||
final subscription = response.stream.listen(
|
||||
fileWrite.add,
|
||||
onDone: () {
|
||||
isEnd = true;
|
||||
},
|
||||
onError: (e, stackTrace) {
|
||||
_log.severe("Failed while request", e, stackTrace);
|
||||
isEnd = true;
|
||||
error = e;
|
||||
},
|
||||
cancelOnError: true,
|
||||
);
|
||||
// wait until download finished
|
||||
while (!isEnd) {
|
||||
if (shouldInterrupt) {
|
||||
await subscription.cancel();
|
||||
break;
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
if (error != null) {
|
||||
throw error!;
|
||||
}
|
||||
} finally {
|
||||
subscription?.cancel();
|
||||
fileWrite.close();
|
||||
}
|
||||
return uri;
|
||||
} on PlatformException catch (e) {
|
||||
switch (e.code) {
|
||||
case MediaStore.exceptionCodePermissionError:
|
||||
throw PermissionException();
|
||||
|
||||
case android.Download.exceptionCodeDownloadError:
|
||||
throw DownloadException(e.message);
|
||||
|
||||
case android.DownloadEvent.exceptionCodeUserCanceled:
|
||||
throw JobCanceledException(e.message);
|
||||
|
||||
default:
|
||||
rethrow;
|
||||
if (shouldInterrupt) {
|
||||
throw JobCanceledException();
|
||||
}
|
||||
|
||||
// copy the file to the actual dir
|
||||
final String path;
|
||||
if (parentDir?.isNotEmpty == true) {
|
||||
path = "$parentDir/$filename";
|
||||
} else {
|
||||
path = filename;
|
||||
}
|
||||
return await MediaStore.copyFileToDownload(path, file.path);
|
||||
} finally {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
cancel() async {
|
||||
if (_downloadId != null) {
|
||||
_log.info("[cancel] Cancel #$_downloadId");
|
||||
return await android.Download.cancel(id: _downloadId!);
|
||||
cancel() {
|
||||
shouldInterrupt = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<Directory> _openDownloadDir() async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final downloadDir = Directory("${tempDir.path}/downloads");
|
||||
if (!await downloadDir.exists()) {
|
||||
return downloadDir.create();
|
||||
} else {
|
||||
return false;
|
||||
return downloadDir;
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> _createTempFile() async {
|
||||
final downloadDir = await _openDownloadDir();
|
||||
while (true) {
|
||||
final fileName = const Uuid().v4();
|
||||
final file = File("${downloadDir.path}/$fileName");
|
||||
if (await file.exists()) {
|
||||
continue;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean up remaining cache files from previous runs
|
||||
///
|
||||
/// Normally the files will be deleted automatically
|
||||
Future<void> _cleanUp() async {
|
||||
final downloadDir = await _openDownloadDir();
|
||||
await for (final f in downloadDir.list(followLinks: false)) {
|
||||
_log.warning("[_cleanUp] Deleting file: ${f.path}");
|
||||
await f.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +149,10 @@ class _AndroidDownload extends itf.Download {
|
|||
final String filename;
|
||||
final String? parentDir;
|
||||
final bool? shouldNotify;
|
||||
int? _downloadId;
|
||||
|
||||
bool shouldInterrupt = false;
|
||||
|
||||
static bool _isInitialDownload = true;
|
||||
|
||||
static final _log = Logger("mobile.download._AndroidDownload");
|
||||
}
|
||||
|
|
|
@ -8,8 +8,11 @@ abstract class Download {
|
|||
|
||||
/// Cancel a download
|
||||
///
|
||||
/// Not all platforms support canceling an ongoing download
|
||||
Future<bool> cancel();
|
||||
/// Not all platforms support canceling an ongoing download. Return true if
|
||||
/// the current platform supports it, however there's no guarantee if and when
|
||||
/// the download task would be canceled. After a download is canceled
|
||||
/// successfully, [JobCanceledException] will be thrown in [call]
|
||||
bool cancel();
|
||||
}
|
||||
|
||||
abstract class DownloadBuilder {
|
||||
|
|
|
@ -43,7 +43,7 @@ class _WebDownload extends itf.Download {
|
|||
}
|
||||
|
||||
@override
|
||||
cancel() => Future.value(false);
|
||||
cancel() => false;
|
||||
|
||||
final String url;
|
||||
final Map<String, String>? headers;
|
||||
|
|
Loading…
Reference in a new issue