// // HSNSURLSession.m // HSDownloadManagerExample // // Created by hans on 15/8/4. // Copyright © 2015年 hans. All rights reserved. // #import "WSDownloadSession.h" #define CunCount 8*1024 @interface WSDownloadSession() @property (nonatomic,copy)NSString *msgUUID; /** 流 */ @property (nonatomic, strong) NSOutputStream *stream; //下载任务 @property (nonatomic, strong) NSURLSessionDataTask *task; @property (nonatomic , strong) NSMutableURLRequest *request; /** 下载地址 */ @property (nonatomic, copy) NSString *url; /** * 缓存根路径/A/ */ @property (nonatomic , copy) NSString *cacheRootPath; /** * 绝对路径(最后的两个绝对,用来拼接)xxj/之后的路径,(需要传出来,所以带上filename) */ @property (nonatomic , copy) NSString *localPath; @property (nonatomic , copy) NSString *attachName; @property (nonatomic , copy) NSString *fileMd5; /** * 附件完整路径/A/xx.yy */ @property (nonatomic , copy) NSString *attachFullPath; /** * 文件大小描述文件完全路径 */ @property (nonatomic , copy) NSString *attachTotalLengthFileFullPath; /** * 已下载长度 */ @property (nonatomic , assign)NSInteger downloadedLength; /** 获得服务器这次请求 返回数据的总长度 */ @property (nonatomic, assign) NSInteger totalLengthSaveFromServer; //请求时文件模型告知的实际大小. @property (nonatomic , assign) NSInteger totalLengthNeedDownload; @property (nonatomic, assign)NSURLSession *session; @end //取消 static const NSUInteger SessionCancerCode = -999; //超时 @implementation WSDownloadSession + (id)oneDownloadSessionWithFileUrlString:(NSString *)downloadUrl savePath:(NSString *)path localPath:(NSString *)localPath fileMd5:(NSString *)md5 UUID:(NSString *)uuid fileName:(NSString *)fileName downloadedSize:(NSInteger)downloadedSize fileSize:(NSInteger)fileSize progressblock:(DownloadProgressBlock)progressBlock successblock:(DownloadSuccessBlock)successblock failedBlock:(DownloadFailedBlock)failedBlock; { WSDownloadSession *wsDowunloadSession = [[WSDownloadSession alloc]initOneDownloadSessionWithFileUrlString:downloadUrl savePath:path localPath:localPath fileMd5:md5 UUID:uuid fileName:fileName downloadedSize:downloadedSize fileSize:fileSize progressblock:progressBlock successblock:successblock failedBlock:failedBlock]; return wsDowunloadSession; } //SAVE PATH: .../Library/xxj/cloudstorage //localpath /cloudstorage/filename // savepath/ localpath /filename //...library/xxj/cloudStorage/dada78dafada/xxx.filename - (instancetype)initOneDownloadSessionWithFileUrlString:(NSString *)downloadUrl savePath:(NSString *)savePath localPath:(NSString *)localPath fileMd5:(NSString *)md5 UUID:(NSString *)uuid fileName:(NSString *)fileName downloadedSize:(NSInteger)downloadedSize fileSize:(NSInteger)fileSize progressblock:(DownloadProgressBlock)progressBlock successblock:(DownloadSuccessBlock)successblock failedBlock:(DownloadFailedBlock)failedBlock { if (self = [super init]) { self.msgUUID = uuid; self.fileMd5 = md5; self.url = downloadUrl; self.totalLengthNeedDownload = fileSize; /** * 实际效果不大?,还是读取实际的大小. */ self.downloadedLength = downloadedSize; self.downloadProgressBlock = progressBlock; self.downloadSuccessBlock = successblock; self.downloadFailedBlock = failedBlock; self.attachName = fileName; //localpath即本地绝对localpath,localpath可以不同,name可以相同. self.localPath = localPath;//![localPath isEqualToString:fileName]?[NSString stringWithFormat:@"%@/%@",localPath,fileName]:fileName;//存入数据库中. //下载完注意删除 self.attachTotalLengthFileFullPath = [savePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@_totalLength.plist",fileName,md5]]; self.attachFullPath = [NSString stringWithFormat:@"%@/%@",savePath,self.localPath]; self.cacheRootPath = self.attachFullPath.stringByDeletingLastPathComponent; [self createCacheDirectory]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc]init]]; //写入文件专用. NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:self.attachFullPath append:YES]; //中文字符的链接处理下 // 创建请求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[downloadUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; self.stream = stream; self.session = session; self.request = request; [self sessionWithNewRequest]; } return self; } - (void)sessionWithNewRequest { //中文字符的链接处理下 // 创建请求 NSInteger maxDownloadLength = self.downloadedLength + CunCount; if (maxDownloadLength > self.totalLengthNeedDownload) { maxDownloadLength = self.totalLengthNeedDownload; } // 设置请求头 NSString *range = [NSString stringWithFormat:@"bytes=%ld-%ld",self.downloadedLength,maxDownloadLength]; [self.request setValue:range forHTTPHeaderField:@"Range"]; NSURLSessionDataTask *task = [self.session dataTaskWithRequest:self.request]; [task resume]; self.task = task; } /** * 创建缓存目录文件 */ - (void)createCacheDirectory { NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:self.cacheRootPath]) { [fileManager createDirectoryAtPath:self.cacheRootPath withIntermediateDirectories:YES attributes:nil error:NULL]; } } //实时获取文件大小 - (NSInteger)downloadedLength { return [[[NSFileManager defaultManager] attributesOfItemAtPath:self.attachFullPath error:nil][NSFileSize] integerValue]; } //挂起. - (void)handle { if (self.task.state == NSURLSessionTaskStateRunning) { [self pause]; } else { [self start]; } } /** * 开始下载 */ - (void)start { [self.task resume]; } /** * 暂停下载-如果url相同的话就有问题. */ - (void)pause { //可能超时,show error,直接cancer吧。 if (!self.task) { return; } [self.task suspend]; } - (void)cancer { if (!self.task) { return; } [self.session finishTasksAndInvalidate]; [self.session invalidateAndCancel]; // 关闭流 [self.stream close]; self.stream.delegate = nil; // 清除任务 [self.task cancel]; self.task = nil; self.stream = nil; self.session = nil; } /** * 判断该文件是否下载完成 */ - (BOOL)isCompletion { if (self.downloadedLength == self.totalLengthNeedDownload ) { return YES; } return NO; } /** * 根据服务端返回的总大小生成的文件 获取该资源总大小 */ - (NSInteger)fileTotalLength { return [[NSDictionary dictionaryWithContentsOfFile:self.attachTotalLengthFileFullPath][self.attachName] integerValue]; } #pragma mrak -- #pragma mark -- URLSessionDelegate /** * 接收到响应 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { // 打开流 if (self.stream.streamStatus != NSStreamStatusOpen) { [self.stream open]; } // 接收这个请求,允许接收服务器的数据 completionHandler(NSURLSessionResponseAllow); } /** * 接收到服务器返回的数据 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { // 写入数据 [self.stream write:data.bytes maxLength:data.length]; self.downloadedLength += data.length; // 下载进度 NSUInteger receivedSize = self.downloadedLength; NSUInteger expectedSize = self.totalLengthNeedDownload; CGFloat progress = 1.0 * receivedSize / expectedSize; if (self.downloadProgressBlock) { self.downloadProgressBlock(receivedSize, expectedSize, progress,self.attachFullPath,self.msgUUID); } } /** * 请求完毕(成功|失败) */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { __weak typeof(self)weakself = self; task = nil; if ([self isCompletion]) { // 下载完成 if (weakself.downloadSuccessBlock) { weakself.downloadSuccessBlock(weakself.downloadedLength,weakself.localPath,weakself.msgUUID); } [self closeSession]; }else if (self.downloadedLength < self.totalLengthNeedDownload && !error) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if(weakself.session)[weakself sessionWithNewRequest]; }); }else if (error && error.code != SessionCancerCode){ // 下载失败 if (self.downloadFailedBlock) { self.downloadFailedBlock(@"error",self.msgUUID); } [self closeSession]; } } - (void)closeSession { [self.session finishTasksAndInvalidate]; [self.session invalidateAndCancel]; // 关闭流 [self.stream close]; self.stream.delegate = nil; // 清除任务 [self.task cancel]; self.request = nil; self.task = nil; self.stream = nil; self.session = nil; } - (void)dealloc { } @end