VLCHTTPFileDownloader.m 11 KB

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