123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- //
- // YTKNetworkAgent.m
- //
- // Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- #import "YTKNetworkAgent.h"
- #import "YTKNetworkConfig.h"
- #import "YTKNetworkPrivate.h"
- #import <pthread/pthread.h>
- #if __has_include(<AFNetworking/AFHTTPSessionManager.h>)
- #import <AFNetworking/AFHTTPSessionManager.h>
- #else
- #import <AFNetworking/AFHTTPSessionManager.h>
- #endif
- #define Lock() pthread_mutex_lock(&_lock)
- #define Unlock() pthread_mutex_unlock(&_lock)
- #define kYTKNetworkIncompleteDownloadFolderName @"Incomplete"
- @implementation YTKNetworkAgent {
- AFHTTPSessionManager *_manager;
- YTKNetworkConfig *_config;
- AFJSONResponseSerializer *_jsonResponseSerializer;
- AFXMLParserResponseSerializer *_xmlParserResponseSerialzier;
- NSMutableDictionary<NSNumber *, YTKBaseRequest *> *_requestsRecord;
- dispatch_queue_t _processingQueue;
- pthread_mutex_t _lock;
- NSIndexSet *_allStatusCodes;
- }
- + (YTKNetworkAgent *)sharedAgent {
- static id sharedInstance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- sharedInstance = [[self alloc] init];
- });
- return sharedInstance;
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- _config = [YTKNetworkConfig sharedConfig];
- _manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:_config.sessionConfiguration];
- _requestsRecord = [NSMutableDictionary dictionary];
- _processingQueue = dispatch_queue_create("com.yuantiku.networkagent.processing", DISPATCH_QUEUE_CONCURRENT);
- _allStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(100, 500)];
- pthread_mutex_init(&_lock, NULL);
- _manager.securityPolicy = _config.securityPolicy;
- _manager.responseSerializer = [AFHTTPResponseSerializer serializer];
- // Take over the status code validation
- _manager.responseSerializer.acceptableStatusCodes = _allStatusCodes;
- _manager.completionQueue = _processingQueue;
- [_manager setTaskDidFinishCollectingMetricsBlock:_config.collectingMetricsBlock];
- }
- return self;
- }
- - (AFJSONResponseSerializer *)jsonResponseSerializer {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- _jsonResponseSerializer = [AFJSONResponseSerializer serializer];
- _jsonResponseSerializer.acceptableStatusCodes = _allStatusCodes;
- });
- return _jsonResponseSerializer;
- }
- - (AFXMLParserResponseSerializer *)xmlParserResponseSerialzier {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- _xmlParserResponseSerialzier = [AFXMLParserResponseSerializer serializer];
- _xmlParserResponseSerialzier.acceptableStatusCodes = _allStatusCodes;
- });
- return _xmlParserResponseSerialzier;
- }
- #pragma mark -
- - (NSString *)buildRequestUrl:(YTKBaseRequest *)request {
- NSParameterAssert(request != nil);
- NSString *detailUrl = [request requestUrl];
- NSURL *temp = [NSURL URLWithString:detailUrl];
- // If detailUrl is valid URL
- if (temp && temp.host && temp.scheme) {
- return detailUrl;
- }
- // Filter URL if needed
- NSArray *filters = [_config urlFilters];
- for (id<YTKUrlFilterProtocol> f in filters) {
- detailUrl = [f filterUrl:detailUrl withRequest:request];
- }
- NSString *baseUrl;
- if ([request useCDN]) {
- if ([request cdnUrl].length > 0) {
- baseUrl = [request cdnUrl];
- } else {
- baseUrl = [_config cdnUrl];
- }
- } else {
- if ([request baseUrl].length > 0) {
- baseUrl = [request baseUrl];
- } else {
- baseUrl = [_config baseUrl];
- }
- }
- // URL slash compatibility
- NSURL *url = [NSURL URLWithString:baseUrl];
- if (baseUrl.length > 0 && ![baseUrl hasSuffix:@"/"]) {
- url = [url URLByAppendingPathComponent:@""];
- }
- return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
- }
- - (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request {
- AFHTTPRequestSerializer *requestSerializer = nil;
- if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
- requestSerializer = [AFHTTPRequestSerializer serializer];
- } else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
- requestSerializer = [AFJSONRequestSerializer serializer];
- }
- requestSerializer.timeoutInterval = [request requestTimeoutInterval];
- requestSerializer.allowsCellularAccess = [request allowsCellularAccess];
- // If api needs server username and password
- NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
- if (authorizationHeaderFieldArray != nil) {
- [requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject
- password:authorizationHeaderFieldArray.lastObject];
- }
- // If api needs to add custom value to HTTPHeaderField
- NSDictionary<NSString *, NSString *> *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary];
- if (headerFieldValueDictionary != nil) {
- for (NSString *httpHeaderField in headerFieldValueDictionary.allKeys) {
- NSString *value = headerFieldValueDictionary[httpHeaderField];
- [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField];
- }
- }
- return requestSerializer;
- }
- - (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
- YTKRequestMethod method = [request requestMethod];
- NSString *url = [self buildRequestUrl:request];
- id param = request.requestArgument;
- AFConstructingBlock constructingBlock = [request constructingBodyBlock];
- AFURLSessionTaskProgressBlock uploadProgressBlock = [request uploadProgressBlock];
- AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
- switch (method) {
- case YTKRequestMethodGET:
- if (request.resumableDownloadPath) {
- return [self downloadTaskWithDownloadPath:request.resumableDownloadPath
- requestSerializer:requestSerializer
- URLString:url
- parameters:param
- progress:request.resumableDownloadProgressBlock
- error:error];
- } else {
- return [self dataTaskWithHTTPMethod:@"GET"
- requestSerializer:requestSerializer
- URLString:url
- parameters:param
- error:error];
- }
- case YTKRequestMethodPOST:
- return [self dataTaskWithHTTPMethod:@"POST"
- requestSerializer:requestSerializer
- URLString:url
- parameters:param
- uploadProgress:uploadProgressBlock
- constructingBodyWithBlock:constructingBlock
- error:error];
- case YTKRequestMethodHEAD:
- return [self dataTaskWithHTTPMethod:@"HEAD"
- requestSerializer:requestSerializer
- URLString:url
- parameters:param
- error:error];
- case YTKRequestMethodPUT:
- return [self dataTaskWithHTTPMethod:@"PUT"
- requestSerializer:requestSerializer
- URLString:url
- parameters:param
- uploadProgress:uploadProgressBlock
- constructingBodyWithBlock:constructingBlock
- error:error];
- case YTKRequestMethodDELETE:
- return [self dataTaskWithHTTPMethod:@"DELETE"
- requestSerializer:requestSerializer
- URLString:url
- parameters:param
- error:error];
- case YTKRequestMethodPATCH:
- return [self dataTaskWithHTTPMethod:@"PATCH"
- requestSerializer:requestSerializer
- URLString:url
- parameters:param
- error:error];
- }
- }
- - (void)addRequest:(YTKBaseRequest *)request {
- NSParameterAssert(request != nil);
- NSError * __autoreleasing requestSerializationError = nil;
- NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
- if (customUrlRequest) {
- __block NSURLSessionDataTask *dataTask = nil;
- dataTask = [_manager dataTaskWithRequest:customUrlRequest
- uploadProgress:nil
- downloadProgress:nil
- completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
- [self handleRequestResult:dataTask responseObject:responseObject error:error];
- }];
- request.requestTask = dataTask;
- } else {
- request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
- }
- if (requestSerializationError) {
- [self requestDidFailWithRequest:request error:requestSerializationError];
- return;
- }
- NSAssert(request.requestTask != nil, @"requestTask should not be nil");
- // Set request task priority
- if ([request.requestTask respondsToSelector:@selector(priority)]) {
- switch (request.requestPriority) {
- case YTKRequestPriorityHigh:
- request.requestTask.priority = NSURLSessionTaskPriorityHigh;
- break;
- case YTKRequestPriorityLow:
- request.requestTask.priority = NSURLSessionTaskPriorityLow;
- break;
- case YTKRequestPriorityDefault:
- /*!!fall through*/
- default:
- request.requestTask.priority = NSURLSessionTaskPriorityDefault;
- break;
- }
- }
- // Retain request
- YTKLog(@"Add request: %@", NSStringFromClass([request class]));
- [self addRequestToRecord:request];
- [request.requestTask resume];
- }
- - (void)cancelRequest:(YTKBaseRequest *)request {
- NSParameterAssert(request != nil);
- if (request.resumableDownloadPath && [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] != nil) {
- NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
- [requestTask cancelByProducingResumeData:^(NSData *resumeData) {
- NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
- [resumeData writeToURL:localUrl atomically:YES];
- }];
- } else {
- [request.requestTask cancel];
- }
- [self removeRequestFromRecord:request];
- [request clearCompletionBlock];
- }
- - (void)cancelAllRequests {
- Lock();
- NSArray *allKeys = [_requestsRecord allKeys];
- Unlock();
- if (allKeys && allKeys.count > 0) {
- NSArray *copiedKeys = [allKeys copy];
- for (NSNumber *key in copiedKeys) {
- Lock();
- YTKBaseRequest *request = _requestsRecord[key];
- Unlock();
- // We are using non-recursive lock.
- // Do not lock `stop`, otherwise deadlock may occur.
- [request stop];
- }
- }
- }
- - (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
- BOOL result = [request statusCodeValidator];
- if (!result) {
- if (error) {
- NSString *desc = [NSString stringWithFormat:@"Invalid status code (%ld)", (long)[request responseStatusCode]];
- *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:desc}];
- }
- return result;
- }
- id json = [request responseJSONObject];
- id validator = [request jsonValidator];
- if (json && validator) {
- result = [YTKNetworkUtils validateJSON:json withValidator:validator];
- if (!result) {
- if (error) {
- *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidJSONFormat userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON format"}];
- }
- return result;
- }
- }
- return YES;
- }
- - (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
- Lock();
- YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
- Unlock();
- // When the request is cancelled and removed from records, the underlying
- // AFNetworking failure callback will still kicks in, resulting in a nil `request`.
- //
- // Here we choose to completely ignore cancelled tasks. Neither success or failure
- // callback will be called.
- if (!request) {
- return;
- }
- YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));
- NSError * __autoreleasing serializationError = nil;
- NSError * __autoreleasing validationError = nil;
- NSError *requestError = nil;
- BOOL succeed = NO;
- request.responseObject = responseObject;
- if ([request.responseObject isKindOfClass:[NSData class]]) {
- request.responseData = responseObject;
- request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
- switch (request.responseSerializerType) {
- case YTKResponseSerializerTypeHTTP:
- // Default serializer. Do nothing.
- break;
- case YTKResponseSerializerTypeJSON:
- request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
- request.responseJSONObject = request.responseObject;
- break;
- case YTKResponseSerializerTypeXMLParser:
- request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
- break;
- }
- }
- if (error) {
- succeed = NO;
- requestError = error;
- } else if (serializationError) {
- succeed = NO;
- requestError = serializationError;
- } else {
- succeed = [self validateResult:request error:&validationError];
- requestError = validationError;
- }
- if (succeed) {
- [self requestDidSucceedWithRequest:request];
- } else {
- [self requestDidFailWithRequest:request error:requestError];
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- [self removeRequestFromRecord:request];
- [request clearCompletionBlock];
- });
- }
- - (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
- @autoreleasepool {
- [request requestCompletePreprocessor];
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- [request toggleAccessoriesWillStopCallBack];
- [request requestCompleteFilter];
- if (request.delegate != nil) {
- [request.delegate requestFinished:request];
- }
- if (request.successCompletionBlock) {
- request.successCompletionBlock(request);
- }
- [request toggleAccessoriesDidStopCallBack];
- });
- }
- - (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error {
- request.error = error;
- YTKLog(@"Request %@ failed, status code = %ld, error = %@",
- NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);
- // Save incomplete download data.
- NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
- NSURL *localUrl = nil;
- if (request.resumableDownloadPath) {
- localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
- }
- if (incompleteDownloadData && localUrl != nil) {
- [incompleteDownloadData writeToURL:localUrl atomically:YES];
- }
- // Load response from file and clean up if download task failed.
- if ([request.responseObject isKindOfClass:[NSURL class]]) {
- NSURL *url = request.responseObject;
- if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
- request.responseData = [NSData dataWithContentsOfURL:url];
- request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
- [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
- }
- request.responseObject = nil;
- }
- @autoreleasepool {
- [request requestFailedPreprocessor];
- }
- dispatch_async(dispatch_get_main_queue(), ^{
- [request toggleAccessoriesWillStopCallBack];
- [request requestFailedFilter];
- if (request.delegate != nil) {
- [request.delegate requestFailed:request];
- }
- if (request.failureCompletionBlock) {
- request.failureCompletionBlock(request);
- }
- [request toggleAccessoriesDidStopCallBack];
- });
- }
- - (void)addRequestToRecord:(YTKBaseRequest *)request {
- Lock();
- _requestsRecord[@(request.requestTask.taskIdentifier)] = request;
- Unlock();
- }
- - (void)removeRequestFromRecord:(YTKBaseRequest *)request {
- Lock();
- [_requestsRecord removeObjectForKey:@(request.requestTask.taskIdentifier)];
- YTKLog(@"Request queue size = %zd", [_requestsRecord count]);
- Unlock();
- }
- #pragma mark -
- - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
- requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
- URLString:(NSString *)URLString
- parameters:(id)parameters
- error:(NSError * _Nullable __autoreleasing *)error {
- return [self dataTaskWithHTTPMethod:method
- requestSerializer:requestSerializer
- URLString:URLString
- parameters:parameters
- uploadProgress:nil
- constructingBodyWithBlock:nil
- error:error];
- }
- - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
- requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
- URLString:(NSString *)URLString
- parameters:(id)parameters
- uploadProgress:(AFURLSessionTaskProgressBlock)uploadProgress
- constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
- error:(NSError * _Nullable __autoreleasing *)error {
- NSMutableURLRequest *request = nil;
- if (block) {
- request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error];
- } else {
- request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error];
- }
- __block NSURLSessionDataTask *dataTask = nil;
- dataTask = [_manager dataTaskWithRequest:request
- uploadProgress:uploadProgress
- downloadProgress:nil
- completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *_error) {
- [self handleRequestResult:dataTask responseObject:responseObject error:_error];
- }];
- return dataTask;
- }
- - (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath
- requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
- URLString:(NSString *)URLString
- parameters:(id)parameters
- progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
- error:(NSError * _Nullable __autoreleasing *)error {
- // add parameters to URL;
- NSMutableURLRequest *urlRequest = [requestSerializer requestWithMethod:@"GET" URLString:URLString parameters:parameters error:error];
- NSString *downloadTargetPath;
- BOOL isDirectory;
- if (![[NSFileManager defaultManager] fileExistsAtPath:downloadPath isDirectory:&isDirectory]) {
- isDirectory = NO;
- }
- // If targetPath is a directory, use the file name we got from the urlRequest.
- // Make sure downloadTargetPath is always a file, not directory.
- if (isDirectory) {
- NSString *fileName = [urlRequest.URL lastPathComponent];
- downloadTargetPath = [NSString pathWithComponents:@[downloadPath, fileName]];
- } else {
- downloadTargetPath = downloadPath;
- }
- // AFN use `moveItemAtURL` to move downloaded file to target path,
- // this method aborts the move attempt if a file already exist at the path.
- // So we remove the exist file before we start the download task.
- // https://github.com/AFNetworking/AFNetworking/issues/3775
- if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
- [[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
- }
- BOOL resumeSucceeded = NO;
- __block NSURLSessionDownloadTask *downloadTask = nil;
- NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:downloadPath];
- if (localUrl != nil) {
- BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:localUrl.path];
- NSData *data = [NSData dataWithContentsOfURL:localUrl];
- BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];
- BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid;
- // Try to resume with resumeData.
- // Even though we try to validate the resumeData, this may still fail and raise excecption.
- if (canBeResumed) {
- @try {
- downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
- return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
- } completionHandler:
- ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
- [self handleRequestResult:downloadTask responseObject:filePath error:error];
- }];
- resumeSucceeded = YES;
- } @catch (NSException *exception) {
- YTKLog(@"Resume download failed, reason = %@", exception.reason);
- resumeSucceeded = NO;
- }
- }
- }
- if (!resumeSucceeded) {
- downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
- return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
- } completionHandler:
- ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
- [self handleRequestResult:downloadTask responseObject:filePath error:error];
- }];
- }
- return downloadTask;
- }
- #pragma mark - Resumable Download
- - (NSString *)incompleteDownloadTempCacheFolder {
- NSFileManager *fileManager = [NSFileManager new];
- NSString *cacheFolder = [NSTemporaryDirectory() stringByAppendingPathComponent:kYTKNetworkIncompleteDownloadFolderName];
- BOOL isDirectory = NO;
- if ([fileManager fileExistsAtPath:cacheFolder isDirectory:&isDirectory] && isDirectory) {
- return cacheFolder;
- }
- NSError *error = nil;
- if ([fileManager createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error] && error == nil) {
- return cacheFolder;
- }
- YTKLog(@"Failed to create cache directory at %@ with error: %@", cacheFolder, error != nil ? error.localizedDescription : @"unkown");
- return nil;
- }
- - (NSURL *)incompleteDownloadTempPathForDownloadPath:(NSString *)downloadPath {
- if (downloadPath == nil || downloadPath.length == 0) {
- return nil;
- }
- NSString *tempPath = nil;
- NSString *md5URLString = [YTKNetworkUtils md5StringFromString:downloadPath];
- tempPath = [[self incompleteDownloadTempCacheFolder] stringByAppendingPathComponent:md5URLString];
- return tempPath == nil ? nil : [NSURL fileURLWithPath:tempPath];
- }
- #pragma mark - Testing
- - (AFHTTPSessionManager *)manager {
- return _manager;
- }
- - (void)resetURLSessionManager {
- _manager = [AFHTTPSessionManager manager];
- }
- - (void)resetURLSessionManagerWithConfiguration:(NSURLSessionConfiguration *)configuration {
- _manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
- }
- @end
|