mzeeshanid / MZDownloadManager

This download manager uses NSURLSession api to download files. It can download multiple files at a time. It can download large files if app is in background. It can resume downloads if app was quit.
BSD 3-Clause "New" or "Revised" License
1.12k stars 238 forks source link

Not Resuming #15

Closed mahong125 closed 8 years ago

mahong125 commented 8 years ago

hey,I refer your idea to write a download manager, but does not support breakpoints download, hope you can help me analyze, be grateful.

code:

#import "DownLoadViewController.h"
#import "BaseCell.h"
#import <AFNetworking.h>
#import "DownModel.h"

#define fileDest [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/"]

NSString * const kMZDownloadKeyURL = @"URL";
NSString * const kMZDownloadKeyStartTime = @"startTime";
NSString * const kMZDownloadKeyFileName = @"fileName";
NSString * const kMZDownloadKeyProgress = @"progress";
NSString * const kMZDownloadKeyTask = @"downloadTask";
NSString * const kMZDownloadKeyStatus = @"requestStatus";
NSString * const kMZDownloadKeyDetails = @"downloadDetails";
NSString * const kMZDownloadKeyResumeData = @"resumedata";

NSString * const RequestStatusDownloading = @"RequestStatusDownloading";
NSString * const RequestStatusPaused = @"RequestStatusPaused";
NSString * const RequestStatusFailed = @"RequestStatusFailed";

@interface DownLoadViewController ()<UITableViewDataSource,UITableViewDelegate,NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate,NSURLSessionDataDelegate>
{
    UITableView *mainTable;
    NSMutableArray *tableData;

    /** 保存所有下载信息的数组 */
    NSMutableArray *downloadingArray;
}

@end

@implementation DownLoadViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"视频下载";

    mainTable = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    mainTable.delegate = self;
    mainTable.dataSource = self;
    [self.view addSubview:mainTable];

    tableData = [NSMutableArray array];

    downloadingArray = [NSMutableArray array];

     /** 请求下载列表 */
    [self requestData];
}

/**
 *  请求数据
 */
- (void)requestData
{
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
    manager.responseSerializer = [AFJSONResponseSerializer serializer];
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/plain", @"text/html", nil];

    [manager GET:@"http://ac.ybjk.com/vod_v1.php?km=km3&m=sp" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {

        NSMutableDictionary *resultDic = (NSMutableDictionary *)responseObject;

        NSArray *array = resultDic[@"data"];

        for (NSDictionary *dic in array)
        {
            DownModel *model = [[DownModel alloc] init];
            model.name = dic[@"title"];
            model.url = dic[@"url"];

            [tableData addObject:model];
        }

        /** 保存所有下载任务信息数组 初始化*/
        for (int i = 0 ; i < tableData.count ; i++)
        {
            NSMutableDictionary *tmpDic = [NSMutableDictionary dictionary];
            [downloadingArray addObject:tmpDic];
        }

        NSLog(@"所有下载任务信息 长度  %lu",(unsigned long)downloadingArray.count);

        [mainTable reloadData];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"请求失败");
    }];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return tableData.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellId = [NSString stringWithFormat:@"%ld%ld",(long)indexPath.section,(long)indexPath.row];
    BaseCell *cell = (BaseCell *)[tableView dequeueReusableCellWithIdentifier:cellId];
    if (!cell)
    {
        cell = [[BaseCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

    DownModel *model = [tableData objectAtIndex:indexPath.row];

    cell.nameLabel.text = model.name;
    cell.downBtn.tag = indexPath.row;
    [cell.downBtn addTarget:self action:@selector(downBtnAction:) forControlEvents:UIControlEventTouchUpInside];

    NSString *fileName = [NSString stringWithFormat:@"%@.mp4",model.name];

    // 2. 生成沙盒的路径
    NSArray *docs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [docs[0] stringByAppendingPathComponent:fileName];

    if ([[NSFileManager defaultManager] fileExistsAtPath:path])
    {
        /** 如果文件存在 */
        cell.downBtn.hidden = YES;
        cell.sizeLabel.text = @"已下载至本地";
    }

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 60.0f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 15.0f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    return 0.1f;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

/**
 *  刷新选中cell
 *
 *  @param cell      cell
 *  @param indexPath 选中行数
 */
- (void)updateCell:(BaseCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSMutableDictionary *downloadInfoDict = [downloadingArray objectAtIndex:indexPath.row];

     /** 文件名称 */
    NSString *fileName = [downloadInfoDict objectForKey:kMZDownloadKeyFileName];
    cell.nameLabel.text = fileName;
     /** 文件大小 */
    [cell.detailTextLabel setText:[downloadInfoDict objectForKey:kMZDownloadKeyDetails]];
     /** 下载进度 */
    [cell.progressView setProgress:[[downloadInfoDict objectForKey:kMZDownloadKeyProgress] floatValue]];
}

#pragma mark -  下载事件
- (void)downBtnAction:(UIButton *)btn
{
     /** 获得当前操作行数 */
    NSIndexPath *path = [NSIndexPath indexPathForRow:btn.tag inSection:0];
    BaseCell *cell = (BaseCell *)[mainTable cellForRowAtIndexPath:path];

     /** 获得当前操作行数对象 */
    DownModel *model = [tableData objectAtIndex:btn.tag];
     /** 下载的文件名称 */
    NSString *fileName = model.name;
     /** 下载的url */
    NSString *fileURL = model.url;

    if (!btn.selected)
    {
        NSLog(@"开始下载");
        [btn setSelected:YES];

        /** 获得当前操作行数 下载任务信息 */
        NSMutableDictionary *downloadInfo = [downloadingArray objectAtIndex:path.row];
        /** 获得当前操作行数 下载任务 */
        NSURLSessionDownloadTask *downloadTask = [downloadInfo objectForKey:kMZDownloadKeyTask];

        if (downloadTask)
        {
            /** 执行断点下载 */

            NSData *resumeData = [[downloadInfo objectForKey:kMZDownloadKeyResumeData] dataUsingEncoding:NSUTF8StringEncoding];
            downloadTask = [self.sessionManager downloadTaskWithResumeData:resumeData];
            [downloadTask resume];
        }
        else
        {
            /** 添加下载任务 */
            [self addDownloadTask:fileName fileURL:fileURL indexRow:btn.tag];
        }

    }
    else
    {
        NSLog(@"暂停下载");
        [btn setSelected:NO];

         /** 获得当前操作行数 下载任务信息 */
        NSMutableDictionary *downloadInfo = [downloadingArray objectAtIndex:path.row];
         /** 获得当前操作行数 下载任务 */
        NSURLSessionDownloadTask *downloadTask = [downloadInfo objectForKey:kMZDownloadKeyTask];
         /** 获得当前操作行数 下载状态 */
        NSString *downloadStatus = [downloadInfo objectForKey:kMZDownloadKeyStatus];

        if ([downloadStatus isEqualToString:RequestStatusDownloading])
        {
             /** 当前状态为正在下载,则暂停下载 */
//            [downloadTask suspend]; /** 暂停下载 */
//            [downloadTask cancel];
             /** 断点下载,保存断点data */
            [downloadTask cancelByProducingResumeData:^(NSData *resumeData) {

                NSString *dataString = [[NSString alloc] initWithData:resumeData encoding:NSUTF8StringEncoding];
                NSLog(@"断点数据 %@",dataString);
                [downloadInfo setObject:dataString forKey:kMZDownloadKeyResumeData];
            }];

            [downloadInfo setObject:RequestStatusPaused forKey:kMZDownloadKeyStatus]; /** 设置下载任务信息为暂停状态 */
             /** 更新下载任务数组中  当前任务信息 */
            [downloadingArray replaceObjectAtIndex:path.row withObject:downloadInfo];

             /** 刷新 当前 选中 cell */
            [self updateCell:cell forRowAtIndexPath:path];
        }
    }
}

#pragma mark -  懒加载 获得session
- (NSURLSession *)sessionManager
{
    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
    });
    return session;
}

/**
 *  添加下载任务
 *
 *  @param fileName 文件名
 *  @param fileURL  文件URL
 */
- (void)addDownloadTask:(NSString *)fileName fileURL:(NSString *)fileURL indexRow:(NSInteger)indexRow
{
     /** 初始化下载任务 */
    NSURL *url = [NSURL URLWithString:fileURL];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDownloadTask *downloadTask = [self.sessionManager downloadTaskWithRequest:request];

     /** 启动下载任务 */
    [downloadTask resume];

     /** 保存本次下载任务信息 */
    NSMutableDictionary *downloadInfo = [NSMutableDictionary dictionary];
    [downloadInfo setObject:fileURL forKey:kMZDownloadKeyURL]; /** 下载url */
    [downloadInfo setObject:fileName forKey:kMZDownloadKeyFileName]; /** 下载文件名称 */

     /** 将保存任务信息的字典转化为json字符串,设置本次下载任务的描述 */
    NSError *error = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:downloadInfo options:NSJSONWritingPrettyPrinted error:&error];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    downloadTask.taskDescription = jsonString;

     /** 继续向字典中添加本次下载任务信息 */
    [downloadInfo setObject:RequestStatusDownloading forKey:kMZDownloadKeyStatus]; /** 下载状态  (正在下载,暂停下载,下载失败)  */
    [downloadInfo setObject:downloadTask forKey:kMZDownloadKeyTask]; /** 本次下载任务 */

     /** 将本次下载任务信息保存到数组中 */
//    [downloadingArray addObject:downloadInfo];
    [downloadingArray replaceObjectAtIndex:indexRow withObject:downloadInfo];
}

#pragma mark -  NSURLSession Delegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
     /** 遍历下载任务信息数组,获得每一个下载任务信息 */
    for (NSMutableDictionary *downloadDict in downloadingArray)
    {

         /** 获得当前所有下载任务中 与 代理方法中的 downloadTask 一致的 */
        if ([downloadTask isEqual:[downloadDict objectForKey:kMZDownloadKeyTask]])
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                /** 回到主线程,渲染UI */
                float progress = (double)downloadTask.countOfBytesReceived/(double)downloadTask.countOfBytesExpectedToReceive;

                /** 获取当前任务所在cell */
                NSInteger indexOfDownloadDict = [downloadingArray indexOfObject:downloadDict];
                NSIndexPath *indexPathToRefresh = [NSIndexPath indexPathForRow:indexOfDownloadDict inSection:0];
                BaseCell *cell = (BaseCell *)[mainTable cellForRowAtIndexPath:indexPathToRefresh];

                /** 进度条 */
                cell.progressView.progress = progress;

                /** 文件大小 */
                cell.sizeLabel.text = [NSString stringWithFormat:@"%.2fM/%.2fM",(CGFloat)totalBytesWritten/1024/1024,(CGFloat)totalBytesExpectedToWrite/1024/1024];

                /** 保存当前下载任务的进度 以及 文件大小信息 */
                [downloadDict setObject:[NSString stringWithFormat:@"%f",progress] forKey:kMZDownloadKeyProgress]; /** 保存进度 */
                [downloadDict setObject:cell.sizeLabel.text forKey:kMZDownloadKeyDetails]; /** 文件大小 */
            });

            break;
        }
    }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    /** 遍历下载任务信息数组,获得每一个下载任务信息 */
    for (NSMutableDictionary *downloadDict in downloadingArray)
    {
        /** 获得当前所有下载任务中 与 代理方法中的 downloadTask 一致的 */
        if ([downloadTask isEqual:[downloadDict objectForKey:kMZDownloadKeyTask]])
        {
             /** 保存的文件名 */
            NSString *fileName = [downloadDict objectForKey:kMZDownloadKeyFileName];
             /** 保存路径 */
            NSString *savePath = [fileDest stringByAppendingPathComponent:fileName];
            NSURL *fileURL = [NSURL fileURLWithPath:savePath];
            NSLog(@"下载文件保存路径: %@",savePath);

             /** 将下载好的文件从临时文件夹中保存到指定路径 */
            if (location)
            {
                NSError *error = nil;
                [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
                if (error)
                {
                    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:error.localizedDescription delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
                    [alert show];
                }
            }

            break;
        }
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSLog(@"error %@",error.localizedDescription);

    NSLog(@"===== %@",[error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
mzeeshanid commented 8 years ago

Are you trying to make somethings similar to IDM (Internet download manager) ?

I did not understand what your requirements are?