HTTP断点续传与断点上传之 -- 文件流操作

不管是下载还是上传,断点的时候,就需要对文件流进行精确的操作。
1、下载断开了,已经下载的数据保存到文件,再次继续下载的时候需要从文件的尾巴继续追加数据;
2、同理上传也是一样,http通信中有可能断开或者丢包的情况,就需要重传指定的文件片;

我封装的这个类,是基于把文件切成片的形式

FileStreamOperation.h

//
//  FileStreamOperation.h
//  CommonProject
//
//  Created by wuyoujian on 16/7/6.
//  Copyright © 2016年 wuyoujian. All rights reserved.
//

#import <Foundation/Foundation.h>

#define FileFragmentMaxSize         1024 * 512 // 512k


@class FileFragment;

/**
 *  文件流操作类
 */
@interface FileStreamOperation : NSObject<NSCoding>
@property (nonatomic, readonly, copy) NSString *fileName;// 包括文件后缀名的文件名
@property (nonatomic, readonly, assign) NSUInteger fileSize;// 文件大小
@property (nonatomic, readonly, copy) NSString *filePath;// 文件所在的文件目录
@property (nonatomic, readonly, strong) NSArray<FileFragment*> *fileFragments;// 文件分片数组

// 若为读取文件数据,打开一个已存在的文件。
// 若为写入文件数据,如果文件不存在,会创建的新的空文件。
- (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation ;

// 获取当前偏移量
- (NSUInteger)offsetInFile;

// 设置偏移量, 仅对读取设置
- (void)seekToFileOffset:(NSUInteger)offset;

// 将偏移量定位到文件的末尾
- (NSUInteger)seekToEndOfFile;

// 关闭文件
- (void)closeFile;

#pragma mark - 读操作
// 通过分片信息读取对应的片数据
- (NSData*)readDateOfFragment:(FileFragment*)fragment;

// 从当前文件偏移量开始
- (NSData*)readDataOfLength:(NSUInteger)bytes;

// 从当前文件偏移量开始
- (NSData*)readDataToEndOfFile;

#pragma mark - 写操作
// 写入文件数据
- (void)writeData:(NSData *)data;

@end

// 上传文件片
@interface FileFragment : NSObject<NSCoding>
@property (nonatomic,copy)NSString          *fragmentId;    // 片的唯一标识
@property (nonatomic,assign)NSUInteger      fragmentSize;   // 片的大小
@property (nonatomic,assign)NSUInteger      fragementOffset;// 片的偏移量
@property (nonatomic,assign)BOOL            fragmentStatus; // 上传状态 YES上传成功
@end

FileStreamOperation.m

//
//  FileStreamOperation.m
//  CommonProject
//
//  Created by wuyoujian on 16/7/6.
//  Copyright © 2016年 wuyoujian. All rights reserved.
//

#import "FileStreamOperation.h"
#import <CommonCrypto/CommonDigest.h>

//// 把FileStreamOpenration类保存到UserDefault中
//static NSString *const UserDefaultFileInfo = @"UserDefaultFileInfo";

#pragma mark - FileStreamOperation

@interface FileStreamOperation ()
@property (nonatomic, copy) NSString                          *fileName;
@property (nonatomic, assign) NSUInteger                      fileSize;
@property (nonatomic, copy) NSString                          *filePath;
@property (nonatomic, strong) NSArray<FileFragment*>          *fileFragments;
@property (nonatomic, strong) NSFileHandle                    *readFileHandle;
@property (nonatomic, strong) NSFileHandle                    *writeFileHandle;
@property (nonatomic, assign) BOOL                            isReadOperation;
@end

@implementation FileStreamOperation

+ (NSString *)fileKey {

    CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
    CFStringRef cfstring = CFUUIDCreateString(kCFAllocatorDefault, uuid);
    const char *cStr = CFStringGetCStringPtr(cfstring,CFStringGetFastestEncoding(cfstring));
    unsigned char result[16];
    CC_MD5( cStr, (unsigned int)strlen(cStr), result );
    CFRelease(uuid);

    return [NSString stringWithFormat:
            @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%08lx",
            result[0], result[1], result[2], result[3],
            result[4], result[5], result[6], result[7],
            result[8], result[9], result[10], result[11],
            result[12], result[13], result[14], result[15],
            (unsigned long)(arc4random() % NSUIntegerMax)];
}

- (void)encodeWithCoder:(NSCoder *)aCoder {

    [aCoder encodeObject:[self fileName] forKey:@"fileName"];
    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fileSize]] forKey:@"fileSize"];
    [aCoder encodeObject:[self filePath] forKey:@"filePath"];
    [aCoder encodeObject:[self fileFragments] forKey:@"fileFragments"];
}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self != nil) {
        [self setFileName:[aDecoder decodeObjectForKey:@"fileName"]];
        [self setFileSize:[[aDecoder decodeObjectForKey:@"fileSize"] unsignedIntegerValue]];
        [self setFilePath:[aDecoder decodeObjectForKey:@"filePath"]];
        [self setFileFragments:[aDecoder decodeObjectForKey:@"fileFragments"]];
    }

    return self;
}


- (BOOL)getFileInfoAtPath:(NSString*)path {

    NSFileManager *fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:path]) {
        NSLog(@"文件不存在:%@",path);
        return NO;
    }

    self.filePath = path;

    NSDictionary *attr =[fileMgr attributesOfItemAtPath:path error:nil];
    self.fileSize = attr.fileSize;

    NSString *fileName = [path lastPathComponent];
    self.fileName = fileName;

    return YES;
}

// 若为读取文件数据,打开一个已存在的文件。
// 若为写入文件数据,如果文件不存在,会创建的新的空文件。
- (instancetype)initFileOperationAtPath:(NSString*)path forReadOperation:(BOOL)isReadOperation {

    if (self = [super init]) {
        self.isReadOperation = isReadOperation;
        if (_isReadOperation) {
            if (![self getFileInfoAtPath:path]) {
                return nil;
            }
            self.readFileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
            [self cutFileForFragments];
        } else {
            NSFileManager *fileMgr = [NSFileManager defaultManager];
            if (![fileMgr fileExistsAtPath:path]) {
                [fileMgr createFileAtPath:path contents:nil attributes:nil];
            }

            if (![self getFileInfoAtPath:path]) {
                return nil;
            }

            self.writeFileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
        }
    }

    return self;
}

#pragma mark - 读操作
// 切分文件片段
- (void)cutFileForFragments {

    NSUInteger offset = FileFragmentMaxSize;
    // 块数
    NSUInteger chunks = (_fileSize%offset==0)?(_fileSize/offset):(_fileSize/(offset) + 1);

    NSMutableArray<FileFragment *> *fragments = [[NSMutableArray alloc] initWithCapacity:0];
    for (NSUInteger i = 0; i < chunks; i ++) {

        FileFragment *fFragment = [[FileFragment alloc] init];
        fFragment.fragmentStatus = NO;
        fFragment.fragmentId = [[self class] fileKey];
        fFragment.fragementOffset = i * offset;

        if (i != chunks - 1) {
            fFragment.fragmentSize = offset;
        } else {
            fFragment.fragmentSize = _fileSize - fFragment.fragementOffset;
        }

        [fragments addObject:fFragment];
    }

    self.fileFragments = fragments;
}

// 通过分片信息读取对应的片数据
- (NSData*)readDateOfFragment:(FileFragment*)fragment {

    if (fragment) {
        [self seekToFileOffset:fragment.fragementOffset];
        return [_readFileHandle readDataOfLength:fragment.fragmentSize];
    }

    return nil;
}

- (NSData*)readDataOfLength:(NSUInteger)bytes {
    return [_readFileHandle readDataOfLength:bytes];
}


- (NSData*)readDataToEndOfFile {
    return [_readFileHandle readDataToEndOfFile];
}

#pragma mark - 写操作

// 写入文件数据
- (void)writeData:(NSData *)data {
    [_writeFileHandle writeData:data];
}

#pragma mark - common
// 获取当前偏移量
- (NSUInteger)offsetInFile{
    if (_isReadOperation) {
       return [_readFileHandle offsetInFile];
    }

    return [_writeFileHandle offsetInFile];
}

// 设置偏移量, 仅对读取设置
- (void)seekToFileOffset:(NSUInteger)offset {
    [_readFileHandle seekToFileOffset:offset];
}

// 将偏移量定位到文件的末尾
- (NSUInteger)seekToEndOfFile{
    if (_isReadOperation) {
        return [_readFileHandle seekToEndOfFile];
    }

    return [_writeFileHandle seekToEndOfFile];
}

// 关闭文件
- (void)closeFile {
    if (_isReadOperation) {
        [_readFileHandle closeFile];
    } else {
        [_writeFileHandle closeFile];
    }
}

@end

#pragma mark - FileFragment

@implementation FileFragment

- (void)encodeWithCoder:(NSCoder *)aCoder {

    [aCoder encodeObject:[self fragmentId] forKey:@"fragmentId"];
    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentSize]] forKey:@"fragmentSize"];
    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragementOffset]] forKey:@"fragementOffset"];
    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:[self fragmentStatus]] forKey:@"fragmentStatus"];
}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self != nil) {
        [self setFragmentId:[aDecoder decodeObjectForKey:@"fragmentId"]];
        [self setFragmentSize:[[aDecoder decodeObjectForKey:@"fragmentSize"] unsignedIntegerValue]];
        [self setFragementOffset:[[aDecoder decodeObjectForKey:@"fragementOffset"] unsignedIntegerValue]];
        [self setFragmentStatus:[[aDecoder decodeObjectForKey:@"fragmentStatus"] boolValue]];
    }

    return self;
}

@end

值得注意的地方,HTTP的断点续传HTTP协议是支持的,但是需要前后端配套开发。但断点上传就需要自定义的方式上传,HTTP协议是不支持的,所以也是需要前后端配套开发。

后面我会补充支持断点的网络通信那一块代码!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章