Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

911 #912

Merged
merged 2 commits into from
Nov 21, 2023
Merged

911 #912

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 2 additions & 39 deletions example/lib/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:io';
import 'dart:isolate';
import 'dart:ui';

import 'package:android_path_provider/android_path_provider.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
Expand Down Expand Up @@ -363,48 +362,12 @@ class _MyHomePageState extends State<MyHomePage> {

Future<String?> _getSavedDir() async {
String? externalStorageDirPath;

if (Platform.isAndroid) {
try {
externalStorageDirPath = await AndroidPathProvider.downloadsPath;
} catch (err, st) {
print('failed to get downloads path: $err, $st');

final directory = await getExternalStorageDirectory();
externalStorageDirPath = directory?.path;
}
} else if (Platform.isIOS) {
// var dir = (await _dirsOnIOS)[0]; // temporary
// var dir = (await _dirsOnIOS)[1]; // applicationSupport
// var dir = (await _dirsOnIOS)[2]; // library
var dir = (await _dirsOnIOS)[3]; // applicationDocuments
// var dir = (await _dirsOnIOS)[4]; // downloads

dir ??= await getApplicationDocumentsDirectory();
externalStorageDirPath = dir.absolute.path;
}
externalStorageDirPath =
(await getApplicationDocumentsDirectory()).absolute.path;

return externalStorageDirPath;
}

Future<List<Directory?>> get _dirsOnIOS async {
final temporary = await getTemporaryDirectory();
final applicationSupport = await getApplicationSupportDirectory();
final library = await getLibraryDirectory();
final applicationDocuments = await getApplicationDocumentsDirectory();
final downloads = await getDownloadsDirectory();

final dirs = [
temporary,
applicationSupport,
library,
applicationDocuments,
downloads
];

return dirs;
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand Down
4 changes: 0 additions & 4 deletions ios/Classes/DBManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@

-(NSArray *)loadDataFromDB:(NSString *)query withParameters:(NSArray *)parameters;

- (void)addLazilyColumnForTable:(const char *)table
column:(const char *)column
type:(const char *)type
defaultValue:(const char *)defaultValue;

-(void)executeQuery:(NSString *)query withParameters:(NSArray *)parameters;

Expand Down
71 changes: 0 additions & 71 deletions ios/Classes/DBManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -83,77 +83,6 @@ -(void)copyDatabaseIntoAppDirectory{
}
}

- (void)addLazilyColumnForTable:(const char *)table
column:(const char *)column
type:(const char *)type
defaultValue:(const char *)defaultValue {

sqlite3 *sqlite3Database;
NSString *databasePath = [self.appDirectory stringByAppendingPathComponent:self.databaseFilename];

if (debug) {
NSLog(@"databasePath: %@", databasePath);
}

int openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database);

if(openDatabaseResult != SQLITE_OK) {
if(debug) {
NSLog(@"error opening the database with error no.: %d", openDatabaseResult);
}

return;
}

if (debug) {
NSLog(@"open DB successfully");
}

sqlite3_stmt *compiledStatement;

const char *prefix = "select * from ";
char *select = (char *)malloc(strlen(prefix) + strlen(table) + 1);
sprintf(select, "%s%s", prefix, table);
int prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, (const char *)select, -1, &compiledStatement, NULL);
if(prepareStatementResult == SQLITE_OK) {
int isExistTargetColumn = -1;
int totalColumns = sqlite3_column_count(compiledStatement);
for (int i = 0; i < totalColumns; i++) {
char *dbDataAsChars = (char *)sqlite3_column_text(compiledStatement, i);
dbDataAsChars = (char *)sqlite3_column_name(compiledStatement, i);
if (strcmp(dbDataAsChars, column) == 0) {
isExistTargetColumn = 0;
break;
}
}

if (isExistTargetColumn == -1) {
const char *component = "alter table";
const char *component1 = "add column";
const char *component2 = "default";
char *insert = (char *)malloc(strlen(component) + strlen(table) + strlen(component1) + strlen(column) + strlen(type) + strlen(column) + strlen(component2) + strlen(defaultValue) + 6 + 1);
sprintf(insert, "%s %s %s %s %s %s %s", component, table, component1, column, type, component2, defaultValue);
int execResult = sqlite3_exec(sqlite3Database, insert, NULL, NULL, NULL);
if (execResult != SQLITE_OK) {
if (debug) {
fprintf(stderr, "sqlite3_exec error :%s\r\n", sqlite3_errmsg(sqlite3Database));
}
}
} else {
if (debug) {
NSLog(@"DB table %s column %s already exists", table, column);
}
}
} else {
if (debug) {
NSLog(@"%s", sqlite3_errmsg(sqlite3Database));
}
}

sqlite3_finalize(compiledStatement);
sqlite3_close(sqlite3Database);
}


- (void)runQuery:(const char *)query withParameters:(NSArray *)parameters isQueryExecutable:(BOOL)queryExecutable {

Expand Down
122 changes: 21 additions & 101 deletions ios/Classes/FlutterDownloaderPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

#define KEY_URL @"url"
#define KEY_SAVED_DIR @"saved_dir"
#define KEY_SEARCH_DIR @"search_dir"

#define KEY_FILE_NAME @"file_name"
#define KEY_PROGRESS @"progress"
#define KEY_ID @"id"
Expand Down Expand Up @@ -58,8 +58,6 @@ @implementation FlutterDownloaderPlugin
static int _step = 10;
static NSMutableDictionary<NSString*, NSMutableDictionary*> *_runningTaskById = nil;

static NSSearchPathDirectory const kDefaultSearchPathDirectory = NSDocumentDirectory;

@synthesize databaseQueue;

- (instancetype)init:(NSObject<FlutterPluginRegistrar> *)registrar;
Expand Down Expand Up @@ -98,11 +96,6 @@ - (instancetype)init:(NSObject<FlutterPluginRegistrar> *)registrar;

_dbManager = [[DBManager alloc] initWithDatabaseFilePath:dbPath];

__typeof__(self) __weak weakSelf = self;
[self executeInDatabaseQueueForTask:^{
[weakSelf addDatabaseColumnForMakingFileCouldSaveInAnyDirectory];
}];

if (_runningTaskById == nil) {
_runningTaskById = [[NSMutableDictionary alloc] init];
}
Expand Down Expand Up @@ -315,13 +308,6 @@ - (void)executeInDatabaseQueueForTask:(void (^)(void))task {
});
}

+ (NSArray<NSNumber *> *)avaliableCommonDirectories {
return @[@(NSCachesDirectory),
@(NSApplicationSupportDirectory),
@(NSLibraryDirectory),
@(kDefaultSearchPathDirectory),
@(NSDownloadsDirectory)];;
}

- (BOOL)openDocumentWithURL:(NSURL*)url {
if (debug) {
Expand Down Expand Up @@ -382,31 +368,10 @@ - (NSURL*)fileUrlOf:(NSString*)taskId taskInfo:(NSDictionary*)taskInfo downloadT
return [self fileUrlFromDict:mutableTaskInfo];
}

- (NSString*)absoluteSavedDirPathWithShortSavedDir:(NSString*)shortSavedDir searchPathDirectory:(NSSearchPathDirectory)searchPathDirectory {
return [[NSSearchPathForDirectoriesInDomains(searchPathDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:shortSavedDir];
}

- (NSString *)sanitizeFilename:(nullable NSString *)filename {
// Define a list of allowed characters for filenames
NSMutableCharacterSet *allowedCharacters = [[NSMutableCharacterSet alloc] init];

// Allow alphabetical characters (lowercase and uppercase)
[allowedCharacters formUnionWithCharacterSet:[NSCharacterSet letterCharacterSet]];

// Allow digits
[allowedCharacters addCharactersInRange:NSMakeRange('0', 10)]; // ASCII digits

// Allow additional characters: -_.()
[allowedCharacters addCharactersInString:@"-_.()"];
NSCharacterSet *allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.() "] invertedSet];

// Allow empty spaces
[allowedCharacters addCharactersInString:@" "];

// Remove the backslash (if you want to disallow it)
[allowedCharacters removeCharactersInString:@"\\"];

// Now, you have a character set that allows the specified characters
NSCharacterSet *finalCharacterSet = [allowedCharacters copy];
if (filename == nil || [filename isEqual:[NSNull null]] || [filename isEqualToString:@""]) {
NSString *defaultFilename = @"default_filename";
return defaultFilename;
Expand Down Expand Up @@ -439,39 +404,30 @@ - (NSString *)sanitizeFilename:(nullable NSString *)filename {
}


- (NSString*)absoluteSavedDirPath:(NSString*)savedDir {
return [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:savedDir];
}

- (NSArray *)shortenSavedDirPath:(NSString*)absolutePath {
- (NSString*)shortenSavedDirPath:(NSString*)absolutePath {
if (debug) {
NSLog(@"Absolute savedDir path: %@", absolutePath);
}

for (NSNumber *element in self.class.avaliableCommonDirectories) {
NSString *shortSvedDirPath = [self shortenSavedDirPath:absolutePath searchPathDirectory:element.unsignedIntegerValue];
if (shortSvedDirPath) {
return @[shortSvedDirPath, element];
}
}

return @[@"", @(kDefaultSearchPathDirectory)];
}

- (NSString*)shortenSavedDirPath:(NSString*)absolutePath searchPathDirectory:(NSSearchPathDirectory)searchPathDirectory {
if (absolutePath) {
NSString *searchDirPath = [NSSearchPathForDirectoriesInDomains(searchPathDirectory, NSUserDomainMask, YES) firstObject];
if ([absolutePath isEqualToString:searchDirPath]) {
NSString* documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
if ([absolutePath isEqualToString:documentDirPath]) {
return @"";
}
NSRange foundRank = [absolutePath rangeOfString:searchDirPath];
NSRange foundRank = [absolutePath rangeOfString:documentDirPath];
if (foundRank.length > 0) {
// we increase the location of range by one because we want to remove the file separator as well.
NSString *shortenSavedDirPath = [absolutePath substringWithRange:NSMakeRange(foundRank.length + 1, absolutePath.length - searchDirPath.length - 1)];
NSString *shortenSavedDirPath = [absolutePath substringWithRange:NSMakeRange(foundRank.length + 1, absolutePath.length - documentDirPath.length - 1)];
return shortenSavedDirPath != nil ? shortenSavedDirPath : @"";
}
}

return nil;
}

return absolutePath;
}

- (long long)currentTimeInMilliseconds
{
Expand All @@ -480,27 +436,7 @@ - (long long)currentTimeInMilliseconds

# pragma mark - Database Accessing

/// Before version 1.11.1, FlutterDownloader only allows file to be saved in [NSDocumentDirectory]. This limits the freedom of development.
///
/// This function serves two purposes:
///
/// 1. Add a database column `search_dir` for determining common root directory such as the flowing directories
///
/// - NSCachesDirectory
/// - NSApplicationSupportDirectory
/// - NSLibraryDirectory
/// - NSDocumentDirectory
/// - NSDownloadsDirectory
///
/// Definition of common root directory refers to [path_provider](https://github.com/flutter/packages/blob/main/packages/path_provider/path_provider/lib/path_provider.dart).
///
/// 2. Resolve previous compatibility issue
- (void)addDatabaseColumnForMakingFileCouldSaveInAnyDirectory {
[_dbManager addLazilyColumnForTable:"task"
column:KEY_SEARCH_DIR.UTF8String
type:"integer"
defaultValue:[NSString stringWithFormat:@"%lu", kDefaultSearchPathDirectory].UTF8String]; // kDefaultSearchPathDirectory is [NSDocumentDirectory](9), this is compatible with previous FlutterDownloader versions.
}

- (NSString*) escape:(NSString*) origin revert:(BOOL)revert
{
if ( origin == (NSString *)[NSNull null] )
Expand All @@ -519,18 +455,16 @@ - (void)addNewTask:(NSString *)taskId
progress:(int)progress
filename:(NSString *)filename
savedDir:(NSString *)savedDir
searchDir:(NSSearchPathDirectory)searchDir
headers:(NSString *)headers
resumable:(BOOL)resumable
showNotification:(BOOL)showNotification
openFileFromNotification:(BOOL)openFileFromNotification {

headers = [self escape:headers revert:NO];

NSString *query = @"INSERT INTO task (task_id, url, status, progress, file_name, saved_dir, search_dir, headers, resumable, show_notification, open_file_from_notification, time_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
NSNumber *searchDirValue = @(searchDir);
NSString *query = @"INSERT INTO task (task_id, url, status, progress, file_name, saved_dir, headers, resumable, show_notification, open_file_from_notification, time_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
NSString *sanitizedFileName = [self sanitizeFilename:filename];
NSArray *values = @[taskId, url, @(status), @(progress), sanitizedFileName, savedDir, searchDirValue, headers, @(resumable ? 1:0), @(showNotification ? 1 : 0), @(openFileFromNotification ? 1: 0), @([self currentTimeInMilliseconds])];
NSArray *values = @[taskId, url, @(status), @(progress), sanitizedFileName, savedDir, headers, @(resumable ? 1:0), @(showNotification ? 1 : 0), @(openFileFromNotification ? 1: 0), @([self currentTimeInMilliseconds])];

[_dbManager executeQuery:query withParameters:values];

Expand Down Expand Up @@ -723,16 +657,9 @@ - (NSDictionary*) taskDictFromRecordArray:(NSArray*)record
int progress = [[record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"progress"]] intValue];
NSString *url = [record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"url"]];
NSString *filename = [record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"file_name"]];
NSString *shortSavedDir = [record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"saved_dir"]];

NSString *searchDirStr = [record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:KEY_SEARCH_DIR]];
int searchDir = [searchDirStr intValue];
NSNumber *searchDirNum = [NSNumber numberWithInt:searchDir];

NSString *savedDir = [self absoluteSavedDirPathWithShortSavedDir:shortSavedDir searchPathDirectory:searchDir];

NSString *savedDir = [self absoluteSavedDirPath:[record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"saved_dir"]]];
NSString *headers = @"";
// in certain cases, headers column might not be available and will cause NSRangeException
// in certain cases, headers column might not be available and will cause NSRangeException
@try {
NSString *rawHeaders = [record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"headers"]];
headers = [self escape:rawHeaders revert:true];
Expand All @@ -743,7 +670,7 @@ - (NSDictionary*) taskDictFromRecordArray:(NSArray*)record
int showNotification = [[record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"show_notification"]] intValue];
int openFileFromNotification = [[record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"open_file_from_notification"]] intValue];
long long timeCreated = [[record objectAtIndex:[_dbManager.arrColumnNames indexOfObject:@"time_created"]] longLongValue];
return [NSDictionary dictionaryWithObjectsAndKeys:taskId, KEY_TASK_ID, @(status), KEY_STATUS, @(progress), KEY_PROGRESS, url, KEY_URL, filename, KEY_FILE_NAME, headers, KEY_HEADERS, savedDir, KEY_SAVED_DIR, searchDirNum, KEY_SEARCH_DIR, [NSNumber numberWithBool:(resumable == 1)], KEY_RESUMABLE, [NSNumber numberWithBool:(showNotification == 1)], KEY_SHOW_NOTIFICATION, [NSNumber numberWithBool:(openFileFromNotification == 1)], KEY_OPEN_FILE_FROM_NOTIFICATION, @(timeCreated), KEY_TIME_CREATED, nil];
return [NSDictionary dictionaryWithObjectsAndKeys:taskId, KEY_TASK_ID, @(status), KEY_STATUS, @(progress), KEY_PROGRESS, url, KEY_URL, filename, KEY_FILE_NAME, headers, KEY_HEADERS, savedDir, KEY_SAVED_DIR, [NSNumber numberWithBool:(resumable == 1)], KEY_RESUMABLE, [NSNumber numberWithBool:(showNotification == 1)], KEY_SHOW_NOTIFICATION, [NSNumber numberWithBool:(openFileFromNotification == 1)], KEY_OPEN_FILE_FROM_NOTIFICATION, @(timeCreated), KEY_TIME_CREATED, nil];
} @catch(NSException *exception) {
NSLog(@"invalid task data: %@", exception);
return [NSDictionary dictionary];
Expand Down Expand Up @@ -789,14 +716,8 @@ - (void) unqueueStatusEvents {

- (void)enqueueMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
NSString *urlString = call.arguments[KEY_URL];

NSString *savedDirFromSource = call.arguments[KEY_SAVED_DIR];
NSArray *shortSavedDirArgs = [self shortenSavedDirPath:savedDirFromSource];
NSString *shortSavedDir = shortSavedDirArgs[0];
NSNumber *searchDirNum = shortSavedDirArgs[1];
NSSearchPathDirectory searchDir = searchDirNum.unsignedIntegerValue;
NSString *savedDir = [self absoluteSavedDirPathWithShortSavedDir:shortSavedDir searchPathDirectory:searchDir];

NSString *savedDir = call.arguments[KEY_SAVED_DIR];
NSString *shortSavedDir = [self shortenSavedDirPath:savedDir];
NSString *fileName = call.arguments[KEY_FILE_NAME];
NSString *headers = call.arguments[KEY_HEADERS];
NSNumber *showNotification = call.arguments[KEY_SHOW_NOTIFICATION];
Expand All @@ -810,7 +731,6 @@ - (void)enqueueMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result
urlString, KEY_URL,
fileName, KEY_FILE_NAME,
savedDir, KEY_SAVED_DIR,
searchDirNum, KEY_SEARCH_DIR,
headers, KEY_HEADERS,
showNotification, KEY_SHOW_NOTIFICATION,
openFileFromNotification, KEY_OPEN_FILE_FROM_NOTIFICATION,
Expand All @@ -822,7 +742,7 @@ - (void)enqueueMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result
__typeof__(self) __weak weakSelf = self;

[self executeInDatabaseQueueForTask:^{
[weakSelf addNewTask:taskId url:urlString status:STATUS_ENQUEUED progress:0 filename:fileName savedDir:shortSavedDir searchDir:searchDir 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];
Expand Down