VLCHTTPFileDownloader.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /*****************************************************************************
  2. * VLCHTTPFileDownloader.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2013-2018 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors: Felix Paul Kühne <fkuehne # videolan.org>
  9. * Pierre Sagaspe <pierre.sagaspe # me.com>
  10. *
  11. * Refer to the COPYING file of the official project for license.
  12. *****************************************************************************/
  13. #import "VLCHTTPFileDownloader.h"
  14. #import "NSString+SupportedMedia.h"
  15. #import "VLCActivityManager.h"
  16. #import "UIDevice+VLC.h"
  17. #import "VLCMediaFileDiscoverer.h"
  18. #import "VLC-Swift.h"
  19. @interface VLCHTTPFileDownloaderTask: NSObject
  20. @property (nonatomic) NSURLSessionTask *sessionTask;
  21. @property (nonatomic, copy) NSURL *url;
  22. @property (nonatomic, copy) NSString *fileName;
  23. @property (nonatomic, copy) NSURL *fileURL;
  24. @end
  25. @implementation VLCHTTPFileDownloaderTask
  26. - (NSMutableURLRequest *)buildRequest
  27. {
  28. NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:self.url];
  29. [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"];
  30. return theRequest;
  31. }
  32. @end
  33. @interface VLCHTTPFileDownloader () <NSURLSessionDelegate>
  34. @property (nonatomic) NSURLSession *urlSession;
  35. @property (nonatomic) NSMutableDictionary *downloads;
  36. @property (nonatomic) dispatch_queue_t downloadsAccessQueue;
  37. @end
  38. @implementation VLCHTTPFileDownloader
  39. - (instancetype)init
  40. {
  41. if (self = [super init]) {
  42. _urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
  43. delegate:self
  44. delegateQueue:nil];
  45. _downloads = [[NSMutableDictionary alloc] init];
  46. _downloadsAccessQueue = dispatch_queue_create("VLCHTTPFileDownloader.downloadsQueue", DISPATCH_QUEUE_SERIAL);
  47. }
  48. return self;
  49. }
  50. - (NSString *)downloadFileFromURL:(NSURL *)url
  51. {
  52. return [self downloadFileFromURL:url withFileName:nil];
  53. }
  54. - (NSString *)createPotentialNameFromName:(NSString *)name
  55. {
  56. NSString *documentDirectoryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
  57. NSUserDomainMask,
  58. YES).firstObject;
  59. return [[self createPotentialPathFromPath:[documentDirectoryPath
  60. stringByAppendingPathComponent:name]] lastPathComponent];
  61. }
  62. - (NSString *)createPotentialPathFromPath:(NSString *)path
  63. {
  64. NSFileManager *fileManager = [NSFileManager defaultManager];
  65. NSString *fileName = [path lastPathComponent];
  66. NSString *finalFilePath = [path stringByDeletingLastPathComponent];
  67. if ([fileManager fileExistsAtPath:path]) {
  68. NSString *potentialFilename;
  69. NSString *fileExtension = [fileName pathExtension];
  70. NSString *rawFileName = [fileName stringByDeletingPathExtension];
  71. for (NSUInteger x = 1; x < 100; x++) {
  72. potentialFilename = [NSString stringWithFormat:@"%@_%lu.%@",
  73. rawFileName, (unsigned long)x, fileExtension];
  74. if (![fileManager fileExistsAtPath:[finalFilePath stringByAppendingPathComponent:potentialFilename]]) {
  75. break;
  76. }
  77. }
  78. return [finalFilePath stringByAppendingPathComponent:potentialFilename];
  79. }
  80. return path;
  81. }
  82. - (NSString *)downloadFileFromURL:(NSURL *)url withFileName:(NSString*)fileName
  83. {
  84. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  85. NSString *libraryPath = [searchPaths firstObject];
  86. VLCHTTPFileDownloaderTask *downloadTask = [[VLCHTTPFileDownloaderTask alloc] init];
  87. downloadTask.url = url;
  88. NSString *downloadFileName;
  89. fileName = fileName ?: [url.lastPathComponent stringByRemovingPercentEncoding];
  90. downloadFileName = [self createPotentialNameFromName:fileName];
  91. if (downloadFileName.pathExtension.length == 0 || ![downloadFileName isSupportedFormat]) {
  92. NSString *urlExtension = url.pathExtension;
  93. NSString *extension = urlExtension.length != 0 ? urlExtension : @"vlc";
  94. downloadFileName = [fileName stringByAppendingPathExtension:extension];
  95. }
  96. downloadTask.fileName = downloadFileName;
  97. downloadTask.fileURL = [NSURL fileURLWithPath:[libraryPath stringByAppendingPathComponent:downloadFileName]];
  98. NSString *identifier = [[NSUUID UUID] UUIDString];
  99. NSURLSessionTask *sessionTask = [self.urlSession downloadTaskWithRequest:[downloadTask buildRequest]];
  100. sessionTask.taskDescription = identifier;
  101. [sessionTask resume];
  102. if (!sessionTask) {
  103. APLog(@"failed to establish connection");
  104. return nil;
  105. } else {
  106. VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
  107. [activityManager networkActivityStarted];
  108. [activityManager disableIdleTimer];
  109. }
  110. downloadTask.sessionTask = sessionTask;
  111. [self _addDownloadTask:downloadTask identifier:identifier];
  112. _downloadInProgress = YES;
  113. return identifier;
  114. }
  115. - (void)URLSession:(NSURLSession *)session
  116. task:(NSURLSessionTask *)task
  117. willPerformHTTPRedirection:(NSHTTPURLResponse *)response
  118. newRequest:(NSURLRequest *)request
  119. completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
  120. {
  121. VLCHTTPFileDownloaderTask *downloadTask = [self _downloadTaskWithIdentifier:task.taskDescription];
  122. NSURL *newUrl = request.URL;
  123. NSFileManager *fileManager = [NSFileManager defaultManager];
  124. if ([fileManager fileExistsAtPath:[downloadTask.fileURL path]])
  125. [fileManager removeItemAtURL:downloadTask.fileURL error:nil];
  126. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  127. NSString *basePath = [[searchPaths firstObject] stringByAppendingPathComponent:kVLCHTTPUploadDirectory];
  128. downloadTask.fileName = [[newUrl lastPathComponent] stringByRemovingPercentEncoding];
  129. downloadTask.fileURL = [NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:downloadTask.fileName]];
  130. if (![fileManager fileExistsAtPath:basePath]) {
  131. [fileManager createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];
  132. }
  133. completionHandler(nil);
  134. }
  135. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
  136. if ([self.delegate respondsToSelector:@selector(progressUpdatedTo:receivedDataSize:expectedDownloadSize:identifier:)]) {
  137. dispatch_async(dispatch_get_main_queue(), ^{
  138. [self.delegate progressUpdatedTo: (float)totalBytesWritten / (float)totalBytesExpectedToWrite receivedDataSize:bytesWritten expectedDownloadSize:totalBytesExpectedToWrite identifier:downloadTask.taskDescription];
  139. });
  140. }
  141. }
  142. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
  143. {
  144. VLCHTTPFileDownloaderTask *task = [self _downloadTaskWithIdentifier:downloadTask.taskDescription];
  145. NSFileManager *fileManager = [NSFileManager defaultManager];
  146. if (![fileManager fileExistsAtPath:[task.fileURL path]]) {
  147. if (@available(iOS 10.3, *)) {
  148. //The copy should be instant iOS 10.3+ with AFS
  149. [fileManager copyItemAtURL:location toURL:task.fileURL error:nil];
  150. } else {
  151. [fileManager moveItemAtURL:location toURL:task.fileURL error:nil];
  152. }
  153. }
  154. }
  155. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
  156. {
  157. if (error.code != -999) {
  158. if (error) {
  159. APLog(@"http file download failed (%li)", (long)error.code);
  160. if ([self.delegate respondsToSelector:@selector(downloadFailedWithIdentifier:errorDescription:)]) {
  161. dispatch_async(dispatch_get_main_queue(), ^{
  162. [self.delegate downloadFailedWithIdentifier:task.taskDescription errorDescription:error.description];
  163. });
  164. }
  165. } else {
  166. APLog(@"http file download complete");
  167. }
  168. [self _downloadEndedWithIdentifier:task.taskDescription];
  169. } else {
  170. APLog(@"http file download canceled");
  171. }
  172. }
  173. - (void)cancelDownloadWithIdentifier:(NSString *)identifier
  174. {
  175. VLCHTTPFileDownloaderTask *downloadTask = [self _downloadTaskWithIdentifier:identifier];
  176. [downloadTask.sessionTask cancel];
  177. /* remove partially downloaded content */
  178. NSFileManager *fileManager = [NSFileManager defaultManager];
  179. if ([fileManager fileExistsAtPath:downloadTask.fileURL.path])
  180. [fileManager removeItemAtURL:downloadTask.fileURL error:nil];
  181. if ([self.delegate respondsToSelector:@selector(downloadFailedWithIdentifier:errorDescription:)]) {
  182. dispatch_async(dispatch_get_main_queue(), ^{
  183. [self.delegate downloadFailedWithIdentifier:identifier errorDescription:NSLocalizedString(@"HTTP_DOWNLOAD_CANCELLED",nil)];
  184. });
  185. }
  186. [self _downloadEndedWithIdentifier:identifier];
  187. }
  188. - (void)_downloadEndedWithIdentifier:(NSString *)identifier
  189. {
  190. VLCHTTPFileDownloaderTask *task = [self _downloadTaskWithIdentifier:identifier];
  191. VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
  192. [activityManager networkActivityStopped];
  193. [activityManager activateIdleTimer];
  194. NSFileManager *fileManager = [NSFileManager defaultManager];
  195. if ([fileManager fileExistsAtPath:[task.fileURL path]]) {
  196. [[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
  197. #if TARGET_OS_IOS
  198. dispatch_async(dispatch_get_main_queue(), ^{
  199. // FIXME: Replace notifications by cleaner observers
  200. [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.VLCNewFileAddedNotification
  201. object:self];
  202. });
  203. #endif
  204. }
  205. [self _removeDownloadWithIdentifier:identifier];
  206. _downloadInProgress = NO;
  207. dispatch_async(dispatch_get_main_queue(), ^{
  208. [self.delegate downloadEndedWithIdentifier:identifier];
  209. });
  210. }
  211. - (void)_removeDownloadWithIdentifier:(NSString *)identifier
  212. {
  213. dispatch_async(_downloadsAccessQueue, ^{
  214. [self.downloads removeObjectForKey:identifier];
  215. });
  216. }
  217. - (VLCHTTPFileDownloaderTask *)_downloadTaskWithIdentifier:(NSString *)identifier
  218. {
  219. __block VLCHTTPFileDownloaderTask *task;
  220. dispatch_sync(_downloadsAccessQueue, ^{
  221. task = [self.downloads objectForKey:identifier];
  222. });
  223. return task;
  224. }
  225. - (void)_addDownloadTask:(VLCHTTPFileDownloaderTask *)task identifier:(NSString *)identifier
  226. {
  227. dispatch_async(_downloadsAccessQueue, ^{
  228. [self.downloads setObject:task forKey:identifier];
  229. });
  230. }
  231. @end