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協議是不支持的,所以也是需要前後端配套開發。

後面我會補充支持斷點的網絡通信那一塊代碼!

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