// ZFDownloadManager.m // // Copyright (c) 2016年 任子丰 ( http://github.com/renzifeng ) // // 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 "ZFDownloadManager.h" static ZFDownloadManager *sharedDownloadManager = nil; @interface ZFDownloadManager () /** 本地临时文件夹文件的个数 */ @property (nonatomic,assign ) NSInteger count; /** 已下载完成的文件列表(文件对象)*/ @property (atomic,strong ) NSMutableArray *finishedlist; /** 正在下载的文件列表(ASIHttpRequest对象)*/ @property (atomic,strong ) NSMutableArray *downinglist; /** 未下载完成的临时文件数组(文件对象)*/ @property (atomic,strong ) NSMutableArray *filelist; /** 下载文件的模型 */ @property (nonatomic,strong ) ZFFileModel *fileInfo; @end @implementation ZFDownloadManager #pragma mark - init methods + (ZFDownloadManager *)sharedDownloadManager { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedDownloadManager = [[self alloc] init]; }); return sharedDownloadManager; } - (instancetype)init { self = [super init]; if (self) { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSString * max = [userDefaults valueForKey:kMaxRequestCount]; if (max == nil) { [userDefaults setObject:@"3" forKey:kMaxRequestCount]; max = @"3"; } [userDefaults synchronize]; _maxCount = [max integerValue]; _filelist = [[NSMutableArray alloc]init]; _downinglist = [[NSMutableArray alloc] init]; _finishedlist = [[NSMutableArray alloc] init]; _count = 0; [self loadFinishedfiles]; [self loadTempfiles]; } return self; } - (void)cleanLastInfo { for (ZFHttpRequest *request in _downinglist) { if([request isExecuting]) [request cancel]; } [self saveFinishedFile]; [_downinglist removeAllObjects]; [_finishedlist removeAllObjects]; [_filelist removeAllObjects]; } #pragma mark - 创建一个下载任务 - (void)downFileUrl:(NSString *)url filename:(NSString *)name fileimage:(UIImage *)image { // 因为是重新下载,则说明肯定该文件已经被下载完,或者有临时文件正在留着,所以检查一下这两个地方,存在则删除掉 _fileInfo = [[ZFFileModel alloc] init]; if (!name) { name = [url lastPathComponent]; } _fileInfo.fileName = name; _fileInfo.fileURL = url; NSDate *myDate = [NSDate date]; _fileInfo.time = [ZFCommonHelper dateToString:myDate]; _fileInfo.fileType = [name pathExtension]; _fileInfo.fileimage = image; _fileInfo.downloadState = ZFDownloading; _fileInfo.error = NO; _fileInfo.tempPath = TEMP_PATH(name); if ([ZFCommonHelper isExistFile:FILE_PATH(name)]) { // 已经下载过一次 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"该文件已下载,是否重新下载?" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; dispatch_async(dispatch_get_main_queue(), ^{ [alert show]; }); return; } // 存在于临时文件夹里 NSString *tempfilePath = [TEMP_PATH(name) stringByAppendingString:@".plist"]; if ([ZFCommonHelper isExistFile:tempfilePath]) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"该文件已经在下载列表中了,是否重新下载?" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil]; dispatch_async(dispatch_get_main_queue(), ^{ [alert show]; }); return; } // 若不存在文件和临时文件,则是新的下载 [self.filelist addObject:_fileInfo]; // 开始下载 [self startLoad]; if (self.VCdelegate && [self.VCdelegate respondsToSelector:@selector(allowNextRequest)]) { [self.VCdelegate allowNextRequest]; } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"该文件成功添加到下载队列" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil]; dispatch_async(dispatch_get_main_queue(), ^{ [alert show]; }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [alert dismissWithClickedButtonIndex:0 animated:YES]; }); } return; } #pragma mark - 下载开始 - (void)beginRequest:(ZFFileModel *)fileInfo isBeginDown:(BOOL)isBeginDown { for(ZFHttpRequest *tempRequest in self.downinglist) { /** * 注意这里判读是否是同一下载的方法,asihttprequest有三种url: url,originalurl,redirectURL * 经过实践,应该使用originalurl,就是最先获得到的原下载地址 **/ if([[[tempRequest.url absoluteString] lastPathComponent] isEqualToString:[fileInfo.fileURL lastPathComponent]]) { if ([tempRequest isExecuting] && isBeginDown) { return; } else if ([tempRequest isExecuting] && !isBeginDown) { [tempRequest setUserInfo:[NSDictionary dictionaryWithObject:fileInfo forKey:@"File"]]; [tempRequest cancel]; [self.downloadDelegate updateCellProgress:tempRequest]; return; } } } [self saveDownloadFile:fileInfo]; // 按照获取的文件名获取临时文件的大小,即已下载的大小 NSFileManager *fileManager = [NSFileManager defaultManager]; NSData *fileData = [fileManager contentsAtPath:fileInfo.tempPath]; NSInteger receivedDataLength = [fileData length]; fileInfo.fileReceivedSize = [NSString stringWithFormat:@"%zd", receivedDataLength]; // NSLog(@"start down:已经下载:%@",fileInfo.fileReceivedSize); ZFHttpRequest *midRequest = [[ZFHttpRequest alloc] initWithURL:[NSURL URLWithString:fileInfo.fileURL]]; midRequest.downloadDestinationPath = FILE_PATH(fileInfo.fileName); midRequest.temporaryFileDownloadPath = fileInfo.tempPath; midRequest.delegate = self; [midRequest setUserInfo:[NSDictionary dictionaryWithObject:fileInfo forKey:@"File"]];//设置上下文的文件基本信息 if (isBeginDown) { [midRequest startAsynchronous]; } // 如果文件重复下载或暂停、继续,则把队列中的请求删除,重新添加 BOOL exit = NO; for (ZFHttpRequest *tempRequest in self.downinglist) { if([[[tempRequest.url absoluteString] lastPathComponent] isEqualToString:[fileInfo.fileURL lastPathComponent]]) { [self.downinglist replaceObjectAtIndex:[_downinglist indexOfObject:tempRequest] withObject:midRequest]; exit = YES; break; } } if (!exit) { [self.downinglist addObject:midRequest]; } [self.downloadDelegate updateCellProgress:midRequest]; } #pragma mark - 存储下载信息到一个plist文件 - (void)saveDownloadFile:(ZFFileModel*)fileinfo { NSData *imagedata = UIImagePNGRepresentation(fileinfo.fileimage); NSDictionary *filedic = [NSDictionary dictionaryWithObjectsAndKeys:fileinfo.fileName,@"filename", fileinfo.fileURL,@"fileurl", fileinfo.time,@"time", fileinfo.fileSize,@"filesize", fileinfo.fileReceivedSize,@"filerecievesize", imagedata,@"fileimage",nil]; NSString *plistPath = [fileinfo.tempPath stringByAppendingPathExtension:@"plist"]; if (![filedic writeToFile:plistPath atomically:YES]) { NSLog(@"write plist fail"); } } #pragma mark - 自动处理下载状态的算法 /*下载状态的逻辑是这样的:三种状态,下载中,等待下载,停止下载 当超过最大下载数时,继续添加的下载会进入等待状态,当同时下载数少于最大限制时会自动开始下载等待状态的任务。 可以主动切换下载状态 所有任务以添加时间排序。 */ - (void)startLoad { NSInteger num = 0; NSInteger max = _maxCount; for (ZFFileModel *file in _filelist) { if (!file.error) { if (file.downloadState == ZFDownloading) { if (num >= max) { file.downloadState = ZFWillDownload; } else { num++; } } } } if (num < max) { for (ZFFileModel *file in _filelist) { if (!file.error) { if (file.downloadState == ZFWillDownload) { num++; if (num>max) { break; } file.downloadState = ZFDownloading; } } } } for (ZFFileModel *file in _filelist) { if (!file.error) { if (file.downloadState == ZFDownloading) { [self beginRequest:file isBeginDown:YES]; } else { [self beginRequest:file isBeginDown:NO]; } } } self.count = [_filelist count]; } #pragma mark - 恢复下载 - (void)resumeRequest:(ZFHttpRequest *)request { NSInteger max = _maxCount; ZFFileModel *fileInfo = [request.userInfo objectForKey:@"File"]; NSInteger downingcount = 0; NSInteger indexmax = -1; for (ZFFileModel *file in _filelist) { if (file.downloadState == ZFDownloading) { downingcount++; if (downingcount==max) { indexmax = [_filelist indexOfObject:file]; } } } // 此时下载中数目是否是最大,并获得最大时的位置Index if (downingcount == max) { ZFFileModel *file = [_filelist objectAtIndex:indexmax]; if (file.downloadState == ZFDownloading) { file.downloadState = ZFWillDownload; } } // 中止一个进程使其进入等待 for (ZFFileModel *file in _filelist) { if ([file.fileName isEqualToString:fileInfo.fileName]) { file.downloadState = ZFDownloading; file.error = NO; } } // 重新开始此下载 [self startLoad]; } #pragma mark - 暂停下载 - (void)stopRequest:(ZFHttpRequest *)request { NSInteger max = self.maxCount; if([request isExecuting]) { [request cancel]; } ZFFileModel *fileInfo = [request.userInfo objectForKey:@"File"]; for (ZFFileModel *file in _filelist) { if ([file.fileName isEqualToString:fileInfo.fileName]) { file.downloadState = ZFStopDownload; break; } } NSInteger downingcount = 0; for (ZFFileModel *file in _filelist) { if (file.downloadState == ZFDownloading) { downingcount++; } } if (downingcount < max) { for (ZFFileModel *file in _filelist) { if (file.downloadState == ZFWillDownload){ file.downloadState = ZFDownloading; break; } } } [self startLoad]; } #pragma mark - 删除下载 - (void)deleteRequest:(ZFHttpRequest *)request { BOOL isexecuting = NO; if([request isExecuting]) { [request cancel]; isexecuting = YES; } NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; ZFFileModel *fileInfo = (ZFFileModel*)[request.userInfo objectForKey:@"File"]; NSString *path = fileInfo.tempPath; NSString *configPath = [NSString stringWithFormat:@"%@.plist",path]; [fileManager removeItemAtPath:path error:&error]; [fileManager removeItemAtPath:configPath error:&error]; if(!error){ NSLog(@"%@",[error description]);} NSInteger delindex = -1; for (ZFFileModel *file in _filelist) { if ([file.fileName isEqualToString:fileInfo.fileName]) { delindex = [_filelist indexOfObject:file]; break; } } if (delindex != NSNotFound) [_filelist removeObjectAtIndex:delindex]; [_downinglist removeObject:request]; if (isexecuting) { [self startLoad]; } self.count = [_filelist count]; } #pragma mark - 可能的UI操作接口 - (void)clearAllFinished { [_finishedlist removeAllObjects]; } - (void)clearAllRquests { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; for (ZFHttpRequest *request in _downinglist) { if([request isExecuting]) [request cancel]; ZFFileModel *fileInfo = (ZFFileModel*)[request.userInfo objectForKey:@"File"]; NSString *path = fileInfo.tempPath;; NSString *configPath = [NSString stringWithFormat:@"%@.plist",path]; [fileManager removeItemAtPath:path error:&error]; [fileManager removeItemAtPath:configPath error:&error]; if(!error) { NSLog(@"%@",[error description]); } } [_downinglist removeAllObjects]; [_filelist removeAllObjects]; } - (void)startAllDownloads { for (ZFHttpRequest *request in _downinglist) { if([request isExecuting]) { [request cancel]; } ZFFileModel *fileInfo = [request.userInfo objectForKey:@"File"]; fileInfo.downloadState = ZFDownloading; } [self startLoad]; } - (void)pauseAllDownloads { for (ZFHttpRequest *request in _downinglist) { if([request isExecuting]) { [request cancel]; } ZFFileModel *fileInfo = [request.userInfo objectForKey:@"File"]; fileInfo.downloadState = ZFStopDownload; } [self startLoad]; } #pragma mark - 从这里获取上次未完成下载的信息 /* 将本地的未下载完成的临时文件加载到正在下载列表里,但是不接着开始下载 */ - (void)loadTempfiles { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSArray *filelist = [fileManager contentsOfDirectoryAtPath:TEMP_FOLDER error:&error]; if(!error) { NSLog(@"%@",[error description]); } NSMutableArray *filearr = [[NSMutableArray alloc]init]; for(NSString *file in filelist) { NSString *filetype = [file pathExtension]; if([filetype isEqualToString:@"plist"]) [filearr addObject:[self getTempfile:TEMP_PATH(file)]]; } NSArray* arr = [self sortbyTime:(NSArray *)filearr]; [_filelist addObjectsFromArray:arr]; [self startLoad]; } - (ZFFileModel *)getTempfile:(NSString *)path { NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:path]; ZFFileModel *file = [[ZFFileModel alloc]init]; file.fileName = [dic objectForKey:@"filename"]; file.fileType = [file.fileName pathExtension ]; file.fileURL = [dic objectForKey:@"fileurl"]; file.fileSize = [dic objectForKey:@"filesize"]; file.fileReceivedSize = [dic objectForKey:@"filerecievesize"]; file.tempPath = TEMP_PATH(file.fileName); file.time = [dic objectForKey:@"time"]; file.fileimage = [UIImage imageWithData:[dic objectForKey:@"fileimage"]]; file.downloadState = ZFStopDownload; file.error = NO; NSData *fileData = [[NSFileManager defaultManager ] contentsAtPath:file.tempPath]; NSInteger receivedDataLength = [fileData length]; file.fileReceivedSize = [NSString stringWithFormat:@"%zd",receivedDataLength]; return file; } - (NSArray *)sortbyTime:(NSArray *)array { NSArray *sorteArray1 = [array sortedArrayUsingComparator:^(id obj1, id obj2){ ZFFileModel *file1 = (ZFFileModel *)obj1; ZFFileModel *file2 = (ZFFileModel *)obj2; NSDate *date1 = [ZFCommonHelper makeDate:file1.time]; NSDate *date2 = [ZFCommonHelper makeDate:file2.time]; if ([[date1 earlierDate:date2]isEqualToDate:date2]) { return (NSComparisonResult)NSOrderedDescending; } if ([[date1 earlierDate:date2]isEqualToDate:date1]) { return (NSComparisonResult)NSOrderedAscending; } return (NSComparisonResult)NSOrderedSame; }]; return sorteArray1; } #pragma mark - 已完成的下载任务在这里处理 /* 将本地已经下载完成的文件加载到已下载列表里 */ - (void)loadFinishedfiles { if ([[NSFileManager defaultManager] fileExistsAtPath:PLIST_PATH]) { NSMutableArray *finishArr = [[NSMutableArray alloc] initWithContentsOfFile:PLIST_PATH]; for (NSDictionary *dic in finishArr) { ZFFileModel *file = [[ZFFileModel alloc]init]; file.fileName = [dic objectForKey:@"filename"]; file.fileType = [file.fileName pathExtension]; file.fileSize = [dic objectForKey:@"filesize"]; file.time = [dic objectForKey:@"time"]; file.fileimage = [UIImage imageWithData:[dic objectForKey:@"fileimage"]]; [_finishedlist addObject:file]; } } } - (void)saveFinishedFile { if (_finishedlist == nil) { return; } NSMutableArray *finishedinfo = [[NSMutableArray alloc] init]; for (ZFFileModel *fileinfo in _finishedlist) { NSData *imagedata = UIImagePNGRepresentation(fileinfo.fileimage); NSDictionary *filedic = [NSDictionary dictionaryWithObjectsAndKeys: fileinfo.fileName,@"filename", fileinfo.time,@"time", fileinfo.fileSize,@"filesize", imagedata,@"fileimage", nil]; [finishedinfo addObject:filedic]; } if (![finishedinfo writeToFile:PLIST_PATH atomically:YES]) { NSLog(@"write plist fail"); } } - (void)deleteFinishFile:(ZFFileModel *)selectFile { [_finishedlist removeObject:selectFile]; NSFileManager *fm = [NSFileManager defaultManager]; NSString *path = FILE_PATH(selectFile.fileName); if ([fm fileExistsAtPath:path]) { [fm removeItemAtPath:path error:nil]; } [self saveFinishedFile]; } #pragma mark -- ASIHttpRequest回调委托 -- // 出错了,如果是等待超时,则继续下载 - (void)requestFailed:(ZFHttpRequest *)request { NSError *error=[request error]; NSLog(@"ASIHttpRequest出错了!%@",error); if (error.code==4) { return; } if ([request isExecuting]) { [request cancel]; } ZFFileModel *fileInfo = [request.userInfo objectForKey:@"File"]; fileInfo.downloadState = ZFStopDownload; fileInfo.error = YES; for (ZFFileModel *file in _filelist) { if ([file.fileName isEqualToString:fileInfo.fileName]) { file.downloadState = ZFStopDownload; file.error = YES; } } [self.downloadDelegate updateCellProgress:request]; } - (void)requestStarted:(ZFHttpRequest *)request { NSLog(@"开始了!"); } - (void)request:(ZFHttpRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders { NSLog(@"收到回复了!"); ZFFileModel *fileInfo = [request.userInfo objectForKey:@"File"]; fileInfo.isFirstReceived = YES; NSString *len = [responseHeaders objectForKey:@"Content-Length"]; // 这个信息头,首次收到的为总大小,那么后来续传时收到的大小为肯定小于或等于首次的值,则忽略 if ([fileInfo.fileSize longLongValue] > [len longLongValue]){ return; } fileInfo.fileSize = [NSString stringWithFormat:@"%lld", [len longLongValue]]; [self saveDownloadFile:fileInfo]; } - (void)request:(ZFHttpRequest *)request didReceiveBytes:(long long)bytes { ZFFileModel *fileInfo = [request.userInfo objectForKey:@"File"]; NSLog(@"%@,%lld",fileInfo.fileReceivedSize,bytes); if (fileInfo.isFirstReceived) { fileInfo.isFirstReceived = NO; fileInfo.fileReceivedSize = [NSString stringWithFormat:@"%lld",bytes]; } else if(!fileInfo.isFirstReceived) { fileInfo.fileReceivedSize = [NSString stringWithFormat:@"%lld",[fileInfo.fileReceivedSize longLongValue]+bytes]; } if([self.downloadDelegate respondsToSelector:@selector(updateCellProgress:)]) { [self.downloadDelegate updateCellProgress:request]; } } // 将正在下载的文件请求ASIHttpRequest从队列里移除,并将其配置文件删除掉,然后向已下载列表里添加该文件对象 - (void)requestFinished:(ZFHttpRequest *)request { ZFFileModel *fileInfo = (ZFFileModel *)[request.userInfo objectForKey:@"File"]; [_finishedlist addObject:fileInfo]; NSString *configPath = [fileInfo.tempPath stringByAppendingString:@".plist"]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; if([fileManager fileExistsAtPath:configPath]) //如果存在临时文件的配置文件 { [fileManager removeItemAtPath:configPath error:&error]; if(!error) { NSLog(@"%@",[error description]); } } [_filelist removeObject:fileInfo]; [_downinglist removeObject:request]; [self saveFinishedFile]; [self startLoad]; if([self.downloadDelegate respondsToSelector:@selector(finishedDownload:)]) { [self.downloadDelegate finishedDownload:request]; } } #pragma mark - UIAlertViewDelegate - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { // 确定按钮 if( buttonIndex == 1 ) { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSInteger delindex = -1; NSString *path = FILE_PATH(_fileInfo.fileName); if([ZFCommonHelper isExistFile:path]) { //已经下载过一次该文件 for (ZFFileModel *info in _finishedlist) { if ([info.fileName isEqualToString:_fileInfo.fileName]) { // 删除文件 [self deleteFinishFile:info]; } } } else { // 如果正在下载中,择重新下载 for(ZFHttpRequest *request in self.downinglist) { ZFFileModel *ZFFileModel = [request.userInfo objectForKey:@"File"]; if([ZFFileModel.fileName isEqualToString:_fileInfo.fileName]) { if ([request isExecuting]) { [request cancel]; } delindex = [_downinglist indexOfObject:request]; break; } } [_downinglist removeObjectAtIndex:delindex]; for (ZFFileModel *file in _filelist) { if ([file.fileName isEqualToString:_fileInfo.fileName]) { delindex = [_filelist indexOfObject:file]; break; } } [_filelist removeObjectAtIndex:delindex]; // 存在于临时文件夹里 NSString * tempfilePath = [_fileInfo.tempPath stringByAppendingString:@".plist"]; if([ZFCommonHelper isExistFile:tempfilePath]) { if (![fileManager removeItemAtPath:tempfilePath error:&error]) { NSLog(@"删除临时文件出错:%@",[error localizedDescription]); } } if([ZFCommonHelper isExistFile:_fileInfo.tempPath]) { if (![fileManager removeItemAtPath:_fileInfo.tempPath error:&error]) { NSLog(@"删除临时文件出错:%@",[error localizedDescription]); } } } self.fileInfo.fileReceivedSize = [ZFCommonHelper getFileSizeString:@"0"]; [_filelist addObject:_fileInfo]; [self startLoad]; } if (self.VCdelegate!=nil && [self.VCdelegate respondsToSelector:@selector(allowNextRequest)]) { [self.VCdelegate allowNextRequest]; } } #pragma mark - setter - (void)setMaxCount:(NSInteger)maxCount { _maxCount = maxCount; [[NSUserDefaults standardUserDefaults] setValue:@(maxCount) forKey:kMaxRequestCount]; [[NSUserDefaults standardUserDefaults] synchronize]; [[ZFDownloadManager sharedDownloadManager] startLoad]; } @end