YTKNetworkAgent.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. //
  2. // YTKNetworkAgent.m
  3. //
  4. // Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. #import "YTKNetworkAgent.h"
  24. #import "YTKNetworkConfig.h"
  25. #import "YTKNetworkPrivate.h"
  26. #import <pthread/pthread.h>
  27. #if __has_include(<AFNetworking/AFHTTPSessionManager.h>)
  28. #import <AFNetworking/AFHTTPSessionManager.h>
  29. #else
  30. #import <AFNetworking/AFHTTPSessionManager.h>
  31. #endif
  32. #define Lock() pthread_mutex_lock(&_lock)
  33. #define Unlock() pthread_mutex_unlock(&_lock)
  34. #define kYTKNetworkIncompleteDownloadFolderName @"Incomplete"
  35. @implementation YTKNetworkAgent {
  36. AFHTTPSessionManager *_manager;
  37. YTKNetworkConfig *_config;
  38. AFJSONResponseSerializer *_jsonResponseSerializer;
  39. AFXMLParserResponseSerializer *_xmlParserResponseSerialzier;
  40. NSMutableDictionary<NSNumber *, YTKBaseRequest *> *_requestsRecord;
  41. dispatch_queue_t _processingQueue;
  42. pthread_mutex_t _lock;
  43. NSIndexSet *_allStatusCodes;
  44. }
  45. + (YTKNetworkAgent *)sharedAgent {
  46. static id sharedInstance = nil;
  47. static dispatch_once_t onceToken;
  48. dispatch_once(&onceToken, ^{
  49. sharedInstance = [[self alloc] init];
  50. });
  51. return sharedInstance;
  52. }
  53. - (instancetype)init {
  54. self = [super init];
  55. if (self) {
  56. _config = [YTKNetworkConfig sharedConfig];
  57. _manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:_config.sessionConfiguration];
  58. _requestsRecord = [NSMutableDictionary dictionary];
  59. _processingQueue = dispatch_queue_create("com.yuantiku.networkagent.processing", DISPATCH_QUEUE_CONCURRENT);
  60. _allStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(100, 500)];
  61. pthread_mutex_init(&_lock, NULL);
  62. _manager.securityPolicy = _config.securityPolicy;
  63. _manager.responseSerializer = [AFHTTPResponseSerializer serializer];
  64. // Take over the status code validation
  65. _manager.responseSerializer.acceptableStatusCodes = _allStatusCodes;
  66. _manager.completionQueue = _processingQueue;
  67. [_manager setTaskDidFinishCollectingMetricsBlock:_config.collectingMetricsBlock];
  68. }
  69. return self;
  70. }
  71. - (AFJSONResponseSerializer *)jsonResponseSerializer {
  72. static dispatch_once_t onceToken;
  73. dispatch_once(&onceToken, ^{
  74. _jsonResponseSerializer = [AFJSONResponseSerializer serializer];
  75. _jsonResponseSerializer.acceptableStatusCodes = _allStatusCodes;
  76. });
  77. return _jsonResponseSerializer;
  78. }
  79. - (AFXMLParserResponseSerializer *)xmlParserResponseSerialzier {
  80. static dispatch_once_t onceToken;
  81. dispatch_once(&onceToken, ^{
  82. _xmlParserResponseSerialzier = [AFXMLParserResponseSerializer serializer];
  83. _xmlParserResponseSerialzier.acceptableStatusCodes = _allStatusCodes;
  84. });
  85. return _xmlParserResponseSerialzier;
  86. }
  87. #pragma mark -
  88. - (NSString *)buildRequestUrl:(YTKBaseRequest *)request {
  89. NSParameterAssert(request != nil);
  90. NSString *detailUrl = [request requestUrl];
  91. NSURL *temp = [NSURL URLWithString:detailUrl];
  92. // If detailUrl is valid URL
  93. if (temp && temp.host && temp.scheme) {
  94. return detailUrl;
  95. }
  96. // Filter URL if needed
  97. NSArray *filters = [_config urlFilters];
  98. for (id<YTKUrlFilterProtocol> f in filters) {
  99. detailUrl = [f filterUrl:detailUrl withRequest:request];
  100. }
  101. NSString *baseUrl;
  102. if ([request useCDN]) {
  103. if ([request cdnUrl].length > 0) {
  104. baseUrl = [request cdnUrl];
  105. } else {
  106. baseUrl = [_config cdnUrl];
  107. }
  108. } else {
  109. if ([request baseUrl].length > 0) {
  110. baseUrl = [request baseUrl];
  111. } else {
  112. baseUrl = [_config baseUrl];
  113. }
  114. }
  115. // URL slash compatibility
  116. NSURL *url = [NSURL URLWithString:baseUrl];
  117. if (baseUrl.length > 0 && ![baseUrl hasSuffix:@"/"]) {
  118. url = [url URLByAppendingPathComponent:@""];
  119. }
  120. return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
  121. }
  122. - (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request {
  123. AFHTTPRequestSerializer *requestSerializer = nil;
  124. if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
  125. requestSerializer = [AFHTTPRequestSerializer serializer];
  126. } else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
  127. requestSerializer = [AFJSONRequestSerializer serializer];
  128. }
  129. requestSerializer.timeoutInterval = [request requestTimeoutInterval];
  130. requestSerializer.allowsCellularAccess = [request allowsCellularAccess];
  131. // If api needs server username and password
  132. NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
  133. if (authorizationHeaderFieldArray != nil) {
  134. [requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject
  135. password:authorizationHeaderFieldArray.lastObject];
  136. }
  137. // If api needs to add custom value to HTTPHeaderField
  138. NSDictionary<NSString *, NSString *> *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary];
  139. if (headerFieldValueDictionary != nil) {
  140. for (NSString *httpHeaderField in headerFieldValueDictionary.allKeys) {
  141. NSString *value = headerFieldValueDictionary[httpHeaderField];
  142. [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField];
  143. }
  144. }
  145. return requestSerializer;
  146. }
  147. - (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
  148. YTKRequestMethod method = [request requestMethod];
  149. NSString *url = [self buildRequestUrl:request];
  150. id param = request.requestArgument;
  151. AFConstructingBlock constructingBlock = [request constructingBodyBlock];
  152. AFURLSessionTaskProgressBlock uploadProgressBlock = [request uploadProgressBlock];
  153. AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
  154. switch (method) {
  155. case YTKRequestMethodGET:
  156. if (request.resumableDownloadPath) {
  157. return [self downloadTaskWithDownloadPath:request.resumableDownloadPath
  158. requestSerializer:requestSerializer
  159. URLString:url
  160. parameters:param
  161. progress:request.resumableDownloadProgressBlock
  162. error:error];
  163. } else {
  164. return [self dataTaskWithHTTPMethod:@"GET"
  165. requestSerializer:requestSerializer
  166. URLString:url
  167. parameters:param
  168. error:error];
  169. }
  170. case YTKRequestMethodPOST:
  171. return [self dataTaskWithHTTPMethod:@"POST"
  172. requestSerializer:requestSerializer
  173. URLString:url
  174. parameters:param
  175. uploadProgress:uploadProgressBlock
  176. constructingBodyWithBlock:constructingBlock
  177. error:error];
  178. case YTKRequestMethodHEAD:
  179. return [self dataTaskWithHTTPMethod:@"HEAD"
  180. requestSerializer:requestSerializer
  181. URLString:url
  182. parameters:param
  183. error:error];
  184. case YTKRequestMethodPUT:
  185. return [self dataTaskWithHTTPMethod:@"PUT"
  186. requestSerializer:requestSerializer
  187. URLString:url
  188. parameters:param
  189. uploadProgress:uploadProgressBlock
  190. constructingBodyWithBlock:constructingBlock
  191. error:error];
  192. case YTKRequestMethodDELETE:
  193. return [self dataTaskWithHTTPMethod:@"DELETE"
  194. requestSerializer:requestSerializer
  195. URLString:url
  196. parameters:param
  197. error:error];
  198. case YTKRequestMethodPATCH:
  199. return [self dataTaskWithHTTPMethod:@"PATCH"
  200. requestSerializer:requestSerializer
  201. URLString:url
  202. parameters:param
  203. error:error];
  204. }
  205. }
  206. - (void)addRequest:(YTKBaseRequest *)request {
  207. NSParameterAssert(request != nil);
  208. NSError * __autoreleasing requestSerializationError = nil;
  209. NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
  210. if (customUrlRequest) {
  211. __block NSURLSessionDataTask *dataTask = nil;
  212. dataTask = [_manager dataTaskWithRequest:customUrlRequest
  213. uploadProgress:nil
  214. downloadProgress:nil
  215. completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
  216. [self handleRequestResult:dataTask responseObject:responseObject error:error];
  217. }];
  218. request.requestTask = dataTask;
  219. } else {
  220. request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
  221. }
  222. if (requestSerializationError) {
  223. [self requestDidFailWithRequest:request error:requestSerializationError];
  224. return;
  225. }
  226. NSAssert(request.requestTask != nil, @"requestTask should not be nil");
  227. // Set request task priority
  228. if ([request.requestTask respondsToSelector:@selector(priority)]) {
  229. switch (request.requestPriority) {
  230. case YTKRequestPriorityHigh:
  231. request.requestTask.priority = NSURLSessionTaskPriorityHigh;
  232. break;
  233. case YTKRequestPriorityLow:
  234. request.requestTask.priority = NSURLSessionTaskPriorityLow;
  235. break;
  236. case YTKRequestPriorityDefault:
  237. /*!!fall through*/
  238. default:
  239. request.requestTask.priority = NSURLSessionTaskPriorityDefault;
  240. break;
  241. }
  242. }
  243. // Retain request
  244. YTKLog(@"Add request: %@", NSStringFromClass([request class]));
  245. [self addRequestToRecord:request];
  246. [request.requestTask resume];
  247. }
  248. - (void)cancelRequest:(YTKBaseRequest *)request {
  249. NSParameterAssert(request != nil);
  250. if (request.resumableDownloadPath && [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] != nil) {
  251. NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
  252. [requestTask cancelByProducingResumeData:^(NSData *resumeData) {
  253. NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
  254. [resumeData writeToURL:localUrl atomically:YES];
  255. }];
  256. } else {
  257. [request.requestTask cancel];
  258. }
  259. [self removeRequestFromRecord:request];
  260. [request clearCompletionBlock];
  261. }
  262. - (void)cancelAllRequests {
  263. Lock();
  264. NSArray *allKeys = [_requestsRecord allKeys];
  265. Unlock();
  266. if (allKeys && allKeys.count > 0) {
  267. NSArray *copiedKeys = [allKeys copy];
  268. for (NSNumber *key in copiedKeys) {
  269. Lock();
  270. YTKBaseRequest *request = _requestsRecord[key];
  271. Unlock();
  272. // We are using non-recursive lock.
  273. // Do not lock `stop`, otherwise deadlock may occur.
  274. [request stop];
  275. }
  276. }
  277. }
  278. - (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
  279. BOOL result = [request statusCodeValidator];
  280. if (!result) {
  281. if (error) {
  282. NSString *desc = [NSString stringWithFormat:@"Invalid status code (%ld)", (long)[request responseStatusCode]];
  283. *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:desc}];
  284. }
  285. return result;
  286. }
  287. id json = [request responseJSONObject];
  288. id validator = [request jsonValidator];
  289. if (json && validator) {
  290. result = [YTKNetworkUtils validateJSON:json withValidator:validator];
  291. if (!result) {
  292. if (error) {
  293. *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidJSONFormat userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON format"}];
  294. }
  295. return result;
  296. }
  297. }
  298. return YES;
  299. }
  300. - (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
  301. Lock();
  302. YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
  303. Unlock();
  304. // When the request is cancelled and removed from records, the underlying
  305. // AFNetworking failure callback will still kicks in, resulting in a nil `request`.
  306. //
  307. // Here we choose to completely ignore cancelled tasks. Neither success or failure
  308. // callback will be called.
  309. if (!request) {
  310. return;
  311. }
  312. YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));
  313. NSError * __autoreleasing serializationError = nil;
  314. NSError * __autoreleasing validationError = nil;
  315. NSError *requestError = nil;
  316. BOOL succeed = NO;
  317. request.responseObject = responseObject;
  318. if ([request.responseObject isKindOfClass:[NSData class]]) {
  319. request.responseData = responseObject;
  320. request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
  321. switch (request.responseSerializerType) {
  322. case YTKResponseSerializerTypeHTTP:
  323. // Default serializer. Do nothing.
  324. break;
  325. case YTKResponseSerializerTypeJSON:
  326. request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
  327. request.responseJSONObject = request.responseObject;
  328. break;
  329. case YTKResponseSerializerTypeXMLParser:
  330. request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
  331. break;
  332. }
  333. }
  334. if (error) {
  335. succeed = NO;
  336. requestError = error;
  337. } else if (serializationError) {
  338. succeed = NO;
  339. requestError = serializationError;
  340. } else {
  341. succeed = [self validateResult:request error:&validationError];
  342. requestError = validationError;
  343. }
  344. if (succeed) {
  345. [self requestDidSucceedWithRequest:request];
  346. } else {
  347. [self requestDidFailWithRequest:request error:requestError];
  348. }
  349. dispatch_async(dispatch_get_main_queue(), ^{
  350. [self removeRequestFromRecord:request];
  351. [request clearCompletionBlock];
  352. });
  353. }
  354. - (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
  355. @autoreleasepool {
  356. [request requestCompletePreprocessor];
  357. }
  358. dispatch_async(dispatch_get_main_queue(), ^{
  359. [request toggleAccessoriesWillStopCallBack];
  360. [request requestCompleteFilter];
  361. if (request.delegate != nil) {
  362. [request.delegate requestFinished:request];
  363. }
  364. if (request.successCompletionBlock) {
  365. request.successCompletionBlock(request);
  366. }
  367. [request toggleAccessoriesDidStopCallBack];
  368. });
  369. }
  370. - (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error {
  371. request.error = error;
  372. YTKLog(@"Request %@ failed, status code = %ld, error = %@",
  373. NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);
  374. // Save incomplete download data.
  375. NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
  376. NSURL *localUrl = nil;
  377. if (request.resumableDownloadPath) {
  378. localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
  379. }
  380. if (incompleteDownloadData && localUrl != nil) {
  381. [incompleteDownloadData writeToURL:localUrl atomically:YES];
  382. }
  383. // Load response from file and clean up if download task failed.
  384. if ([request.responseObject isKindOfClass:[NSURL class]]) {
  385. NSURL *url = request.responseObject;
  386. if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
  387. request.responseData = [NSData dataWithContentsOfURL:url];
  388. request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
  389. [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
  390. }
  391. request.responseObject = nil;
  392. }
  393. @autoreleasepool {
  394. [request requestFailedPreprocessor];
  395. }
  396. dispatch_async(dispatch_get_main_queue(), ^{
  397. [request toggleAccessoriesWillStopCallBack];
  398. [request requestFailedFilter];
  399. if (request.delegate != nil) {
  400. [request.delegate requestFailed:request];
  401. }
  402. if (request.failureCompletionBlock) {
  403. request.failureCompletionBlock(request);
  404. }
  405. [request toggleAccessoriesDidStopCallBack];
  406. });
  407. }
  408. - (void)addRequestToRecord:(YTKBaseRequest *)request {
  409. Lock();
  410. _requestsRecord[@(request.requestTask.taskIdentifier)] = request;
  411. Unlock();
  412. }
  413. - (void)removeRequestFromRecord:(YTKBaseRequest *)request {
  414. Lock();
  415. [_requestsRecord removeObjectForKey:@(request.requestTask.taskIdentifier)];
  416. YTKLog(@"Request queue size = %zd", [_requestsRecord count]);
  417. Unlock();
  418. }
  419. #pragma mark -
  420. - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
  421. requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
  422. URLString:(NSString *)URLString
  423. parameters:(id)parameters
  424. error:(NSError * _Nullable __autoreleasing *)error {
  425. return [self dataTaskWithHTTPMethod:method
  426. requestSerializer:requestSerializer
  427. URLString:URLString
  428. parameters:parameters
  429. uploadProgress:nil
  430. constructingBodyWithBlock:nil
  431. error:error];
  432. }
  433. - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
  434. requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
  435. URLString:(NSString *)URLString
  436. parameters:(id)parameters
  437. uploadProgress:(AFURLSessionTaskProgressBlock)uploadProgress
  438. constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
  439. error:(NSError * _Nullable __autoreleasing *)error {
  440. NSMutableURLRequest *request = nil;
  441. if (block) {
  442. request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error];
  443. } else {
  444. request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error];
  445. }
  446. __block NSURLSessionDataTask *dataTask = nil;
  447. dataTask = [_manager dataTaskWithRequest:request
  448. uploadProgress:uploadProgress
  449. downloadProgress:nil
  450. completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *_error) {
  451. [self handleRequestResult:dataTask responseObject:responseObject error:_error];
  452. }];
  453. return dataTask;
  454. }
  455. - (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath
  456. requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
  457. URLString:(NSString *)URLString
  458. parameters:(id)parameters
  459. progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
  460. error:(NSError * _Nullable __autoreleasing *)error {
  461. // add parameters to URL;
  462. NSMutableURLRequest *urlRequest = [requestSerializer requestWithMethod:@"GET" URLString:URLString parameters:parameters error:error];
  463. NSString *downloadTargetPath;
  464. BOOL isDirectory;
  465. if (![[NSFileManager defaultManager] fileExistsAtPath:downloadPath isDirectory:&isDirectory]) {
  466. isDirectory = NO;
  467. }
  468. // If targetPath is a directory, use the file name we got from the urlRequest.
  469. // Make sure downloadTargetPath is always a file, not directory.
  470. if (isDirectory) {
  471. NSString *fileName = [urlRequest.URL lastPathComponent];
  472. downloadTargetPath = [NSString pathWithComponents:@[downloadPath, fileName]];
  473. } else {
  474. downloadTargetPath = downloadPath;
  475. }
  476. // AFN use `moveItemAtURL` to move downloaded file to target path,
  477. // this method aborts the move attempt if a file already exist at the path.
  478. // So we remove the exist file before we start the download task.
  479. // https://github.com/AFNetworking/AFNetworking/issues/3775
  480. if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
  481. [[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
  482. }
  483. BOOL resumeSucceeded = NO;
  484. __block NSURLSessionDownloadTask *downloadTask = nil;
  485. NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:downloadPath];
  486. if (localUrl != nil) {
  487. BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:localUrl.path];
  488. NSData *data = [NSData dataWithContentsOfURL:localUrl];
  489. BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];
  490. BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid;
  491. // Try to resume with resumeData.
  492. // Even though we try to validate the resumeData, this may still fail and raise excecption.
  493. if (canBeResumed) {
  494. @try {
  495. downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
  496. return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
  497. } completionHandler:
  498. ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
  499. [self handleRequestResult:downloadTask responseObject:filePath error:error];
  500. }];
  501. resumeSucceeded = YES;
  502. } @catch (NSException *exception) {
  503. YTKLog(@"Resume download failed, reason = %@", exception.reason);
  504. resumeSucceeded = NO;
  505. }
  506. }
  507. }
  508. if (!resumeSucceeded) {
  509. downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
  510. return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
  511. } completionHandler:
  512. ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
  513. [self handleRequestResult:downloadTask responseObject:filePath error:error];
  514. }];
  515. }
  516. return downloadTask;
  517. }
  518. #pragma mark - Resumable Download
  519. - (NSString *)incompleteDownloadTempCacheFolder {
  520. NSFileManager *fileManager = [NSFileManager new];
  521. NSString *cacheFolder = [NSTemporaryDirectory() stringByAppendingPathComponent:kYTKNetworkIncompleteDownloadFolderName];
  522. BOOL isDirectory = NO;
  523. if ([fileManager fileExistsAtPath:cacheFolder isDirectory:&isDirectory] && isDirectory) {
  524. return cacheFolder;
  525. }
  526. NSError *error = nil;
  527. if ([fileManager createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error] && error == nil) {
  528. return cacheFolder;
  529. }
  530. YTKLog(@"Failed to create cache directory at %@ with error: %@", cacheFolder, error != nil ? error.localizedDescription : @"unkown");
  531. return nil;
  532. }
  533. - (NSURL *)incompleteDownloadTempPathForDownloadPath:(NSString *)downloadPath {
  534. if (downloadPath == nil || downloadPath.length == 0) {
  535. return nil;
  536. }
  537. NSString *tempPath = nil;
  538. NSString *md5URLString = [YTKNetworkUtils md5StringFromString:downloadPath];
  539. tempPath = [[self incompleteDownloadTempCacheFolder] stringByAppendingPathComponent:md5URLString];
  540. return tempPath == nil ? nil : [NSURL fileURLWithPath:tempPath];
  541. }
  542. #pragma mark - Testing
  543. - (AFHTTPSessionManager *)manager {
  544. return _manager;
  545. }
  546. - (void)resetURLSessionManager {
  547. _manager = [AFHTTPSessionManager manager];
  548. }
  549. - (void)resetURLSessionManagerWithConfiguration:(NSURLSessionConfiguration *)configuration {
  550. _manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
  551. }
  552. @end