From 6044bde90fbb7aa2caf4a911e9da500c01e82987 Mon Sep 17 00:00:00 2001 From: Thomas Fiedler Date: Wed, 28 Jul 2021 13:39:11 +0200 Subject: [PATCH 1/5] #408 Add option to only download via wifi + Add `allowCellular` flag to the `enqueue` that defaults to `true` + Add separate `NSURLSession` on iOS that sets `allowsCellularAccess` false in its configuration + Schedule download requests with one of the two sessions depending on the flag of the download + Add the flag to the Android plugin and set the network constraints to either `NetworkType.CONNECTED` or `NetworkType.UNMETERED` depending on the value of the flag + Migrate sql database on Android to keep track of the flag --- .../flutterdownloader/DownloadTask.java | 6 +- .../FlutterDownloaderPlugin.java | 16 +- .../flutterdownloader/TaskContract.java | 1 + .../vn/hunghd/flutterdownloader/TaskDao.java | 10 +- .../flutterdownloader/TaskDbHelper.java | 3 +- example/lib/main.dart | 1 + ios/Classes/FlutterDownloaderPlugin.m | 465 ++++++++++-------- lib/src/downloader.dart | 7 +- 8 files changed, 291 insertions(+), 218 deletions(-) diff --git a/android/src/main/java/vn/hunghd/flutterdownloader/DownloadTask.java b/android/src/main/java/vn/hunghd/flutterdownloader/DownloadTask.java index 6d529885..14150afd 100644 --- a/android/src/main/java/vn/hunghd/flutterdownloader/DownloadTask.java +++ b/android/src/main/java/vn/hunghd/flutterdownloader/DownloadTask.java @@ -16,9 +16,10 @@ public class DownloadTask { String notificationTitle; long timeCreated; boolean saveInPublicStorage; + boolean allowCellular; DownloadTask(int primaryId, String taskId, int status, int progress, String url, String filename, String savedDir, - String headers, String mimeType, boolean resumable, boolean showNotification, boolean openFileFromNotification, String notificationTitle, long timeCreated, boolean saveInPublicStorage) { + String headers, String mimeType, boolean resumable, boolean showNotification, boolean openFileFromNotification, String notificationTitle, long timeCreated, boolean saveInPublicStorage, boolean allowCellular) { this.primaryId = primaryId; this.taskId = taskId; this.status = status; @@ -34,10 +35,11 @@ public class DownloadTask { this.notificationTitle = notificationTitle; this.timeCreated = timeCreated; this.saveInPublicStorage = saveInPublicStorage; + this.allowCellular = allowCellular; } @Override public String toString() { - return "DownloadTask{taskId=" + taskId + ", status=" + status + ", progress=" + progress + ", url=" + url + ", filename=" + filename + ", savedDir=" + savedDir + ", headers=" + headers + ", notificationTitle=" + notificationTitle + ", saveInPublicStorage= " + saveInPublicStorage + "}"; + return "DownloadTask{taskId=" + taskId + ", status=" + status + ", progress=" + progress + ", url=" + url + ", filename=" + filename + ", savedDir=" + savedDir + ", headers=" + headers + ", notificationTitle=" + notificationTitle + ", saveInPublicStorage= " + saveInPublicStorage + ", allowCellular=" + allowCellular + "}"; } } diff --git a/android/src/main/java/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.java b/android/src/main/java/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.java index 8c72a618..44157656 100644 --- a/android/src/main/java/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.java +++ b/android/src/main/java/vn/hunghd/flutterdownloader/FlutterDownloaderPlugin.java @@ -111,11 +111,12 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { private WorkRequest buildRequest(String url, String savedDir, String filename, String headers, boolean showNotification, boolean openFileFromNotification, String notificationTitle, - boolean isResume, boolean requiresStorageNotLow, boolean saveInPublicStorage) { + boolean isResume, boolean requiresStorageNotLow, boolean saveInPublicStorage, + boolean allowCellular) { WorkRequest request = new OneTimeWorkRequest.Builder(DownloadWorker.class) .setConstraints(new Constraints.Builder() + .setRequiredNetworkType(allowCellular ? NetworkType.CONNECTED : NetworkType.UNMETERED) .setRequiresStorageNotLow(requiresStorageNotLow) - .setRequiredNetworkType(NetworkType.CONNECTED) .build()) .addTag(TAG) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS) @@ -176,14 +177,17 @@ private void enqueue(MethodCall call, MethodChannel.Result result) { String notificationTitle = call.argument("notification_title"); boolean requiresStorageNotLow = call.argument("requires_storage_not_low"); boolean saveInPublicStorage = call.argument("save_in_public_storage"); + boolean allowCellular = call.argument("allow_cellular"); WorkRequest request = buildRequest(url, savedDir, filename, headers, showNotification, - openFileFromNotification, notificationTitle, false, requiresStorageNotLow, saveInPublicStorage); + openFileFromNotification, notificationTitle, false, requiresStorageNotLow, saveInPublicStorage, + allowCellular); WorkManager.getInstance(context).enqueue(request); String taskId = request.getId().toString(); result.success(taskId); sendUpdateProgress(taskId, DownloadStatus.ENQUEUED, 0); taskDao.insertOrUpdateNewTask(taskId, url, DownloadStatus.ENQUEUED, 0, filename, - savedDir, headers, showNotification, openFileFromNotification, notificationTitle, saveInPublicStorage); + savedDir, headers, showNotification, openFileFromNotification, notificationTitle, saveInPublicStorage, + allowCellular); } private void loadTasks(MethodCall call, MethodChannel.Result result) { @@ -257,7 +261,7 @@ private void resume(MethodCall call, MethodChannel.Result result) { if (partialFile.exists()) { WorkRequest request = buildRequest(task.url, task.savedDir, task.filename, task.headers, task.showNotification, task.openFileFromNotification, - task.notificationTitle, true, requiresStorageNotLow, task.saveInPublicStorage); + task.notificationTitle, true, requiresStorageNotLow, task.saveInPublicStorage, task.allowCellular); String newTaskId = request.getId().toString(); result.success(newTaskId); sendUpdateProgress(newTaskId, DownloadStatus.RUNNING, task.progress); @@ -283,7 +287,7 @@ private void retry(MethodCall call, MethodChannel.Result result) { if (task.status == DownloadStatus.FAILED || task.status == DownloadStatus.CANCELED) { WorkRequest request = buildRequest(task.url, task.savedDir, task.filename, task.headers, task.showNotification, task.openFileFromNotification, - task.notificationTitle, false, requiresStorageNotLow, task.saveInPublicStorage); + task.notificationTitle, false, requiresStorageNotLow, task.saveInPublicStorage, task.allowCellular); String newTaskId = request.getId().toString(); result.success(newTaskId); sendUpdateProgress(newTaskId, DownloadStatus.ENQUEUED, task.progress); diff --git a/android/src/main/java/vn/hunghd/flutterdownloader/TaskContract.java b/android/src/main/java/vn/hunghd/flutterdownloader/TaskContract.java index 796645e4..9a4e83cf 100644 --- a/android/src/main/java/vn/hunghd/flutterdownloader/TaskContract.java +++ b/android/src/main/java/vn/hunghd/flutterdownloader/TaskContract.java @@ -22,6 +22,7 @@ public static class TaskEntry implements BaseColumns { public static final String COLUMN_NAME_NOTIFICATION_TITLE = "notification_title"; public static final String COLUMN_NAME_TIME_CREATED = "time_created"; public static final String COLUMN_SAVE_IN_PUBLIC_STORAGE = "save_in_public_storage"; + public static final String COLUMN_NAME_ALLOW_CELLULAR = "wifi_only"; } } diff --git a/android/src/main/java/vn/hunghd/flutterdownloader/TaskDao.java b/android/src/main/java/vn/hunghd/flutterdownloader/TaskDao.java index 70cbe8b1..2184caef 100644 --- a/android/src/main/java/vn/hunghd/flutterdownloader/TaskDao.java +++ b/android/src/main/java/vn/hunghd/flutterdownloader/TaskDao.java @@ -26,7 +26,8 @@ public class TaskDao { TaskContract.TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION, TaskContract.TaskEntry.COLUMN_NAME_NOTIFICATION_TITLE, TaskContract.TaskEntry.COLUMN_NAME_TIME_CREATED, - TaskContract.TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE + TaskContract.TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE, + TaskContract.TaskEntry.COLUMN_NAME_ALLOW_CELLULAR }; public TaskDao(TaskDbHelper helper) { @@ -34,7 +35,8 @@ public TaskDao(TaskDbHelper helper) { } public void insertOrUpdateNewTask(String taskId, String url, int status, int progress, String fileName, - String savedDir, String headers, boolean showNotification, boolean openFileFromNotification, String notificationTitle, boolean saveInPublicStorage) { + String savedDir, String headers, boolean showNotification, boolean openFileFromNotification, + String notificationTitle, boolean saveInPublicStorage, boolean allowCellular) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -52,6 +54,7 @@ public void insertOrUpdateNewTask(String taskId, String url, int status, int pro values.put(TaskContract.TaskEntry.COLUMN_NAME_RESUMABLE, 0); values.put(TaskContract.TaskEntry.COLUMN_NAME_TIME_CREATED, System.currentTimeMillis()); values.put(TaskContract.TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE, saveInPublicStorage ? 1 : 0); + values.put(TaskContract.TaskEntry.COLUMN_NAME_ALLOW_CELLULAR, allowCellular ? 1 : 0); db.beginTransaction(); try { @@ -229,8 +232,9 @@ private DownloadTask parseCursor(Cursor cursor) { int clickToOpenDownloadedFile = cursor.getShort(cursor.getColumnIndexOrThrow(TaskContract.TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION)); long timeCreated = cursor.getLong(cursor.getColumnIndexOrThrow(TaskContract.TaskEntry.COLUMN_NAME_TIME_CREATED)); int saveInPublicStorage = cursor.getShort(cursor.getColumnIndexOrThrow(TaskContract.TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE)); + int allowCellular = cursor.getInt(cursor.getColumnIndexOrThrow(TaskContract.TaskEntry.COLUMN_NAME_ALLOW_CELLULAR)); return new DownloadTask(primaryId, taskId, status, progress, url, filename, savedDir, headers, - mimeType, resumable == 1, showNotification == 1, clickToOpenDownloadedFile == 1, notificationTitle, timeCreated, saveInPublicStorage == 1); + mimeType, resumable == 1, showNotification == 1, clickToOpenDownloadedFile == 1, notificationTitle, timeCreated, saveInPublicStorage == 1, allowCellular == 1); } } diff --git a/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java b/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java index 71b1fabc..62b1de53 100644 --- a/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java +++ b/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java @@ -28,7 +28,8 @@ public class TaskDbHelper extends SQLiteOpenHelper { TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION + " TINYINT DEFAULT 0, " + TaskEntry.COLUMN_NAME_NOTIFICATION_TITLE + " TEXT, " + TaskEntry.COLUMN_NAME_TIME_CREATED + " INTEGER DEFAULT 0, " + - TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE + " TINYINT DEFAULT 0" + TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE + " TINYINT DEFAULT 0" + + TaskEntry.COLUMN_NAME_ALLOW_CELLULAR + " INTEGER DEFAULT 0" + ")"; private static final String SQL_DELETE_ENTRIES = diff --git a/example/lib/main.dart b/example/lib/main.dart index 1cbf9969..29c1e56c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:flutter_downloader_example/home_page.dart'; void main() async { diff --git a/ios/Classes/FlutterDownloaderPlugin.m b/ios/Classes/FlutterDownloaderPlugin.m index 3e4974c3..c9adda9b 100644 --- a/ios/Classes/FlutterDownloaderPlugin.m +++ b/ios/Classes/FlutterDownloaderPlugin.m @@ -23,6 +23,7 @@ #define KEY_OPEN_FILE_FROM_NOTIFICATION @"open_file_from_notification" #define KEY_QUERY @"query" #define KEY_TIME_CREATED @"time_created" +#define KEY_ALLOW_CELLULAR @"allow_cellular" #define NULL_VALUE @"" @@ -33,7 +34,9 @@ @interface FlutterDownloaderPlugin() *_registrar; + NSObject *_registrar; + NSURLSession *_session; + NSURLSession *_wifiSession; DBManager *_dbManager; NSString *_allFilesDownloadedMsg; NSMutableArray *_eventQueue; @@ -60,8 +63,7 @@ @implementation FlutterDownloaderPlugin @synthesize databaseQueue; -- (instancetype)init:(NSObject *)registrar; -{ +- (instancetype)init:(NSObject *)registrar; { if (self = [super init]) { BOOL _isolate = NO; if (_headlessRunner == nil) { @@ -73,13 +75,13 @@ - (instancetype)init:(NSObject *)registrar; _registrar = registrar; _mainChannel = [FlutterMethodChannel - methodChannelWithName:@"vn.hunghd/downloader" - binaryMessenger:[registrar messenger]]; + methodChannelWithName:@"vn.hunghd/downloader" + binaryMessenger:[registrar messenger]]; [registrar addMethodCallDelegate:self channel:_mainChannel]; _callbackChannel = - [FlutterMethodChannel methodChannelWithName:@"vn.hunghd/downloader_background" - binaryMessenger:[_headlessRunner binaryMessenger]]; + [FlutterMethodChannel methodChannelWithName:@"vn.hunghd/downloader_background" + binaryMessenger:[_headlessRunner binaryMessenger]]; _eventQueue = [[NSMutableArray alloc] init]; @@ -92,17 +94,15 @@ - (instancetype)init:(NSObject *)registrar; if (debug) { NSLog(@"database path: %@", dbPath); } - databaseQueue = dispatch_queue_create("vn.hunghd.flutter_downloader", 0); + databaseQueue = dispatch_queue_create("vn.hunghd.flutter_downloader", nil); _dbManager = [[DBManager alloc] initWithDatabaseFilePath:dbPath]; - + if (_runningTaskById == nil) { _runningTaskById = [[NSMutableDictionary alloc] init]; } NSBundle *mainBundle = [NSBundle mainBundle]; - - // init NSURLSession in background isolate - if (_isolate) { + if(_isolate) { NSNumber *maxConcurrentTasks = [mainBundle objectForInfoDictionaryKey:@"FDMaximumConcurrentTasks"]; if (maxConcurrentTasks == nil) { maxConcurrentTasks = @3; @@ -110,13 +110,20 @@ - (instancetype)init:(NSObject *)registrar; if (debug) { NSLog(@"MAXIMUM_CONCURRENT_TASKS = %@", maxConcurrentTasks); } - // session identifier needs to be the same for background download and resume to work - NSString *identifier = [NSString stringWithFormat:@"%@.download.background.session", NSBundle.mainBundle.bundleIdentifier]; - NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier]; + + // configure a session that only allows downloads via wifi + NSURLSessionConfiguration *wifiSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[NSString stringWithFormat:@"%@.wifi.download.background.%f", NSBundle.mainBundle.bundleIdentifier, [[NSDate date] timeIntervalSince1970]]]; + wifiSessionConfiguration.HTTPMaximumConnectionsPerHost = [maxConcurrentTasks intValue]; + wifiSessionConfiguration.allowsCellularAccess = false; + _wifiSession = [NSURLSession sessionWithConfiguration:wifiSessionConfiguration delegate:self delegateQueue:nil]; + + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[NSString stringWithFormat:@"%@.download.background.%f", NSBundle.mainBundle.bundleIdentifier, [[NSDate date] timeIntervalSince1970]]]; sessionConfiguration.HTTPMaximumConnectionsPerHost = [maxConcurrentTasks intValue]; + sessionConfiguration.allowsCellularAccess = true; _session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil]; if (debug) { NSLog(@"init NSURLSession with id: %@", [[_session configuration] identifier]); + NSLog(@"init wifi only NSURLSession with id: %@", [[_wifiSession configuration] identifier]); } } @@ -155,12 +162,15 @@ - (FlutterMethodChannel *)channel { return _mainChannel; } -- (NSURLSession*)currentSession { +- (NSURLSession *)currentSession { return _session; } -- (NSURLSessionDownloadTask*)downloadTaskWithURL: (NSURL*) url fileName: (NSString*) fileName andSavedDir: (NSString*) savedDir andHeaders: (NSString*) headers -{ +- (NSURLSession *)wifiSession { + return _wifiSession; +} + +- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url fileName:(NSString *)fileName andSavedDir:(NSString *)savedDir andHeaders:(NSString *)headers wifiOnly:(bool)wifiOnly { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; if (headers != nil && [headers length] > 0) { NSError *jsonError; @@ -175,7 +185,12 @@ - (NSURLSessionDownloadTask*)downloadTaskWithURL: (NSURL*) url fileName: (NSStri [request setValue:value forHTTPHeaderField:key]; } } - NSURLSessionDownloadTask *task = [[self currentSession] downloadTaskWithRequest:request]; + NSURLSessionDownloadTask *task; + if (wifiOnly) { + task = [[self wifiSession] downloadTaskWithRequest:request]; + } else { + task = [[self currentSession] downloadTaskWithRequest:request]; + } // store task id in taskDescription task.taskDescription = [self createTaskId]; [task resume]; @@ -183,95 +198,117 @@ - (NSURLSessionDownloadTask*)downloadTaskWithURL: (NSURL*) url fileName: (NSStri return task; } -- (NSString*) createTaskId { +//- (NSString*) createTaskId { return [NSString stringWithFormat:@"%@.download.task.%d.%f", NSBundle.mainBundle.bundleIdentifier, arc4random_uniform(100000), [[NSDate date] timeIntervalSince1970]]; } -- (NSString*)identifierForTask:(NSURLSessionTask*) task -{ - return task.taskDescription; -} +- (NSString *)identifierForTask:(NSURLSessionTask *)task { +// return task.taskDescription; +//} -- (NSString*)identifierForTask:(NSURLSessionTask*) task ofSession:(NSURLSession *)session -{ +- (NSString *)identifierForTask:(NSURLSessionTask *)task ofSession:(NSURLSession *)session { return task.taskDescription; } -- (void)updateRunningTaskById:(NSString*)taskId progress:(int)progress status:(int)status resumable:(BOOL)resumable { +- (void)updateRunningTaskById:(NSString *)taskId progress:(int)progress status:(int)status resumable:(BOOL)resumable { _runningTaskById[taskId][KEY_PROGRESS] = @(progress); _runningTaskById[taskId][KEY_STATUS] = @(status); _runningTaskById[taskId][KEY_RESUMABLE] = @(resumable); } -- (void)pauseTaskWithId: (NSString*)taskId -{ +- (void)pauseTaskWithId:(NSString *)taskId { if (debug) { NSLog(@"pause task with id: %@", taskId); } __typeof__(self) __weak weakSelf = self; - [[self currentSession] getTasksWithCompletionHandler:^(NSArray *data, NSArray *uploads, NSArray *downloads) { - for (NSURLSessionDownloadTask *download in downloads) { - NSURLSessionTaskState state = download.state; - NSString *taskIdValue = [weakSelf identifierForTask:download]; - if ([taskId isEqualToString:taskIdValue] && (state == NSURLSessionTaskStateRunning)) { - int64_t bytesReceived = download.countOfBytesReceived; - int64_t bytesExpectedToReceive = download.countOfBytesExpectedToReceive; - int progress = round(bytesReceived * 100 / (double)bytesExpectedToReceive); - NSDictionary *task = [weakSelf loadTaskWithId:taskIdValue]; - [download cancelByProducingResumeData:^(NSData * _Nullable resumeData) { - // Save partial downloaded data to a file - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *destinationURL = [weakSelf fileUrlOf:taskId taskInfo:task downloadTask:download]; - - if ([fileManager fileExistsAtPath:[destinationURL path]]) { - [fileManager removeItemAtURL:destinationURL error:nil]; - } + for (NSURLSessionDownloadTask *download in [self getTasksForAllSessions]) { + NSURLSessionTaskState state = download.state; + NSString *taskIdValue = [weakSelf identifierForTask:download ofSession: self.currentSession]; + NSString *taskIdWifi = [weakSelf identifierForTask:download ofSession: self.wifiSession]; + if (([taskId isEqualToString:taskIdValue] || [taskId isEqualToString:taskIdWifi]) && (state == NSURLSessionTaskStateRunning)) { + int64_t bytesReceived = download.countOfBytesReceived; + int64_t bytesExpectedToReceive = download.countOfBytesExpectedToReceive; + int progress = round(bytesReceived * 100 / (double) bytesExpectedToReceive); + NSDictionary *task = [weakSelf loadTaskWithId:taskIdValue]; + [download cancelByProducingResumeData:^(NSData *_Nullable resumeData) { + // Save partial downloaded data to a file + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *destinationURL = [weakSelf fileUrlOf:taskId taskInfo:task downloadTask:download]; + + if ([fileManager fileExistsAtPath:[destinationURL path]]) { + [fileManager removeItemAtURL:destinationURL error:nil]; + } - BOOL success = [resumeData writeToURL:destinationURL atomically:YES]; - if (debug) { - NSLog(@"save partial downloaded data to a file: %s", success ? "success" : "failure"); - } - }]; + BOOL success = [resumeData writeToURL:destinationURL atomically:YES]; + if (debug) { + NSLog(@"save partial downloaded data to a file: %s", success ? "success" : "failure"); + } + }]; - [weakSelf updateRunningTaskById:taskId progress:progress status:STATUS_PAUSED resumable:YES]; + [weakSelf updateRunningTaskById:taskId progress:progress status:STATUS_PAUSED resumable:YES]; - [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_PAUSED) andProgress:@(progress)]; + [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_PAUSED) andProgress:@(progress)]; - [weakSelf executeInDatabaseQueueForTask:^{ - [weakSelf updateTask:taskId status:STATUS_PAUSED progress:progress resumable:YES]; - }]; - return; - } - }; - }]; + [weakSelf executeInDatabaseQueueForTask: ^{ + [weakSelf updateTask:taskId status:STATUS_PAUSED progress:progress resumable:YES]; + }]; + return; + } + }; } -- (void)cancelTaskWithId: (NSString*)taskId -{ +- (void)cancelTaskWithId:(NSString *)taskId { if (debug) { NSLog(@"cancel task with id: %@", taskId); } + __typeof__(self) __weak weakSelf = self; + for (NSURLSessionDownloadTask *download in [self getTasksForAllSessions]) { + NSURLSessionTaskState state = download.state; + NSString *taskIdValue = [self identifierForTask:download ofSession: self.currentSession]; + NSString *taskIdWifi = [self identifierForTask:download ofSession: self.wifiSession]; + if (([taskId isEqualToString:taskIdValue] || [taskId isEqualToString:taskIdWifi]) && (state == NSURLSessionTaskStateRunning)) { + [download cancel]; + [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_CANCELED) andProgress:@(-1)]; + [weakSelf executeInDatabaseQueueForTask: ^{ + [weakSelf updateTask:taskId status:STATUS_CANCELED progress:-1]; + }]; + return; + } + + }; +} + +- (NSArray *)getTasksForAllSessions { + NSArray *allDownloads; + + [[self currentSession] getTasksWithCompletionHandler:^(NSArray *data, NSArray *uploads, NSArray *downloads) { + [allDownloads arrayByAddingObjectsFromArray:downloads]; + }]; + + [[self wifiSession] getTasksWithCompletionHandler:^(NSArray *data, NSArray *uploads, NSArray *downloads) { + [allDownloads arrayByAddingObjectsFromArray:downloads]; + }]; + + return allDownloads; +} + +- (void)cancelAllTasks { __typeof__(self) __weak weakSelf = self; [[self currentSession] getTasksWithCompletionHandler:^(NSArray *data, NSArray *uploads, NSArray *downloads) { for (NSURLSessionDownloadTask *download in downloads) { NSURLSessionTaskState state = download.state; - NSString *taskIdValue = [self identifierForTask:download]; - if ([taskId isEqualToString:taskIdValue] && (state == NSURLSessionTaskStateRunning)) { + if (state == NSURLSessionTaskStateRunning) { [download cancel]; + NSString *taskId = [self identifierForTask:download]; [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_CANCELED) andProgress:@(-1)]; [weakSelf executeInDatabaseQueueForTask:^{ [weakSelf updateTask:taskId status:STATUS_CANCELED progress:-1]; }]; - return; } }; }]; -} - -- (void)cancelAllTasks { - __typeof__(self) __weak weakSelf = self; - [[self currentSession] getTasksWithCompletionHandler:^(NSArray *data, NSArray *uploads, NSArray *downloads) { + [[self wifiSession] getTasksWithCompletionHandler:^(NSArray *data, NSArray *uploads, NSArray *downloads) { for (NSURLSessionDownloadTask *download in downloads) { NSURLSessionTaskState state = download.state; if (state == NSURLSessionTaskStateRunning) { @@ -286,8 +323,19 @@ - (void)cancelAllTasks { }]; } -- (void)sendUpdateProgressForTaskId: (NSString*)taskId inStatus: (NSNumber*) status andProgress: (NSNumber*) progress -{ +- (void)cancel:(NSURLSessionDownloadTask *)download ofSession:(NSURLSession *)session { + NSURLSessionTaskState state = download.state; + if (state == NSURLSessionTaskStateRunning) { + [download cancel]; + NSString *taskId = [self identifierForTask:download ofSession:session]; + [self sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_CANCELED) andProgress:@(-1)]; + dispatch_sync([self databaseQueue], ^{ + [self updateTask:taskId status:STATUS_CANCELED progress:-1]; + }); + } +} + +- (void)sendUpdateProgressForTaskId:(NSString *)taskId inStatus:(NSNumber *)status andProgress:(NSNumber *)progress { NSArray *args = @[@(_callbackHandle), taskId, status, progress]; if (initialized && _callbackHandle != 0) { [_callbackChannel invokeMethod:@"" arguments:args]; @@ -304,15 +352,14 @@ - (void)executeInDatabaseQueueForTask:(void (^)(void))task { }); } -- (BOOL)openDocumentWithURL:(NSURL*)url { +- (BOOL)openDocumentWithURL:(NSURL *)url { if (debug) { NSLog(@"try to open file in url: %@", url); } BOOL result = NO; - UIDocumentInteractionController* tmpDocController = [UIDocumentInteractionController - interactionControllerWithURL:url]; - if (tmpDocController) - { + UIDocumentInteractionController *tmpDocController = [UIDocumentInteractionController + interactionControllerWithURL:url]; + if (tmpDocController) { if (debug) { NSLog(@"initialize UIDocumentInteractionController successfully"); } @@ -322,8 +369,7 @@ - (BOOL)openDocumentWithURL:(NSURL*)url { return result; } -- (NSURL*)fileUrlFromDict:(NSDictionary*)dict -{ +- (NSURL *)fileUrlFromDict:(NSDictionary *)dict { NSString *savedDir = dict[KEY_SAVED_DIR]; NSString *filename = dict[KEY_FILE_NAME]; if (debug) { @@ -334,7 +380,7 @@ - (NSURL*)fileUrlFromDict:(NSDictionary*)dict return [savedDirURL URLByAppendingPathComponent:filename]; } -- (NSURL*)fileUrlOf:(NSString*)taskId taskInfo:(NSDictionary*)taskInfo downloadTask:(NSURLSessionDownloadTask*)downloadTask { +- (NSURL *)fileUrlOf:(NSString *)taskId taskInfo:(NSDictionary *)taskInfo downloadTask:(NSURLSessionDownloadTask *)downloadTask { NSString *filename = taskInfo[KEY_FILE_NAME]; NSString *suggestedFilename = downloadTask.response.suggestedFilename; if (debug) { @@ -342,7 +388,7 @@ - (NSURL*)fileUrlOf:(NSString*)taskId taskInfo:(NSDictionary*)taskInfo downloadT } // check filename, if it is empty then we try to extract it from http response or url path - if (filename == (NSString*) [NSNull null] || [NULL_VALUE isEqualToString: filename]) { + if (filename == (NSString *) [NSNull null] || [NULL_VALUE isEqualToString:filename]) { if (suggestedFilename) { filename = suggestedFilename; } else { @@ -369,17 +415,17 @@ - (NSURL*)fileUrlOf:(NSString*)taskId taskInfo:(NSDictionary*)taskInfo downloadT return [self fileUrlFromDict:taskInfo]; } -- (NSString*)absoluteSavedDirPath:(NSString*)savedDir { +- (NSString *)absoluteSavedDirPath:(NSString *)savedDir { return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:savedDir]; } -- (NSString*)shortenSavedDirPath:(NSString*)absolutePath { +- (NSString *)shortenSavedDirPath:(NSString *)absolutePath { if (debug) { NSLog(@"Absolute savedDir path: %@", absolutePath); } if (absolutePath) { - NSString* documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; if ([absolutePath isEqualToString:documentDirPath]) { return @""; } @@ -394,26 +440,22 @@ - (NSString*)shortenSavedDirPath:(NSString*)absolutePath { return absolutePath; } -- (long long)currentTimeInMilliseconds -{ - return (long long)([[NSDate date] timeIntervalSince1970]*1000); +- (long long)currentTimeInMilliseconds { + return (long long) ([[NSDate date] timeIntervalSince1970] * 1000); } # pragma mark - Database Accessing -- (NSString*) escape:(NSString*) origin revert:(BOOL)revert -{ - if ( origin == (NSString *)[NSNull null] ) - { +- (NSString *)escape:(NSString *)origin revert:(BOOL)revert { + if (origin == (NSString *) [NSNull null]) { return @""; } return revert - ? [origin stringByRemovingPercentEncoding] - : [origin stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]; + ? [origin stringByRemovingPercentEncoding] + : [origin stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]; } -- (void) addNewTask: (NSString*) taskId url: (NSString*) url status: (int) status progress: (int) progress filename: (NSString*) filename savedDir: (NSString*) savedDir headers: (NSString*) headers resumable: (BOOL) resumable showNotification: (BOOL) showNotification openFileFromNotification: (BOOL) openFileFromNotification -{ +- (void)addNewTask:(NSString *)taskId url:(NSString *)url status:(int)status progress:(int)progress filename:(NSString *)filename savedDir:(NSString *)savedDir headers:(NSString *)headers resumable:(BOOL)resumable showNotification:(BOOL)showNotification openFileFromNotification:(BOOL)openFileFromNotification { headers = [self escape:headers revert:false]; NSString *query = [NSString stringWithFormat:@"INSERT INTO task (task_id,url,status,progress,file_name,saved_dir,headers,resumable,show_notification,open_file_from_notification,time_created) VALUES (\"%@\",\"%@\",%d,%d,\"%@\",\"%@\",\"%@\",%d,%d,%d,%lld)", taskId, url, status, progress, filename, savedDir, headers, resumable ? 1 : 0, showNotification ? 1 : 0, openFileFromNotification ? 1 : 0, [self currentTimeInMilliseconds]]; [_dbManager executeQuery:query]; @@ -426,8 +468,7 @@ - (void) addNewTask: (NSString*) taskId url: (NSString*) url status: (int) statu } } -- (void) updateTask: (NSString*) taskId status: (int) status progress: (int) progress -{ +- (void)updateTask:(NSString *)taskId status:(int)status progress:(int)progress { NSString *query = [NSString stringWithFormat:@"UPDATE task SET status=%d, progress=%d WHERE task_id=\"%@\"", status, progress, taskId]; [_dbManager executeQuery:query]; if (debug) { @@ -439,7 +480,7 @@ - (void) updateTask: (NSString*) taskId status: (int) status progress: (int) pro } } -- (void) updateTask: (NSString*) taskId filename: (NSString*) filename { +- (void)updateTask:(NSString *)taskId filename:(NSString *)filename { NSString *query = [NSString stringWithFormat:@"UPDATE task SET file_name=\"%@\" WHERE task_id=\"%@\"", filename, taskId]; [_dbManager executeQuery:query]; if (debug) { @@ -451,7 +492,7 @@ - (void) updateTask: (NSString*) taskId filename: (NSString*) filename { } } -- (void) updateTask: (NSString*) taskId status: (int) status progress: (int) progress resumable: (BOOL) resumable { +- (void)updateTask:(NSString *)taskId status:(int)status progress:(int)progress resumable:(BOOL)resumable { NSString *query = [NSString stringWithFormat:@"UPDATE task SET status=%d, progress=%d, resumable=%d WHERE task_id=\"%@\"", status, progress, resumable ? 1 : 0, taskId]; [_dbManager executeQuery:query]; if (debug) { @@ -463,7 +504,7 @@ - (void) updateTask: (NSString*) taskId status: (int) status progress: (int) pro } } -- (void) updateTask: (NSString*) currentTaskId newTaskId: (NSString*) newTaskId status: (int) status resumable: (BOOL) resumable { +- (void)updateTask:(NSString *)currentTaskId newTaskId:(NSString *)newTaskId status:(int)status resumable:(BOOL)resumable { NSString *query = [NSString stringWithFormat:@"UPDATE task SET task_id=\"%@\", status=%d, resumable=%d, time_created=%lld WHERE task_id=\"%@\"", newTaskId, status, resumable ? 1 : 0, [self currentTimeInMilliseconds], currentTaskId]; [_dbManager executeQuery:query]; if (debug) { @@ -475,8 +516,7 @@ - (void) updateTask: (NSString*) currentTaskId newTaskId: (NSString*) newTaskId } } -- (void) updateTask: (NSString*) taskId resumable: (BOOL) resumable -{ +- (void)updateTask:(NSString *)taskId resumable:(BOOL)resumable { NSString *query = [NSString stringWithFormat:@"UPDATE task SET resumable=%d WHERE task_id=\"%@\"", resumable ? 1 : 0, taskId]; [_dbManager executeQuery:query]; if (debug) { @@ -488,7 +528,7 @@ - (void) updateTask: (NSString*) taskId resumable: (BOOL) resumable } } -- (void) deleteTask: (NSString*) taskId { +- (void)deleteTask:(NSString *)taskId { NSString *query = [NSString stringWithFormat:@"DELETE FROM task WHERE task_id=\"%@\"", taskId]; [_dbManager executeQuery:query]; if (debug) { @@ -500,15 +540,14 @@ - (void) deleteTask: (NSString*) taskId { } } -- (NSArray*)loadAllTasks -{ +- (NSArray *)loadAllTasks { NSString *query = @"SELECT * FROM task"; NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query]]; if (debug) { NSLog(@"Load tasks successfully"); } NSMutableArray *results = [NSMutableArray new]; - for(NSArray *record in records) { + for (NSArray *record in records) { NSDictionary *task = [self taskDictFromRecordArray:record]; if (debug) { NSLog(@"%@", task); @@ -518,21 +557,19 @@ - (NSArray*)loadAllTasks return results; } -- (NSArray*)loadTasksWithRawQuery: (NSString*)query -{ +- (NSArray *)loadTasksWithRawQuery:(NSString *)query { NSArray *records = [[NSArray alloc] initWithArray:[_dbManager loadDataFromDB:query]]; if (debug) { NSLog(@"Load tasks successfully"); } NSMutableArray *results = [NSMutableArray new]; - for(NSArray *record in records) { + for (NSArray *record in records) { [results addObject:[self taskDictFromRecordArray:record]]; } return results; } -- (NSDictionary*)loadTaskWithId:(NSString*)taskId -{ +- (NSDictionary *)loadTaskWithId:(NSString *)taskId { // check task in memory-cache first if ([_runningTaskById objectForKey:taskId]) { return [_runningTaskById objectForKey:taskId]; @@ -589,7 +626,7 @@ - (NSDictionary*) taskDictFromRecordArray:(NSArray*)record # pragma mark - FlutterDownloader -- (void)initializeMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)initializeMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSArray *arguments = call.arguments; debug = [arguments[1] boolValue]; _dbManager.debug = debug; @@ -597,7 +634,7 @@ - (void)initializeMethodCall:(FlutterMethodCall*)call result:(FlutterResult)resu result([NSNull null]); } -- (void)didInitializeDispatcherMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)didInitializeDispatcherMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { initialized = YES; if (_callbackHandle != 0) { // unqueue if callback handler has been set [self unqueueStatusEvents]; @@ -605,7 +642,7 @@ - (void)didInitializeDispatcherMethodCall:(FlutterMethodCall*)call result:(Flutt result([NSNull null]); } -- (void)registerCallbackMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)registerCallbackMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSArray *arguments = call.arguments; _callbackHandle = [arguments[0] longLongValue]; _step = [arguments[1] intValue]; @@ -624,7 +661,7 @@ - (void) unqueueStatusEvents { } } -- (void)enqueueMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)enqueueMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *urlString = call.arguments[KEY_URL]; NSString *savedDir = call.arguments[KEY_SAVED_DIR]; NSString *shortSavedDir = [self shortenSavedDirPath:savedDir]; @@ -632,71 +669,71 @@ - (void)enqueueMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result NSString *headers = call.arguments[KEY_HEADERS]; NSNumber *showNotification = call.arguments[KEY_SHOW_NOTIFICATION]; NSNumber *openFileFromNotification = call.arguments[KEY_OPEN_FILE_FROM_NOTIFICATION]; + bool allowCellular = [call.arguments[KEY_ALLOW_CELLULAR] boolValue]; - NSURLSessionDownloadTask *task = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers]; + NSURLSessionDownloadTask *task = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers wifiOnly:allowCellular]; - NSString *taskId = [self identifierForTask:task]; + NSString *taskId = allowCellular ? [self identifierForTask:task ofSession:self.currentSession] : [self identifierForTask:task ofSession:self.wifiSession]; - [_runningTaskById setObject: [NSMutableDictionary dictionaryWithObjectsAndKeys: - urlString, KEY_URL, - fileName, KEY_FILE_NAME, - savedDir, KEY_SAVED_DIR, - headers, KEY_HEADERS, - showNotification, KEY_SHOW_NOTIFICATION, - openFileFromNotification, KEY_OPEN_FILE_FROM_NOTIFICATION, - @(NO), KEY_RESUMABLE, - @(STATUS_ENQUEUED), KEY_STATUS, - @(0), KEY_PROGRESS, nil] - forKey:taskId]; + _runningTaskById[taskId] = [@{KEY_URL: urlString, + KEY_FILE_NAME: fileName, + KEY_SAVED_DIR: savedDir, + KEY_HEADERS: headers, + KEY_SHOW_NOTIFICATION: showNotification, + KEY_OPEN_FILE_FROM_NOTIFICATION: openFileFromNotification, + KEY_RESUMABLE: @(NO), + KEY_STATUS: @(STATUS_ENQUEUED), + KEY_PROGRESS: @(0), + KEY_ALLOW_CELLULAR: @(allowCellular)} mutableCopy]; __typeof__(self) __weak weakSelf = self; - + [self executeInDatabaseQueueForTask:^{ - [weakSelf addNewTask:taskId url:urlString status:STATUS_ENQUEUED progress:0 filename:fileName savedDir:shortSavedDir headers:headers resumable:NO showNotification: [showNotification boolValue] openFileFromNotification: [openFileFromNotification boolValue]]; + [weakSelf addNewTask:taskId url:urlString status:STATUS_ENQUEUED progress:0 filename:fileName savedDir:shortSavedDir headers:headers resumable:NO showNotification:[showNotification boolValue] openFileFromNotification:[openFileFromNotification boolValue]]; }]; result(taskId); [self sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_ENQUEUED) andProgress:@0]; } -- (void)loadTasksMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)loadTasksMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { __typeof__(self) __weak weakSelf = self; [self executeInDatabaseQueueForTask:^{ - NSArray* tasks = [weakSelf loadAllTasks]; + NSArray *tasks = [weakSelf loadAllTasks]; result(tasks); }]; } -- (void)loadTasksWithRawQueryMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)loadTasksWithRawQueryMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *query = call.arguments[KEY_QUERY]; __typeof__(self) __weak weakSelf = self; [self executeInDatabaseQueueForTask:^{ - NSArray* tasks = [weakSelf loadTasksWithRawQuery:query]; + NSArray *tasks = [weakSelf loadTasksWithRawQuery:query]; result(tasks); }]; } -- (void)cancelMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)cancelMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *taskId = call.arguments[KEY_TASK_ID]; [self cancelTaskWithId:taskId]; result([NSNull null]); } -- (void)cancelAllMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)cancelAllMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { [self cancelAllTasks]; result([NSNull null]); } -- (void)pauseMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)pauseMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *taskId = call.arguments[KEY_TASK_ID]; [self pauseTaskWithId:taskId]; result([NSNull null]); } -- (void)resumeMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)resumeMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *taskId = call.arguments[KEY_TASK_ID]; - NSDictionary* taskDict = [self loadTaskWithId:taskId]; + NSDictionary *taskDict = [self loadTaskWithId:taskId]; if (taskDict != nil) { - NSNumber* status = taskDict[KEY_STATUS]; + NSNumber *status = taskDict[KEY_STATUS]; if ([status intValue] == STATUS_PAUSED) { NSURL *partialFileURL = [self fileUrlFromDict:taskDict]; @@ -707,7 +744,8 @@ - (void)resumeMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSData *resumeData = [NSData dataWithContentsOfURL:partialFileURL]; if (resumeData != nil) { - NSURLSessionDownloadTask *task = [[self currentSession] downloadTaskWithResumeData:resumeData]; + bool wifiOnly = (bool)taskDict[KEY_ALLOW_CELLULAR]; + NSURLSessionDownloadTask *task = [(wifiOnly ? self.wifiSession : self.currentSession) downloadTaskWithResumeData:resumeData]; NSString *newTaskId = [self createTaskId]; task.taskDescription = newTaskId; [task resume]; @@ -743,19 +781,20 @@ - (void)resumeMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } -- (void)retryMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)retryMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *taskId = call.arguments[KEY_TASK_ID]; - NSDictionary* taskDict = [self loadTaskWithId:taskId]; + NSDictionary *taskDict = [self loadTaskWithId:taskId]; if (taskDict != nil) { - NSNumber* status = taskDict[KEY_STATUS]; + NSNumber *status = taskDict[KEY_STATUS]; if ([status intValue] == STATUS_FAILED || [status intValue] == STATUS_CANCELED) { NSString *urlString = taskDict[KEY_URL]; NSString *savedDir = taskDict[KEY_SAVED_DIR]; NSString *fileName = taskDict[KEY_FILE_NAME]; NSString *headers = taskDict[KEY_HEADERS]; + bool allowCellular = (bool) taskDict[KEY_ALLOW_CELLULAR]; - NSURLSessionDownloadTask *newTask = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers]; - NSString *newTaskId = [self identifierForTask:newTask]; + NSURLSessionDownloadTask *newTask = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers wifiOnly: allowCellular]; + NSString *newTaskId = [self identifierForTask:newTask ofSession: (allowCellular ? self.currentSession : self.wifiSession)]; // update memory-cache NSMutableDictionary *newTaskDict = [NSMutableDictionary dictionaryWithDictionary:taskDict]; @@ -781,11 +820,11 @@ - (void)retryMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } -- (void)openMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)openMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *taskId = call.arguments[KEY_TASK_ID]; - NSDictionary* taskDict = [self loadTaskWithId:taskId]; + NSDictionary *taskDict = [self loadTaskWithId:taskId]; if (taskDict != nil) { - NSNumber* status = taskDict[KEY_STATUS]; + NSNumber *status = taskDict[KEY_STATUS]; if ([status intValue] == STATUS_COMPLETE) { NSURL *downloadedFileURL = [self fileUrlFromDict:taskDict]; @@ -801,35 +840,34 @@ - (void)openMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } -- (void)removeMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)removeMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { __typeof__(self) __weak weakSelf = self; - NSString *taskId = call.arguments[KEY_TASK_ID]; - Boolean shouldDeleteContent = [call.arguments[@"should_delete_content"] boolValue]; - NSDictionary* taskDict = [self loadTaskWithId:taskId]; + Boolean shouldDeleteContent = (Boolean) [call.arguments[@"should_delete_content"] boolValue]; + NSDictionary *taskDict = [self loadTaskWithId:taskId]; if (taskDict != nil) { - NSNumber* status = taskDict[KEY_STATUS]; + NSNumber *status = taskDict[KEY_STATUS]; if ([status intValue] == STATUS_ENQUEUED || [status intValue] == STATUS_RUNNING) { - [[self currentSession] getTasksWithCompletionHandler:^(NSArray *data, NSArray *uploads, NSArray *downloads) { - for (NSURLSessionDownloadTask *download in downloads) { - NSURLSessionTaskState state = download.state; - NSString *taskIdValue = [weakSelf identifierForTask:download]; - if ([taskId isEqualToString:taskIdValue] && (state == NSURLSessionTaskStateRunning)) { - [download cancel]; - [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_CANCELED) andProgress:@(-1)]; - [weakSelf executeInDatabaseQueueForTask:^{ - [weakSelf deleteTask:taskId]; - }]; - return; - } - }; - }]; + __typeof__(self) __weak weakSelf = self; + for (NSURLSessionDownloadTask *download in [self getTasksForAllSessions]) { + NSURLSessionTaskState state = download.state; + NSString *taskIdValue = [weakSelf identifierForTask:download ofSession:self.currentSession]; + NSString *taskIdWifi = [weakSelf identifierForTask:download ofSession:self.wifiSession]; + if (([taskId isEqualToString:taskIdValue] || [taskIdWifi isEqualToString:taskIdValue]) && (state == NSURLSessionTaskStateRunning)) { + [download cancel]; + [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_CANCELED) andProgress:@(-1)]; + [weakSelf executeInDatabaseQueueForTask:^{ + [weakSelf deleteTask:taskId]; + }]; + return; + } + }; } - + [self executeInDatabaseQueueForTask:^{ [weakSelf deleteTask:taskId]; }]; - + if (shouldDeleteContent) { NSURL *destinationURL = [self fileUrlFromDict:taskDict]; @@ -855,15 +893,15 @@ - (void)removeMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { # pragma mark - FlutterPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [registrar addApplicationDelegate: [[FlutterDownloaderPlugin alloc] init:registrar]]; ++ (void)registerWithRegistrar:(NSObject *)registrar { + [registrar addApplicationDelegate:[[FlutterDownloaderPlugin alloc] init:registrar]]; } + (void)setPluginRegistrantCallback:(FlutterPluginRegistrantCallback)callback { - registerPlugins = callback; + registerPlugins = callback; } -- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"initialize" isEqualToString:call.method]) { [self initializeMethodCall:call result:result]; } else if ([@"didInitializeDispatcher" isEqualToString:call.method]) { @@ -902,15 +940,15 @@ - (BOOL)application:(UIApplication *)application handleEventsForBackgroundURLSes } # pragma mark - NSURLSessionTaskDelegate -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite -{ + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { if (totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown) { if (debug) { NSLog(@"Unknown transfer size"); } } else { - NSString *taskId = [self identifierForTask:downloadTask]; - int progress = round(totalBytesWritten * 100 / (double)totalBytesExpectedToWrite); + NSString *taskId = [self identifierForTask:downloadTask ofSession: session]; + int progress = round(totalBytesWritten * 100 / (double) totalBytesExpectedToWrite); NSNumber *lastProgress = _runningTaskById[taskId][KEY_PROGRESS]; if (([lastProgress intValue] == 0 || (progress > ([lastProgress intValue] + _step)) || progress == 100) && progress != [lastProgress intValue]) { [self sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_RUNNING) andProgress:@(progress)]; @@ -923,36 +961,35 @@ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTas } } -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location -{ - +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) downloadTask.response; long httpStatusCode = (long)[httpResponse statusCode]; - + if (debug) { NSLog(@"%s HTTP status code: %ld", __FUNCTION__, httpStatusCode); } - + bool isSuccess = (httpStatusCode >= 200 && httpStatusCode < 300); - + if (isSuccess) { NSString *taskId = [self identifierForTask:downloadTask ofSession:session]; NSDictionary *task = [self loadTaskWithId:taskId]; NSURL *destinationURL = [self fileUrlOf:taskId taskInfo:task downloadTask:downloadTask]; - + [_runningTaskById removeObjectForKey:taskId]; - + NSError *error; NSFileManager *fileManager = [NSFileManager defaultManager]; - + if ([fileManager fileExistsAtPath:[destinationURL path]]) { [fileManager removeItemAtURL:destinationURL error:nil]; } - + BOOL success = [fileManager copyItemAtURL:location toURL:destinationURL error:&error]; - + __typeof__(self) __weak weakSelf = self; if (success) { [self sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_COMPLETE) andProgress:@100]; @@ -969,19 +1006,19 @@ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTas }]; } } - + } -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) task.response; - long httpStatusCode = (long)[httpResponse statusCode]; - + long httpStatusCode = (long) [httpResponse statusCode]; + if (debug) { NSLog(@"%s HTTP status code: %ld", __FUNCTION__, httpStatusCode); } - + bool isSuccess = (httpStatusCode >= 200 && httpStatusCode < 300); if (error != nil || !isSuccess) { if (debug) { @@ -1007,8 +1044,7 @@ -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompl } } --(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session -{ +- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { if (debug) { NSLog(@"URLSessionDidFinishEventsForBackgroundURLSession:"); } @@ -1021,7 +1057,33 @@ -(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session if (self.backgroundTransferCompletionHandler != nil) { // Copy locally the completion handler. - void(^completionHandler)(void) = self.backgroundTransferCompletionHandler; + void (^completionHandler)(void) = self.backgroundTransferCompletionHandler; + + // Make nil the backgroundTransferCompletionHandler. + self.backgroundTransferCompletionHandler = nil; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + // Call the completion handler to tell the system that there are no other background transfers. + completionHandler(); + + // Show a local notification when all downloads are over. + UILocalNotification *localNotification = [[UILocalNotification alloc] init]; + localNotification.alertBody = self->_allFilesDownloadedMsg; + [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification]; + }]; + } + } + }]; + + [[self wifiSession] getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { + if ([downloadTasks count] == 0) { + if (debug) { + NSLog(@"all download tasks have been finished"); + } + + if (self.backgroundTransferCompletionHandler != nil) { + // Copy locally the completion handler. + void (^completionHandler)(void) = self.backgroundTransferCompletionHandler; // Make nil the backgroundTransferCompletionHandler. self.backgroundTransferCompletionHandler = nil; @@ -1047,23 +1109,20 @@ - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UID return [UIApplication sharedApplication].delegate.window.rootViewController; } -- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application -{ +- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application { if (debug) { NSLog(@"Send the document to app %@ ...", application); } } -- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application -{ +- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application { if (debug) { NSLog(@"Finished sending the document to app %@ ...", application); } } -- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller -{ +- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller { if (debug) { NSLog(@"Finished previewing the document"); } diff --git a/lib/src/downloader.dart b/lib/src/downloader.dart index 600abfb4..28912344 100644 --- a/lib/src/downloader.dart +++ b/lib/src/downloader.dart @@ -96,6 +96,7 @@ class FlutterDownloader { bool openFileFromNotification = true, bool requiresStorageNotLow = true, bool saveInPublicStorage = false, + bool allowCellular = true, }) async { assert(_initialized, 'plugin flutter_downloader is not initialized'); assert(Directory(savedDir).existsSync(), "savedDir does not exist"); @@ -120,6 +121,7 @@ class FlutterDownloader { 'notification_title': notificationTitle, 'requires_storage_not_low': requiresStorageNotLow, 'save_in_public_storage': saveInPublicStorage, + 'allow_cellular': allowCellular, }); return taskId; } on PlatformException catch (e) { @@ -166,14 +168,14 @@ class FlutterDownloader { /// query: 'SELECT * FROM task WHERE status=3', /// ); /// ``` + static Future?> loadTasksWithRawQuery({ required String query, }) async { assert(_initialized, 'plugin flutter_downloader is not initialized'); try { - List result = await _channel - .invokeMethod('loadTasksWithRawQuery', {'query': query}); + List result = await _channel.invokeMethod('loadTasksWithRawQuery', {'query': query}); return result .map((item) => DownloadTask( taskId: item['task_id'], @@ -372,7 +374,6 @@ class FlutterDownloader { 0 <= step && step <= 100, 'step size is not in the inclusive <0;100> range', ); - _channel.invokeMethod( 'registerCallback', [callbackHandle!.toRawHandle(), step], From 1aec4062b14ce08522f3107e66f95c077972bd10 Mon Sep 17 00:00:00 2001 From: Thomas Fiedler Date: Wed, 25 Aug 2021 09:28:41 +0200 Subject: [PATCH 2/5] Correctly evaluate flag on iOS --- ios/Classes/FlutterDownloaderPlugin.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/Classes/FlutterDownloaderPlugin.m b/ios/Classes/FlutterDownloaderPlugin.m index c9adda9b..5ddb13ba 100644 --- a/ios/Classes/FlutterDownloaderPlugin.m +++ b/ios/Classes/FlutterDownloaderPlugin.m @@ -170,7 +170,7 @@ - (NSURLSession *)wifiSession { return _wifiSession; } -- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url fileName:(NSString *)fileName andSavedDir:(NSString *)savedDir andHeaders:(NSString *)headers wifiOnly:(bool)wifiOnly { +- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url fileName:(NSString *)fileName andSavedDir:(NSString *)savedDir andHeaders:(NSString *)headers allowCellular:(bool)allowCellular { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; if (headers != nil && [headers length] > 0) { NSError *jsonError; @@ -186,10 +186,10 @@ - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url fileName:(NSStrin } } NSURLSessionDownloadTask *task; - if (wifiOnly) { - task = [[self wifiSession] downloadTaskWithRequest:request]; - } else { + if (allowCellular) { task = [[self currentSession] downloadTaskWithRequest:request]; + } else { + task = [[self wifiSession] downloadTaskWithRequest:request]; } // store task id in taskDescription task.taskDescription = [self createTaskId]; @@ -671,7 +671,7 @@ - (void)enqueueMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result NSNumber *openFileFromNotification = call.arguments[KEY_OPEN_FILE_FROM_NOTIFICATION]; bool allowCellular = [call.arguments[KEY_ALLOW_CELLULAR] boolValue]; - NSURLSessionDownloadTask *task = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers wifiOnly:allowCellular]; + NSURLSessionDownloadTask *task = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers allowCellular:allowCellular]; NSString *taskId = allowCellular ? [self identifierForTask:task ofSession:self.currentSession] : [self identifierForTask:task ofSession:self.wifiSession]; @@ -793,7 +793,7 @@ - (void)retryMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *headers = taskDict[KEY_HEADERS]; bool allowCellular = (bool) taskDict[KEY_ALLOW_CELLULAR]; - NSURLSessionDownloadTask *newTask = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers wifiOnly: allowCellular]; + NSURLSessionDownloadTask *newTask = [self downloadTaskWithURL:[NSURL URLWithString:urlString] fileName:fileName andSavedDir:savedDir andHeaders:headers allowCellular:allowCellular]; NSString *newTaskId = [self identifierForTask:newTask ofSession: (allowCellular ? self.currentSession : self.wifiSession)]; // update memory-cache From d1cf83cbabcd430e77039aebac2d7f65468ed5b1 Mon Sep 17 00:00:00 2001 From: Peter Leibiger Date: Fri, 29 Oct 2021 14:20:27 +0200 Subject: [PATCH 3/5] Format dart files and fix rebase mistakes --- .../src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java b/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java index 62b1de53..4fd1b5bb 100644 --- a/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java +++ b/android/src/main/java/vn/hunghd/flutterdownloader/TaskDbHelper.java @@ -28,7 +28,7 @@ public class TaskDbHelper extends SQLiteOpenHelper { TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION + " TINYINT DEFAULT 0, " + TaskEntry.COLUMN_NAME_NOTIFICATION_TITLE + " TEXT, " + TaskEntry.COLUMN_NAME_TIME_CREATED + " INTEGER DEFAULT 0, " + - TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE + " TINYINT DEFAULT 0" + + TaskEntry.COLUMN_SAVE_IN_PUBLIC_STORAGE + " TINYINT DEFAULT 0, " + TaskEntry.COLUMN_NAME_ALLOW_CELLULAR + " INTEGER DEFAULT 0" + ")"; From 14aada4f1f1fdb838bdcd052ac2246db7b6e8b84 Mon Sep 17 00:00:00 2001 From: Thomas Fiedler Date: Mon, 21 Mar 2022 09:38:06 +0100 Subject: [PATCH 4/5] Update schema file on iOS + Add `allow_cellular` column to the prepared database schema --- ios/Assets/download_tasks.sql | Bin 16384 -> 16384 bytes ios/Classes/DBManager.m | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Assets/download_tasks.sql b/ios/Assets/download_tasks.sql index 1c20b143d0657efc443ad95423156ebb565c6bf2..d9fc016d2d559cfd111378a2a07ee8c1fd3f73b6 100644 GIT binary patch delta 97 zcmZo@U~Fh$oFFa8#lXP80>z9#`u;>6W1yg3Z~`yC9|IF_2m{|A-jIz2Pk6YRRlV57 tMMW9gbT^mrwlYfSC?w|O{f~2msLs7!d#f delta 76 zcmZo@U~Fh$oFFa8$-uzC0>z9#`rbqxW1yfOV-zpH9|IF_4g=pG-kgmEPk1ID;*;QN Y=JjG17Zqh}t=$~Q+se54Hy^7Z04O>T`v3p{ diff --git a/ios/Classes/DBManager.m b/ios/Classes/DBManager.m index b73e4b97..efaa5bb8 100644 --- a/ios/Classes/DBManager.m +++ b/ios/Classes/DBManager.m @@ -162,7 +162,7 @@ -(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{ } } else { - // In the database cannot be opened then show the error message on the debugger. + // If the database cannot be opened then show the error message on the debugger. if (debug) { NSLog(@"%s", sqlite3_errmsg(sqlite3Database)); } From b53fac029b2e39683b78510eaf347a4fcf664ba2 Mon Sep 17 00:00:00 2001 From: Thomas Fiedler Date: Wed, 10 Aug 2022 10:06:04 +0200 Subject: [PATCH 5/5] Fix rebase mistakes on iOS --- example/lib/main.dart | 1 - ios/Classes/FlutterDownloaderPlugin.m | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 29c1e56c..1cbf9969 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:flutter_downloader_example/home_page.dart'; void main() async { diff --git a/ios/Classes/FlutterDownloaderPlugin.m b/ios/Classes/FlutterDownloaderPlugin.m index 5ddb13ba..78bd2499 100644 --- a/ios/Classes/FlutterDownloaderPlugin.m +++ b/ios/Classes/FlutterDownloaderPlugin.m @@ -35,8 +35,6 @@ @interface FlutterDownloaderPlugin() *_registrar; - NSURLSession *_session; - NSURLSession *_wifiSession; DBManager *_dbManager; NSString *_allFilesDownloadedMsg; NSMutableArray *_eventQueue; @@ -55,6 +53,7 @@ @implementation FlutterDownloaderPlugin static BOOL initialized = NO; static BOOL debug = YES; static NSURLSession *_session = nil; +static NSURLSession *_wifiSession = nil; static FlutterEngine *_headlessRunner = nil; static int64_t _callbackHandle = 0; static int _step = 10; @@ -198,15 +197,11 @@ - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url fileName:(NSStrin return task; } -//- (NSString*) createTaskId { +- (NSString*) createTaskId { return [NSString stringWithFormat:@"%@.download.task.%d.%f", NSBundle.mainBundle.bundleIdentifier, arc4random_uniform(100000), [[NSDate date] timeIntervalSince1970]]; } -- (NSString *)identifierForTask:(NSURLSessionTask *)task { -// return task.taskDescription; -//} - - (NSString *)identifierForTask:(NSURLSessionTask *)task ofSession:(NSURLSession *)session { return task.taskDescription; } @@ -300,7 +295,7 @@ - (void)cancelAllTasks { NSURLSessionTaskState state = download.state; if (state == NSURLSessionTaskStateRunning) { [download cancel]; - NSString *taskId = [self identifierForTask:download]; + NSString *taskId = download.taskDescription; [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_CANCELED) andProgress:@(-1)]; [weakSelf executeInDatabaseQueueForTask:^{ [weakSelf updateTask:taskId status:STATUS_CANCELED progress:-1]; @@ -313,7 +308,7 @@ - (void)cancelAllTasks { NSURLSessionTaskState state = download.state; if (state == NSURLSessionTaskStateRunning) { [download cancel]; - NSString *taskId = [self identifierForTask:download]; + NSString *taskId = download.taskDescription; [weakSelf sendUpdateProgressForTaskId:taskId inStatus:@(STATUS_CANCELED) andProgress:@(-1)]; [weakSelf executeInDatabaseQueueForTask:^{ [weakSelf updateTask:taskId status:STATUS_CANCELED progress:-1];