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. return identifier;
  113. }
  114. - (void)URLSession:(NSURLSession *)session
  115. task:(NSURLSessionTask *)task
  116. willPerformHTTPRedirection:(NSHTTPURLResponse *)response
  117. newRequest:(NSURLRequest *)request
  118. completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
  119. {
  120. VLCHTTPFileDownloaderTask *downloadTask = [self _downloadTaskWithIdentifier:task.taskDescription];
  121. NSURL *newUrl = request.URL;
  122. NSFileManager *fileManager = [NSFileManager defaultManager];
  123. if ([fileManager fileExistsAtPath:[downloadTask.fileURL path]])
  124. [fileManager removeItemAtURL:downloadTask.fileURL error:nil];
  125. NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  126. NSString *basePath = [[searchPaths firstObject] stringByAppendingPathComponent:kVLCHTTPUploadDirectory];
  127. downloadTask.fileName = [[newUrl lastPathComponent] stringByRemovingPercentEncoding];
  128. downloadTask.fileURL = [NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:downloadTask.fileName]];
  129. if (![fileManager fileExistsAtPath:basePath]) {
  130. [fileManager createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil];
  131. }
  132. completionHandler(nil);
  133. }
  134. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
  135. if ([self.delegate respondsToSelector:@selector(progressUpdatedTo:receivedDataSize:expectedDownloadSize:identifier:)]) {
  136. dispatch_async(dispatch_get_main_queue(), ^{
  137. [self.delegate progressUpdatedTo: (float)totalBytesWritten / (float)totalBytesExpectedToWrite receivedDataSize:bytesWritten expectedDownloadSize:totalBytesExpectedToWrite identifier:downloadTask.taskDescription];
  138. });
  139. }
  140. }
  141. - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
  142. {
  143. VLCHTTPFileDownloaderTask *task = [self _downloadTaskWithIdentifier:downloadTask.taskDescription];
  144. NSFileManager *fileManager = [NSFileManager defaultManager];
  145. if (![fileManager fileExistsAtPath:[task.fileURL path]]) {
  146. if (@available(iOS 10.3, *)) {
  147. //The copy should be instant iOS 10.3+ with AFS
  148. [fileManager copyItemAtURL:location toURL:task.fileURL error:nil];
  149. } else {
  150. [fileManager moveItemAtURL:location toURL:task.fileURL error:nil];
  151. }
  152. }
  153. }
  154. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
  155. {
  156. if (error.code != -999) {
  157. if (error) {
  158. APLog(@"http file download failed (%li)", (long)error.code);
  159. if ([self.delegate respondsToSelector:@selector(downloadFailedWithIdentifier:errorDescription:)]) {
  160. dispatch_async(dispatch_get_main_queue(), ^{
  161. [self.delegate downloadFailedWithIdentifier:task.taskDescription errorDescription:error.description];
  162. });
  163. }
  164. } else {
  165. APLog(@"http file download complete");
  166. }
  167. [self _downloadEndedWithIdentifier:task.taskDescription];
  168. } else {
  169. APLog(@"http file download canceled");
  170. }
  171. }
  172. - (void)cancelDownloadWithIdentifier:(NSString *)identifier
  173. {
  174. VLCHTTPFileDownloaderTask *downloadTask = [self _downloadTaskWithIdentifier:identifier];
  175. [downloadTask.sessionTask cancel];
  176. /* remove partially downloaded content */
  177. NSFileManager *fileManager = [NSFileManager defaultManager];
  178. if ([fileManager fileExistsAtPath:downloadTask.fileURL.path])
  179. [fileManager removeItemAtURL:downloadTask.fileURL error:nil];
  180. if ([self.delegate respondsToSelector:@selector(downloadFailedWithIdentifier:errorDescription:)]) {
  181. dispatch_async(dispatch_get_main_queue(), ^{
  182. [self.delegate downloadFailedWithIdentifier:identifier errorDescription:NSLocalizedString(@"HTTP_DOWNLOAD_CANCELLED",nil)];
  183. });
  184. }
  185. [self _downloadEndedWithIdentifier:identifier];
  186. }
  187. - (void)_downloadEndedWithIdentifier:(NSString *)identifier
  188. {
  189. VLCHTTPFileDownloaderTask *task = [self _downloadTaskWithIdentifier:identifier];
  190. VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
  191. [activityManager networkActivityStopped];
  192. [activityManager activateIdleTimer];
  193. NSFileManager *fileManager = [NSFileManager defaultManager];
  194. if ([fileManager fileExistsAtPath:[task.fileURL path]]) {
  195. [[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
  196. #if TARGET_OS_IOS
  197. dispatch_async(dispatch_get_main_queue(), ^{
  198. // FIXME: Replace notifications by cleaner observers
  199. [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.VLCNewFileAddedNotification
  200. object:self];
  201. });
  202. #endif
  203. }
  204. [self _removeDownloadWithIdentifier:identifier];
  205. dispatch_async(dispatch_get_main_queue(), ^{
  206. [self.delegate downloadEndedWithIdentifier:identifier];
  207. });
  208. }
  209. - (void)_removeDownloadWithIdentifier:(NSString *)identifier
  210. {
  211. dispatch_async(_downloadsAccessQueue, ^{
  212. [self.downloads removeObjectForKey:identifier];
  213. });
  214. }
  215. - (VLCHTTPFileDownloaderTask *)_downloadTaskWithIdentifier:(NSString *)identifier
  216. {
  217. __block VLCHTTPFileDownloaderTask *task;
  218. dispatch_sync(_downloadsAccessQueue, ^{
  219. task = [self.downloads objectForKey:identifier];
  220. });
  221. return task;
  222. }
  223. - (void)_addDownloadTask:(VLCHTTPFileDownloaderTask *)task identifier:(NSString *)identifier
  224. {
  225. dispatch_async(_downloadsAccessQueue, ^{
  226. [self.downloads setObject:task forKey:identifier];
  227. });
  228. }
  229. @end