Files
yinmeng-ios/xplan-ios/Base/Tool/WebResource/Downloader/YYWebResourceDownloader.m
2022-03-10 18:55:18 +08:00

416 lines
16 KiB
Objective-C

//
// YYWebResourceDownloader.m
// YYMobileFramework
//
// Created by wuwei on 14/6/12.
// Copyright (c) 2014年 YY Inc. All rights reserved.
//
#import "YYWebResourceDownloader.h"
#import <AFURLSessionManager.h>
#import "FBKVOController.h"
#if TRAFFIC_DATA
#import "../../../../YYMobileCore/YYMobileCore/Cores/CoreManager.h"
#import "../../../../YYMobileCore/YYMobileCore/Cores/Carrier/ICarrierService.h"
#import "../../../../YYMobileCore/YYMobileCore/Cores/Carrier/CarrierServiceTypes.h"
#endif
#define ENABLE_UNICOM_PROXY 0
static NSString * const kProgressCallbackKey = @"progress";
static NSString * const kCompletionCallbackKey = @"completed";
#if TRAFFIC_DATA
extern NSString * const CarrierDataTrafficPackageSubscribeStateUpdateNotification;
#endif
static const NSTimeInterval kDefaultDownloadTimeout = 15.0f;
@interface YYWebResourceDownloadOperation ()
- (instancetype)initWithTask:(NSURLSessionTask *)task;
- (instancetype)initWithTask:(NSURLSessionTask *)task cancelledBlock:(dispatch_block_t)cancelBlock;
@end
@implementation YYWebResourceDownloadOperation
{
NSURLSessionTask *_task;
dispatch_block_t _cancelBlock;
}
- (instancetype)initWithTask:(NSURLSessionTask *)task {
self = [super init];
if (self) {
_task = task;
}
return self;
}
- (instancetype)initWithTask:(NSURLSessionTask *)task cancelledBlock:(dispatch_block_t)cancel
{
self = [self initWithTask:task];
if (self) {
_cancelBlock = cancel;
}
return self;
}
- (void)cancel {
[super cancel];
[_task cancel];
if (_cancelBlock) {
_cancelBlock();
}
}
@end
@interface YYWebResourceDownloader ()
@property (nonatomic, strong, readonly) AFURLSessionManager *sessionManager;
@property (nonatomic, strong) NSURLSessionConfiguration *sessionConfiguration;
@property (nonatomic, strong, readonly) dispatch_queue_t barrierQueue;
@property (nonatomic, strong, readonly) NSMutableDictionary *URLCallbacks;
@property (nonatomic, strong, readonly) NSMutableDictionary *HTTPHeaders;
@property (nonatomic, strong, readonly) NSMapTable *downloadingTasks;
@end
@implementation YYWebResourceDownloader
@synthesize barrierQueue = _barrierQueue;
@synthesize URLCallbacks = _URLCallbacks;
@synthesize HTTPHeaders =_HTTPHeaders;
@synthesize downloaderName = _downloaderName;
@synthesize sessionManager = _sessionManager;
+ (instancetype)sharedDownloader
{
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] initWithName:@"default"];
});
return instance;
}
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
_downloaderName = name;
_downloadingTasks = [NSMapTable strongToWeakObjectsMapTable];
#if TRAFFIC_DATA
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(OnCarrierDataTrafficPackageSubscribeStateUpdate:) name:CarrierDataTrafficPackageSubscribeStateUpdateNotification object:nil];
#endif
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
/*
defaultConfiguration.connectionProxyDictionary = @{
(NSString *)kCFStreamPropertyHTTPProxyHost: @"huanjushidai.gzproxy.10155.com",
(NSString *)kCFStreamPropertyHTTPProxyPort: @(8080)};
defaultConfiguration.HTTPAdditionalHeaders = @{@"Authorization": @"Basic MzAwMDAwNDU0NDo5OEI4NTU5MDI0ODYwQkE1NDE2MTUwNDM4NzA2NjdGMQ=="};
*/
_sessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
_downloadTimeout = kDefaultDownloadTimeout;
_URLCallbacks = [NSMutableDictionary dictionary];
_HTTPHeaders = [NSMutableDictionary dictionary];
// Create a queue using the downloader name
name = name ? : @"<anonymous>";
NSString *queueName = [NSString stringWithFormat:@"com.yy.webResourceDownloader.%@.barrierQueue", name];
_barrierQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
static char kBarrierQueueSpecificKey;
dispatch_queue_set_specific(self.barrierQueue, &kBarrierQueueSpecificKey, (__bridge void *)self, NULL);
}
return self;
}
- (instancetype)init
{
return [self initWithName:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field
{
if (value) {
self.HTTPHeaders[field] = value;
}
else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field
{
return self.HTTPHeaders[field];
}
- (YYWebResourceDownloadOperation *)downloadWithURL:(NSURL *)url
fileName:(NSString *)fileName
options:(YYWebResourceDownloaderOptions)options
progress:(YYWebResourceDownloaderProgressBlock)progress
completion:(YYWebResourceDownloaderCompletionBlock)completion
{
#if TRAFFIC_DATA
//处理缓存的Session的代理配置
if(!_sessionManager)
{
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
BOOL isDataTrafficeFree = [GetCoreI(ICarrierService) isDataTrafficFree];
if(isDataTrafficeFree)
{
CarrierProxyConfiguration* httpProxyConfiguration = [GetCoreI(ICarrierService) getProxyConfiguration];
if(httpProxyConfiguration)
{
defaultConfiguration.connectionProxyDictionary = @{ (NSString *)kCFStreamPropertyHTTPProxyHost: httpProxyConfiguration.serverAddress,
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpProxyConfiguration.serverPort)};
defaultConfiguration.HTTPAdditionalHeaders = @{@"Authorization": [NSString stringWithFormat:@"Basic %@", httpProxyConfiguration.authenticationString]};
}
self.sessionConfiguration = defaultConfiguration;
}
_sessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:self.sessionConfiguration];
[_sessionManager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest *(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request) {
NSMutableURLRequest *redirectRequest = [request mutableCopy];
if(isDataTrafficeFree)
{
CarrierProxyConfiguration* httpProxyConfiguration = [GetCoreI(ICarrierService) getProxyConfiguration];
[redirectRequest addValue:[NSString stringWithFormat:@"Basic %@", httpProxyConfiguration.authenticationString] forHTTPHeaderField:@"Authorization"];
}
return redirectRequest;
}];
}
#endif
__block YYWebResourceDownloadOperation *downloadOperation = nil;
__weak YYWebResourceDownloader *wself = self;
[self _addProgressCallback:progress andCompletionBlock:completion forURL:url createCallback:^{
NSTimeInterval timeoutInterval = wself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = kDefaultDownloadTimeout;
}
// In order to prevent from potential duplicate caching (NSURLCache + YYImageCache), we disable the cache for image requests if told otherwise
NSURLRequestCachePolicy cachePolicy = (options & YYWebResourceDownloaderUseNSURLCache) ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & YYWebResourceDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headerFilter) {
request.allHTTPHeaderFields = wself.headerFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = [wself.HTTPHeaders copy];
}
__block NSProgress *progress;
__block FBKVOController *controller = [FBKVOController controllerWithObserver:wself];
__block NSURLSessionTask *sessionTask = [wself.sessionManager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
progress = downloadProgress;
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
// return [targetPath URLByAppendingPathExtension:@"download"];
NSString *fullPath;
if (fileName.length > 0 || fileName) {
fullPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:fileName];
}else {
fullPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
}
return [NSURL fileURLWithPath:fullPath];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
if (error) {
// [YYLogger debug:TAG(THTTP,TNetSend) message:@"[Task: %@] Download %@ failed with error %@", sessionTask, url, error];
} else {
// [YYLogger debug:TAG(THTTP,TNetSend) message:@"[Task: %@] Download %@ successfully, store to %@", sessionTask, url, filePath];
}
if (error.code == NSURLErrorCancelled) {
// Cancel由YYWebResourceOperation的cancelBlock处理
return;
}
if (!wself) return; // return if deallocated
__strong __typeof__(wself) sself = wself;
NSArray *callbacksForURL = [sself _callbacksForURL:url];
[sself _removeCallbacksForURL:url];
for (NSDictionary *callbacks in callbacksForURL) {
YYWebResourceDownloaderCompletionBlock complete = callbacks[kCompletionCallbackKey];
if (complete) {
complete(filePath, error, YES);
}
}
// 移除文件
[[NSFileManager defaultManager] removeItemAtURL:filePath error:nil];
// Remove KVO
controller = nil;
}];
// Observe the fractionCompleted property of the progress
[controller observe:progress keyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
if (!wself) return; // return if deallocated
__strong __typeof__(wself) sself = wself;
NSArray *callbacksForURL = [sself _callbacksForURL:url];
for (NSDictionary *callbacks in callbacksForURL) {
YYWebResourceDownloaderProgressBlock progressBlock = callbacks[kProgressCallbackKey];
if (progressBlock) {
progressBlock(progress.completedUnitCount, progress.totalUnitCount, progress.fractionCompleted);
}
}
}];
// [YYLogger debug:TAG(THTTP,TNetSend)message:@"[Task: %@] download %@ DID START!", sessionTask, url];
downloadOperation = [[YYWebResourceDownloadOperation alloc] initWithTask:sessionTask cancelledBlock:^{
__strong __typeof__(wself) sself = wself;
if (sself) {
[sself _removeCallbacksForURL:url];
}
}];
// [wself.downloadingTasks setObject:downloadOperation forKey:url];
// start the task
[sessionTask resume];
}];
return downloadOperation;
}
- (void)_addProgressCallback:(YYWebResourceDownloaderProgressBlock)progress
andCompletionBlock:(YYWebResourceDownloaderCompletionBlock)completion
forURL:(NSURL *)url
createCallback:(dispatch_block_t)createCallback
{
NSParameterAssert(createCallback);
if (url == nil) {
if (completion != nil) {
completion(nil, nil, NO);
}
return;
}
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
id task = [self.downloadingTasks objectForKey:url];
// [YYLogger debug:TAG(THTTP,TNetSend) message:@"add Callbacks for url %@, existing task: %@, isFirst: %d", url, task, first];
// Handle single download of simulaneous download request for the same URL
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progress) {
callbacks[kProgressCallbackKey] = [progress copy];
}
if (completion) {
callbacks[kCompletionCallbackKey] = [completion copy];
}
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
if (first) {
createCallback();
}
});
}
- (NSArray *)_callbacksForURL:(NSURL *)url {
__block NSArray *callbacksForURL;
dispatch_sync(self.barrierQueue, ^{
callbacksForURL = self.URLCallbacks[url];
});
return [callbacksForURL copy];
}
- (void)_removeCallbacksForURL:(NSURL *)url {
dispatch_barrier_async(self.barrierQueue, ^{
[self.URLCallbacks removeObjectForKey:url];
});
}
#if TRAFFIC_DATA
-(void) OnCarrierDataTrafficPackageSubscribeStateUpdate:(NSNotification*)notification
{
CarrierProxyConfiguration* httpProxyConfiguration = [notification object];
if (httpProxyConfiguration && httpProxyConfiguration.serverAddress.length > 0)
{
NSURLSessionConfiguration *proxyConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
proxyConfiguration.connectionProxyDictionary = @{ (NSString *)kCFStreamPropertyHTTPProxyHost: httpProxyConfiguration.serverAddress,
(NSString *)kCFStreamPropertyHTTPProxyPort: @(httpProxyConfiguration.serverPort)};
proxyConfiguration.HTTPAdditionalHeaders = @{@"Authorization": [NSString stringWithFormat:@"Basic %@", httpProxyConfiguration.authenticationString]};
self.sessionConfiguration = proxyConfiguration;
if(!_sessionManager)
{
return;
}
if([_sessionManager.tasks count] > 0)
{
[_sessionManager invalidateSessionCancelingTasks:false];
[[NSNotificationCenter defaultCenter] addObserverForName:AFURLSessionDidInvalidateNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification *note) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFURLSessionDidInvalidateNotification object:nil];
_sessionManager = nil;
}];
}
else
{
_sessionManager = nil;
}
}
else
{
if(!self.sessionConfiguration)
return;
if([_sessionManager.tasks count] > 0)
{
[_sessionManager invalidateSessionCancelingTasks:false];
[[NSNotificationCenter defaultCenter] addObserverForName:AFURLSessionDidInvalidateNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(__unused NSNotification *note) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFURLSessionDidInvalidateNotification object:nil];
self.sessionConfiguration = nil;
_sessionManager = nil;
}];
}
else
{
self.sessionConfiguration = nil;
_sessionManager = nil;
}
}
}
#endif
@end