Browse Source

Rewrote VLCHTTPFileDownloader

Signed-off-by: Felix Paul Kühne <felix@feepk.net>
Justin Anderson 6 years ago
parent
commit
ff9a2c275e

+ 0 - 2
Resources/en.lproj/Localizable.strings

@@ -89,8 +89,6 @@
 "SMB_CIFS_FILE_SERVERS_SHORT" = "SMB";
 "SMB_CIFS_FILE_SERVERS_SHORT" = "SMB";
 
 
 
 
-"HTTP_DOWNLOAD_FAILED" = "Download failed with HTTP code %i";
-"HTTP_FILE_CREATION_FAILED" = "File creation failed";
 "HTTP_DOWNLOAD_CANCELLED" = "Download canceled by user";
 "HTTP_DOWNLOAD_CANCELLED" = "Download canceled by user";
 
 
 "DOWNLOAD_FROM_HTTP_HELP" = "Enter an address to download the file to your %@.";
 "DOWNLOAD_FROM_HTTP_HELP" = "Enter an address to download the file to your %@.";

+ 16 - 13
Sources/VLCDownloadViewController.m

@@ -33,6 +33,7 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
     NSString *_humanReadableFilename;
     NSString *_humanReadableFilename;
     NSMutableArray *_currentDownloadFilename;
     NSMutableArray *_currentDownloadFilename;
     NSTimeInterval _startDL;
     NSTimeInterval _startDL;
+    NSString *_currentDownloadIdentifier;
 
 
     VLCHTTPFileDownloader *_httpDownloader;
     VLCHTTPFileDownloader *_httpDownloader;
 
 
@@ -168,7 +169,7 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
 
 
 - (void)_updateUI
 - (void)_updateUI
 {
 {
-    _currentDownloadType != VLCDownloadSchemeNone ? [self downloadStarted] : [self downloadEnded];
+    _currentDownloadType != VLCDownloadSchemeNone ? [self downloadStartedWithIdentifier:nil] : [self downloadEndedWithIdentifier:nil];
     [self.downloadsTable reloadData];
     [self.downloadsTable reloadData];
 }
 }
 
 
@@ -203,16 +204,16 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
 
 
 - (void)_downloadSchemeHttp
 - (void)_downloadSchemeHttp
 {
 {
-    if (self.httpDownloader.downloadInProgress) {
+    if (_currentDownloadIdentifier) {
         return;
         return;
     }
     }
     _currentDownloadType = VLCDownloadSchemeHTTP;
     _currentDownloadType = VLCDownloadSchemeHTTP;
     if (![_currentDownloadFilename.firstObject isEqualToString:@""]) {
     if (![_currentDownloadFilename.firstObject isEqualToString:@""]) {
         _humanReadableFilename = [[_currentDownloadFilename firstObject] stringByRemovingPercentEncoding];
         _humanReadableFilename = [[_currentDownloadFilename firstObject] stringByRemovingPercentEncoding];
-        [self.httpDownloader downloadFileFromURL:_currentDownloads.firstObject withFileName:_humanReadableFilename];
+        _currentDownloadIdentifier = [self.httpDownloader downloadFileFromURL:_currentDownloads.firstObject withFileName:_humanReadableFilename];
     } else {
     } else {
-        [self.httpDownloader downloadFileFromURL:_currentDownloads.firstObject];
-        _humanReadableFilename = self.httpDownloader.userReadableDownloadName;
+        _currentDownloadIdentifier = [self.httpDownloader downloadFileFromURL:_currentDownloads.firstObject];
+        _humanReadableFilename = [_currentDownloads.firstObject lastPathComponent];
     }
     }
     [self _startDownload];
     [self _startDownload];
 }
 }
@@ -273,7 +274,7 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
 - (IBAction)cancelDownload:(id)sender
 - (IBAction)cancelDownload:(id)sender
 {
 {
     if (_currentDownloadType == VLCDownloadSchemeHTTP && self.httpDownloader.downloadInProgress) {
     if (_currentDownloadType == VLCDownloadSchemeHTTP && self.httpDownloader.downloadInProgress) {
-        [self.httpDownloader cancelDownload];
+        [self.httpDownloader cancelDownloadWithIdentifier:_currentDownloadIdentifier];
     } else if (_currentDownloadType == VLCDownloadSchemeFTP && _FTPDownloadRequest) {
     } else if (_currentDownloadType == VLCDownloadSchemeFTP && _FTPDownloadRequest) {
         NSURL *target = _FTPDownloadRequest.downloadLocation;
         NSURL *target = _FTPDownloadRequest.downloadLocation;
         [_FTPDownloadRequest destroy];
         [_FTPDownloadRequest destroy];
@@ -286,8 +287,9 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
 
 
 #pragma mark - VLC HTTP Downloader delegate
 #pragma mark - VLC HTTP Downloader delegate
 
 
-- (void)downloadStarted
+- (void)downloadStartedWithIdentifier:(NSString *)identifier
 {
 {
+    _currentDownloadIdentifier = identifier;
     [self.activityIndicator stopAnimating];
     [self.activityIndicator stopAnimating];
 
 
     VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
     VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
@@ -305,8 +307,9 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
     APLog(@"download started");
     APLog(@"download started");
 }
 }
 
 
-- (void)downloadEnded
+- (void)downloadEndedWithIdentifier:(NSString *)identifier
 {
 {
+    _currentDownloadIdentifier = nil;
     [[VLCActivityManager defaultManager] networkActivityStopped];
     [[VLCActivityManager defaultManager] networkActivityStopped];
     _currentDownloadType = VLCDownloadSchemeNone;
     _currentDownloadType = VLCDownloadSchemeNone;
     APLog(@"download ended");
     APLog(@"download ended");
@@ -315,14 +318,14 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
     [self _triggerNextDownload];
     [self _triggerNextDownload];
 }
 }
 
 
-- (void)downloadFailedWithErrorDescription:(NSString *)description
+- (void)downloadFailedWithIdentifier:(NSString *)identifier errorDescription:(NSString *)description
 {
 {
     [VLCAlertViewController alertViewManagerWithTitle:NSLocalizedString(@"DOWNLOAD_FAILED", nil)
     [VLCAlertViewController alertViewManagerWithTitle:NSLocalizedString(@"DOWNLOAD_FAILED", nil)
                                          errorMessage:description
                                          errorMessage:description
                                        viewController:self];
                                        viewController:self];
 }
 }
 
 
-- (void)progressUpdatedTo:(CGFloat)percentage receivedDataSize:(CGFloat)receivedDataSize  expectedDownloadSize:(CGFloat)expectedDownloadSize
+- (void)progressUpdatedTo:(CGFloat)percentage receivedDataSize:(CGFloat)receivedDataSize  expectedDownloadSize:(CGFloat)expectedDownloadSize identifier:(NSString *)identifier
 {
 {
     if ((_lastStatsUpdate > 0 && ([NSDate timeIntervalSinceReferenceDate] - _lastStatsUpdate > .5)) || _lastStatsUpdate <= 0) {
     if ((_lastStatsUpdate > 0 && ([NSDate timeIntervalSinceReferenceDate] - _lastStatsUpdate > .5)) || _lastStatsUpdate <= 0) {
         [self.progressPercent setText:[NSString stringWithFormat:@"%.1f%%", percentage*100]];
         [self.progressPercent setText:[NSString stringWithFormat:@"%.1f%%", percentage*100]];
@@ -379,19 +382,19 @@ typedef NS_ENUM(NSUInteger, VLCDownloadScheme) {
 
 
 - (void)requestStarted:(WRRequest *)request
 - (void)requestStarted:(WRRequest *)request
 {
 {
-    [self downloadStarted];
+    [self downloadStartedWithIdentifier:request.fullURLString];
 }
 }
 
 
 - (void)requestCompleted:(WRRequest *)request
 - (void)requestCompleted:(WRRequest *)request
 {
 {
     _FTPDownloadRequest = nil;
     _FTPDownloadRequest = nil;
-    [self downloadEnded];
+    [self downloadEndedWithIdentifier:request.fullURLString];
 }
 }
 
 
 - (void)requestFailed:(WRRequest *)request
 - (void)requestFailed:(WRRequest *)request
 {
 {
     _FTPDownloadRequest = nil;
     _FTPDownloadRequest = nil;
-    [self downloadEnded];
+    [self downloadEndedWithIdentifier:request.fullURLString];
     [VLCAlertViewController alertViewManagerWithTitle:[NSString stringWithFormat:NSLocalizedString(@"ERROR_NUMBER", nil), request.error.errorCode]
     [VLCAlertViewController alertViewManagerWithTitle:[NSString stringWithFormat:NSLocalizedString(@"ERROR_NUMBER", nil), request.error.errorCode]
                                          errorMessage:request.error.message
                                          errorMessage:request.error.message
                                        viewController:self];
                                        viewController:self];

+ 7 - 9
Sources/VLCHTTPFileDownloader.h

@@ -13,24 +13,22 @@
 
 
 @protocol VLCHTTPFileDownloader <NSObject>
 @protocol VLCHTTPFileDownloader <NSObject>
 @required
 @required
-- (void)downloadStarted;
-- (void)downloadEnded;
+- (void)downloadStartedWithIdentifier:(NSString *)identifier;
+- (void)downloadEndedWithIdentifier:(NSString *)identifier;
 
 
 @optional
 @optional
-- (void)downloadFailedWithErrorDescription:(NSString *)description;
-- (void)progressUpdatedTo:(CGFloat)percentage receivedDataSize:(CGFloat)receivedDataSize  expectedDownloadSize:(CGFloat)expectedDownloadSize;
+- (void)downloadFailedWithIdentifier:(NSString *)identifier errorDescription:(NSString *)description;
+- (void)progressUpdatedTo:(CGFloat)percentage receivedDataSize:(CGFloat)receivedDataSize  expectedDownloadSize:(CGFloat)expectedDownloadSize identifier:(NSString *)identifier;
 
 
 @end
 @end
 
 
 @interface VLCHTTPFileDownloader : NSObject
 @interface VLCHTTPFileDownloader : NSObject
 
 
-@property (readonly, nonatomic) NSString *userReadableDownloadName;
-
 @property (nonatomic, readonly) BOOL downloadInProgress;
 @property (nonatomic, readonly) BOOL downloadInProgress;
 @property (nonatomic, retain) id delegate;
 @property (nonatomic, retain) id delegate;
 
 
-- (void)cancelDownload;
-- (void)downloadFileFromURL:(NSURL *)url;
-- (void)downloadFileFromURL:(NSURL *)url withFileName:(NSString*) fileName;
+- (void)cancelDownloadWithIdentifier:(NSString *)identifier;
+- (NSString *)downloadFileFromURL:(NSURL *)url;
+- (NSString *)downloadFileFromURL:(NSURL *)url withFileName:(NSString*)fileName;
 
 
 @end
 @end

+ 152 - 127
Sources/VLCHTTPFileDownloader.m

@@ -18,29 +18,47 @@
 #import "VLCMediaFileDiscoverer.h"
 #import "VLCMediaFileDiscoverer.h"
 #import "VLC-Swift.h"
 #import "VLC-Swift.h"
 
 
-@interface VLCHTTPFileDownloader () <NSURLSessionDelegate>
+@interface VLCHTTPFileDownloaderTask: NSObject
+@property (nonatomic) NSURLSessionTask *sessionTask;
+@property (nonatomic, copy) NSURL *url;
+@property (nonatomic, copy) NSString *fileName;
+@property (nonatomic, copy) NSURL *fileURL;
+@end
+
+@implementation VLCHTTPFileDownloaderTask
+
+- (NSMutableURLRequest *)buildRequest
 {
 {
-    NSString *_filePath;
-    long long _expectedDownloadSize;
-    NSUInteger _receivedDataSize;
-    NSString *_fileName;
-    NSURLSessionTask *_sessionTask;
-    NSMutableURLRequest *_originalRequest;
-    NSUInteger _statusCode;
+    NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:self.url];
+    [theRequest addValue:[NSString stringWithFormat:@"Mozilla/5.0 (%@; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/%@ Safari/9537.53 VLC for iOS/%@", UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? @"iPad" : @"iPhone", [[UIDevice currentDevice] systemVersion], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]] forHTTPHeaderField:@"User-Agent"];
+    return theRequest;
 }
 }
+@end
+
+@interface VLCHTTPFileDownloader () <NSURLSessionDelegate>
 
 
+@property (nonatomic) NSURLSession *urlSession;
+@property (nonatomic) NSMutableDictionary *downloads;
+@property (nonatomic) dispatch_queue_t downloadsAccessQueue;
 @end
 @end
 
 
 @implementation VLCHTTPFileDownloader
 @implementation VLCHTTPFileDownloader
 
 
-- (NSString *)userReadableDownloadName
+- (instancetype)init
 {
 {
-    return _fileName;
+    if (self = [super init]) {
+        _urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
+                                                    delegate:self
+                                               delegateQueue:nil];
+        _downloads = [[NSMutableDictionary alloc] init];
+        _downloadsAccessQueue = dispatch_queue_create("VLCHTTPFileDownloader.downloadsQueue", DISPATCH_QUEUE_SERIAL);
+    }
+    return self;
 }
 }
 
 
-- (void)downloadFileFromURL:(NSURL *)url
+- (NSString *)downloadFileFromURL:(NSURL *)url
 {
 {
-    [self downloadFileFromURL:url withFileName:nil];
+    return [self downloadFileFromURL:url withFileName:nil];
 }
 }
 
 
 - (NSString *)createPotentialNameFromName:(NSString *)name
 - (NSString *)createPotentialNameFromName:(NSString *)name
@@ -75,120 +93,94 @@
     return path;
     return path;
 }
 }
 
 
-- (void)downloadFileFromURL:(NSURL *)url withFileName:(NSString*)fileName
+- (NSString *)downloadFileFromURL:(NSURL *)url withFileName:(NSString*)fileName
 {
 {
-    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
-    NSString *basePath = [searchPaths.firstObject stringByAppendingPathComponent:@"Upload"];
+    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString *libraryPath = [searchPaths firstObject];
 
 
-    if (fileName)
-        _fileName = [self createPotentialNameFromName:fileName];
-    else
-        _fileName = [url.lastPathComponent stringByRemovingPercentEncoding];
+    VLCHTTPFileDownloaderTask *downloadTask = [[VLCHTTPFileDownloaderTask alloc] init];
+    downloadTask.url = url;
+    NSString *downloadFileName;
+    if (fileName) {
+        downloadFileName = [self createPotentialNameFromName:fileName];
+    } else {
+        downloadFileName = [url.lastPathComponent stringByRemovingPercentEncoding];
+    }
 
 
-    if (_fileName.pathExtension.length == 0 || ![_fileName isSupportedFormat]) {
-        _fileName = [_fileName stringByAppendingPathExtension:@"vlc"];
+    if (downloadFileName.pathExtension.length == 0 || ![downloadFileName isSupportedFormat]) {
+        NSString *urlExtension = url.pathExtension;
+        NSString *extension = urlExtension.length != 0 ? urlExtension : @"vlc";
+        downloadFileName = [fileName stringByAppendingPathExtension:extension];
     }
     }
+    downloadTask.fileName = downloadFileName;
+    downloadTask.fileURL = [NSURL fileURLWithPath:[libraryPath stringByAppendingPathComponent:downloadFileName]];
 
 
-    _filePath = [basePath stringByAppendingPathComponent:_fileName];
-    NSFileManager *fileManager = [NSFileManager defaultManager];
-    if (![fileManager fileExistsAtPath:basePath])
-        [fileManager createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];
-    _expectedDownloadSize = _receivedDataSize = 0;
-    NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
-    [theRequest addValue:[NSString stringWithFormat:@"Mozilla/5.0 (%@; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/%@ Mobile/11A465 Safari/9537.53 VLC for iOS/%@", UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? @"iPad" : @"iPhone", [[UIDevice currentDevice] systemVersion], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]] forHTTPHeaderField:@"User-Agent"];
-    _originalRequest = [theRequest mutableCopy];
-
-    NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
-    _sessionTask = [urlSession dataTaskWithRequest:theRequest];
-    [_sessionTask resume];
-    if (!_sessionTask) {
+    NSString *identifier = [[NSUUID UUID] UUIDString];
+
+    NSURLSessionTask *sessionTask = [self.urlSession downloadTaskWithRequest:[downloadTask buildRequest]];
+    sessionTask.taskDescription = identifier;
+    [sessionTask resume];
+
+    if (!sessionTask) {
         APLog(@"failed to establish connection");
         APLog(@"failed to establish connection");
-        _downloadInProgress = NO;
+        return nil;
     } else {
     } else {
-        _downloadInProgress = YES;
         VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
         VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
         [activityManager networkActivityStarted];
         [activityManager networkActivityStarted];
         [activityManager disableIdleTimer];
         [activityManager disableIdleTimer];
     }
     }
-}
 
 
-- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
-{
-    if (redirectResponse) {
-        NSURL *URL = [request URL];
-
-        NSFileManager *fileManager = [NSFileManager defaultManager];
-
-        if ([fileManager fileExistsAtPath:_filePath])
-            [fileManager removeItemAtPath:_filePath error:nil];
-
-        NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
-        NSString *basePath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
-        _fileName = [[URL lastPathComponent] stringByRemovingPercentEncoding];
-        _filePath = [basePath stringByAppendingPathComponent:_fileName];
-        if (![fileManager fileExistsAtPath:basePath])
-            [fileManager createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];
-
-        NSMutableURLRequest *newRequest = [_originalRequest mutableCopy];
-        [newRequest setURL:URL];
-        return newRequest;
-    } else
-        return request;
+    downloadTask.sessionTask = sessionTask;
+    [self _addDownloadTask:downloadTask identifier:identifier];
+
+    return identifier;
 }
 }
 
 
-- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
+- (void)URLSession:(NSURLSession *)session
+              task:(NSURLSessionTask *)task
+willPerformHTTPRedirection:(NSHTTPURLResponse *)response
+        newRequest:(NSURLRequest *)request
+ completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
 {
 {
-    completionHandler(NSURLSessionResponseAllow);
-    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
-    _statusCode = [httpResponse statusCode];
-    if (_statusCode == 200) {
-        _expectedDownloadSize = [response expectedContentLength];
-        APLog(@"expected download size: %lli", _expectedDownloadSize);
-        if (_expectedDownloadSize  > [[UIDevice currentDevice] VLCFreeDiskSpace].longLongValue) { //handle too big a download
-            [VLCAlertViewController alertViewManagerWithTitle:NSLocalizedString(@"DISK_FULL", nil)
-                                                 errorMessage:[NSString stringWithFormat:NSLocalizedString(@"DISK_FULL_FORMAT", nil), _fileName, [[UIDevice currentDevice] model]]
-                                               viewController:self.delegate];
-            [_sessionTask cancel];
-            [self _downloadEnded];
-            return;
-        }
-        [self.delegate downloadStarted];
-    } else {
-        APLog(@"unhandled status code %lu", (unsigned long)_statusCode);
-        if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
-            [self.delegate downloadFailedWithErrorDescription:[NSString stringWithFormat:NSLocalizedString(@"HTTP_DOWNLOAD_FAILED",nil), _statusCode]];
+    VLCHTTPFileDownloaderTask *downloadTask = [self _downloadTaskWithIdentifier:task.taskDescription];
+    NSURL *newUrl = request.URL;
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+
+    if ([fileManager fileExistsAtPath:[downloadTask.fileURL path]])
+        [fileManager removeItemAtURL:downloadTask.fileURL error:nil];
+
+    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+    NSString *basePath = [[searchPaths firstObject] stringByAppendingPathComponent:@"Upload"];
+    downloadTask.fileName = [[newUrl lastPathComponent] stringByRemovingPercentEncoding];
+    downloadTask.fileURL = [NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:downloadTask.fileName]];
+
+    if (![fileManager fileExistsAtPath:basePath]) {
+        [fileManager createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];
     }
     }
+    completionHandler(nil);
 }
 }
 
 
-- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
-{
-    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:_filePath];
-    if (!fileHandle && _statusCode != 404) {
-        // create file
-        [[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil];
-        fileHandle = [NSFileHandle fileHandleForWritingAtPath:_filePath];
-
-        if (!fileHandle) {
-            APLog(@"file creation failed, no data was saved");
-            if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
-                [self.delegate downloadFailedWithErrorDescription:NSLocalizedString(@"HTTP_FILE_CREATION_FAILED",nil)];
-            return;
-        }
+- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
+    if ([self.delegate respondsToSelector:@selector(progressUpdatedTo:receivedDataSize:expectedDownloadSize:identifier:)]) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [self.delegate progressUpdatedTo: (float)totalBytesWritten / (float)totalBytesExpectedToWrite receivedDataSize:bytesWritten expectedDownloadSize:totalBytesExpectedToWrite identifier:downloadTask.taskDescription];
+        });
     }
     }
+}
 
 
-    @try {
-        [fileHandle seekToEndOfFile];
-        [fileHandle writeData:data];
+- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
+{
+    VLCHTTPFileDownloaderTask *task = [self _downloadTaskWithIdentifier:downloadTask.taskDescription];
 
 
-        _receivedDataSize = _receivedDataSize + [data length];
-        if ([self.delegate respondsToSelector:@selector(progressUpdatedTo:receivedDataSize:expectedDownloadSize:)])
-            [self.delegate progressUpdatedTo: (float)_receivedDataSize / (float)_expectedDownloadSize receivedDataSize:_receivedDataSize expectedDownloadSize:_expectedDownloadSize];
-    }
-    @catch (NSException * e) {
-        APLog(@"exception when writing to file %@", _filePath);
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    if (![fileManager fileExistsAtPath:[task.fileURL path]]) {
+        if (@available(iOS 10.3, *)) {
+            //The copy should be instant iOS 10.3+ with AFS
+            [fileManager copyItemAtURL:location toURL:task.fileURL error:nil];
+        } else {
+            [fileManager moveItemAtURL:location toURL:task.fileURL error:nil];
+        }
     }
     }
-
-    [fileHandle closeFile];
 }
 }
 
 
 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
@@ -196,55 +188,88 @@
     if (error.code != -999) {
     if (error.code != -999) {
         if (error) {
         if (error) {
             APLog(@"http file download failed (%li)", (long)error.code);
             APLog(@"http file download failed (%li)", (long)error.code);
-            if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
-                [self.delegate downloadFailedWithErrorDescription:error.description];
+
+            if ([self.delegate respondsToSelector:@selector(downloadFailedWithIdentifier:errorDescription:)]) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [self.delegate downloadFailedWithIdentifier:task.taskDescription errorDescription:error.description];
+                });
+            }
         } else {
         } else {
             APLog(@"http file download complete");
             APLog(@"http file download complete");
         }
         }
-        [self _downloadEnded];
+        [self _downloadEndedWithIdentifier:task.taskDescription];
     } else {
     } else {
         APLog(@"http file download canceled");
         APLog(@"http file download canceled");
     }
     }
 }
 }
 
 
-- (void)cancelDownload
+- (void)cancelDownloadWithIdentifier:(NSString *)identifier
 {
 {
-    [_sessionTask cancel];
+    VLCHTTPFileDownloaderTask *downloadTask = [self _downloadTaskWithIdentifier:identifier];
+    [downloadTask.sessionTask cancel];
     /* remove partially downloaded content */
     /* remove partially downloaded content */
     NSFileManager *fileManager = [NSFileManager defaultManager];
     NSFileManager *fileManager = [NSFileManager defaultManager];
-    if ([fileManager fileExistsAtPath:_filePath])
-        [fileManager removeItemAtPath:_filePath error:nil];
+    if ([fileManager fileExistsAtPath:downloadTask.fileURL.path])
+        [fileManager removeItemAtURL:downloadTask.fileURL error:nil];
 
 
-    if ([self.delegate respondsToSelector:@selector(downloadFailedWithErrorDescription:)])
-        [self.delegate downloadFailedWithErrorDescription:NSLocalizedString(@"HTTP_DOWNLOAD_CANCELLED",nil)];
+    if ([self.delegate respondsToSelector:@selector(downloadFailedWithIdentifier:errorDescription:)]) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [self.delegate downloadFailedWithIdentifier:identifier errorDescription:NSLocalizedString(@"HTTP_DOWNLOAD_CANCELLED",nil)];
+        });
+    }
 
 
-    [self _downloadEnded];
+    [self _downloadEndedWithIdentifier:identifier];
 }
 }
 
 
-- (void)_downloadEnded
+- (void)_downloadEndedWithIdentifier:(NSString *)identifier
 {
 {
-    _downloadInProgress = NO;
+    VLCHTTPFileDownloaderTask *task = [self _downloadTaskWithIdentifier:identifier];
+
     VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
     VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
     [activityManager networkActivityStopped];
     [activityManager networkActivityStopped];
     [activityManager activateIdleTimer];
     [activityManager activateIdleTimer];
 
 
-    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
-    NSString *libraryPath = searchPaths[0];
-
     NSFileManager *fileManager = [NSFileManager defaultManager];
     NSFileManager *fileManager = [NSFileManager defaultManager];
-    NSString *finalFilePath = [libraryPath stringByAppendingPathComponent:_fileName];
-
-    if ([fileManager fileExistsAtPath:_filePath]) {
-        [fileManager moveItemAtPath:_filePath toPath:finalFilePath error:nil];
+    if ([fileManager fileExistsAtPath:[task.fileURL path]]) {
         [[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
         [[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
 #if TARGET_OS_IOS
 #if TARGET_OS_IOS
-        // FIXME: Replace notifications by cleaner observers
-        [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.VLCNewFileAddedNotification
-                                                            object:self];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            // FIXME: Replace notifications by cleaner observers
+            [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.VLCNewFileAddedNotification
+                                                                object:self];
+        });
 #endif
 #endif
     }
     }
 
 
-    [self.delegate downloadEnded];
+    [self _removeDownloadWithIdentifier:identifier];
+
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self.delegate downloadEndedWithIdentifier:identifier];
+    });
+}
+
+
+- (void)_removeDownloadWithIdentifier:(NSString *)identifier
+{
+    dispatch_async(_downloadsAccessQueue, ^{
+        [self.downloads removeObjectForKey:identifier];
+    });
+}
+
+- (VLCHTTPFileDownloaderTask *)_downloadTaskWithIdentifier:(NSString *)identifier
+{
+    __block VLCHTTPFileDownloaderTask *task;
+    dispatch_sync(_downloadsAccessQueue, ^{
+        task = [self.downloads objectForKey:identifier];
+    });
+    return task;
+}
+
+- (void)_addDownloadTask:(VLCHTTPFileDownloaderTask *)task identifier:(NSString *)identifier
+{
+    dispatch_async(_downloadsAccessQueue, ^{
+        [self.downloads setObject:task forKey:identifier];
+    });
 }
 }
 
 
 @end
 @end