123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- //
- // QNResumeUpload.m
- // QiniuSDK
- //
- // Created by bailong on 14/10/1.
- // Copyright (c) 2014年 Qiniu. All rights reserved.
- //
- #import "QNResumeUpload.h"
- #import "QNConfiguration.h"
- #import "QNCrc32.h"
- #import "QNRecorderDelegate.h"
- #import "QNResponseInfo.h"
- #import "QNUploadManager.h"
- #import "QNUploadOption+Private.h"
- #import "QNUrlSafeBase64.h"
- typedef void (^task)(void);
- @interface QNResumeUpload ()
- @property (nonatomic, strong) id<QNHttpDelegate> httpManager;
- @property UInt32 size;
- @property (nonatomic) int retryTimes;
- @property (nonatomic, strong) NSString *key;
- @property (nonatomic, strong) NSString *recorderKey;
- @property (nonatomic) NSDictionary *headers;
- @property (nonatomic, strong) QNUploadOption *option;
- @property (nonatomic, strong) QNUpToken *token;
- @property (nonatomic, strong) QNUpCompletionHandler complete;
- @property (nonatomic, strong) NSMutableArray *contexts;
- @property int64_t modifyTime;
- @property (nonatomic, strong) id<QNRecorderDelegate> recorder;
- @property (nonatomic, strong) QNConfiguration *config;
- @property UInt32 chunkCrc;
- @property (nonatomic, strong) id<QNFileDelegate> file;
- //@property (nonatomic, strong) NSArray *fileAry;
- @property (nonatomic) float previousPercent;
- @property (nonatomic, strong) NSString *access; //AK
- - (void)makeBlock:(NSString *)uphost
- offset:(UInt32)offset
- blockSize:(UInt32)blockSize
- chunkSize:(UInt32)chunkSize
- progress:(QNInternalProgressBlock)progressBlock
- complete:(QNCompleteBlock)complete;
- - (void)putChunk:(NSString *)uphost
- offset:(UInt32)offset
- size:(UInt32)size
- context:(NSString *)context
- progress:(QNInternalProgressBlock)progressBlock
- complete:(QNCompleteBlock)complete;
- - (void)makeFile:(NSString *)uphost
- complete:(QNCompleteBlock)complete;
- @end
- @implementation QNResumeUpload
- - (instancetype)initWithFile:(id<QNFileDelegate>)file
- withKey:(NSString *)key
- withToken:(QNUpToken *)token
- withCompletionHandler:(QNUpCompletionHandler)block
- withOption:(QNUploadOption *)option
- withRecorder:(id<QNRecorderDelegate>)recorder
- withRecorderKey:(NSString *)recorderKey
- withHttpManager:(id<QNHttpDelegate>)http
- withConfiguration:(QNConfiguration *)config;
- {
- if (self = [super init]) {
- _file = file;
- _size = (UInt32)[file size];
- _key = key;
- NSString *tokenUp = [NSString stringWithFormat:@"UpToken %@", token.token];
- _option = option != nil ? option : [QNUploadOption defaultOptions];
- _complete = block;
- _headers = @{@"Authorization" : tokenUp, @"Content-Type" : @"application/octet-stream"};
- _recorder = recorder;
- _httpManager = http;
- _modifyTime = [file modifyTime];
- _recorderKey = recorderKey;
- _contexts = [[NSMutableArray alloc] initWithCapacity:(_size + kQNBlockSize - 1) / kQNBlockSize];
- _config = config;
- _token = token;
- _previousPercent = 0;
- _access = token.access;
- }
- return self;
- }
- // save json value
- //{
- // "size":filesize,
- // "offset":lastSuccessOffset,
- // "modify_time": lastFileModifyTime,
- // "contexts": contexts
- //}
- - (void)record:(UInt32)offset {
- NSString *key = self.recorderKey;
- if (offset == 0 || _recorder == nil || key == nil || [key isEqualToString:@""]) {
- return;
- }
- NSNumber *n_size = @(self.size);
- NSNumber *n_offset = @(offset);
- NSNumber *n_time = [NSNumber numberWithLongLong:_modifyTime];
- NSMutableDictionary *rec = [NSMutableDictionary dictionaryWithObjectsAndKeys:n_size, @"size", n_offset, @"offset", n_time, @"modify_time", _contexts, @"contexts", nil];
- NSError *error;
- NSData *data = [NSJSONSerialization dataWithJSONObject:rec options:NSJSONWritingPrettyPrinted error:&error];
- if (error != nil) {
- NSLog(@"up record json error %@ %@", key, error);
- return;
- }
- error = [_recorder set:key data:data];
- if (error != nil) {
- NSLog(@"up record set error %@ %@", key, error);
- }
- }
- - (void)removeRecord {
- if (_recorder == nil) {
- return;
- }
- [_recorder del:self.recorderKey];
- }
- - (UInt32)recoveryFromRecord {
- NSString *key = self.recorderKey;
- if (_recorder == nil || key == nil || [key isEqualToString:@""]) {
- return 0;
- }
- NSData *data = [_recorder get:key];
- if (data == nil) {
- return 0;
- }
- NSError *error;
- NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
- if (error != nil) {
- NSLog(@"recovery error %@ %@", key, error);
- [_recorder del:self.key];
- return 0;
- }
- NSNumber *n_offset = info[@"offset"];
- NSNumber *n_size = info[@"size"];
- NSNumber *time = info[@"modify_time"];
- NSArray *contexts = info[@"contexts"];
- if (n_offset == nil || n_size == nil || time == nil || contexts == nil) {
- return 0;
- }
- UInt32 offset = [n_offset unsignedIntValue];
- UInt32 size = [n_size unsignedIntValue];
- if (offset > size || size != self.size) {
- return 0;
- }
- UInt64 t = [time unsignedLongLongValue];
- if (t != _modifyTime) {
- NSLog(@"modify time changed %llu, %llu", t, _modifyTime);
- return 0;
- }
- _contexts = [[NSMutableArray alloc] initWithArray:contexts copyItems:true];
- return offset;
- }
- - (void)nextTask:(UInt32)offset retriedTimes:(int)retried host:(NSString *)host {
- if (self.option.cancellationSignal()) {
- self.complete([QNResponseInfo cancel], self.key, nil);
- return;
- }
- if (offset == self.size) {
- QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
- if (info.isOK) {
- [self removeRecord];
- self.option.progressHandler(self.key, 1.0);
- } else if (info.couldRetry && retried < _config.retryMax) {
- [self nextTask:offset retriedTimes:retried + 1 host:host];
- return;
- }
- self.complete(info, self.key, resp);
- };
- [self makeFile:host complete:completionHandler];
- return;
- }
- UInt32 chunkSize = [self calcPutSize:offset];
- QNInternalProgressBlock progressBlock = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
- float percent = (float)(offset + totalBytesWritten) / (float)self.size;
- if (percent > 0.95) {
- percent = 0.95;
- }
- if (percent > _previousPercent) {
- _previousPercent = percent;
- } else {
- percent = _previousPercent;
- }
- self.option.progressHandler(self.key, percent);
- };
- QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
- if (info.error != nil) {
- if (info.statusCode == 701) {
- [self nextTask:(offset / kQNBlockSize) * kQNBlockSize retriedTimes:0 host:host];
- return;
- }
- if (retried >= _config.retryMax || !info.couldRetry) {
- self.complete(info, self.key, resp);
- return;
- }
- NSString *nextHost = host;
- if (info.isConnectionBroken || info.needSwitchServer) {
- nextHost = [_config.zone up:_token isHttps:_config.useHttps frozenDomain:nextHost];
- }
- [self nextTask:offset retriedTimes:retried + 1 host:nextHost];
- return;
- }
- if (resp == nil) {
- [self nextTask:offset retriedTimes:retried host:host];
- return;
- }
- NSString *ctx = resp[@"ctx"];
- NSNumber *crc = resp[@"crc32"];
- if (ctx == nil || crc == nil || [crc unsignedLongValue] != _chunkCrc) {
- [self nextTask:offset retriedTimes:retried host:host];
- return;
- }
- _contexts[offset / kQNBlockSize] = ctx;
- [self record:offset + chunkSize];
- [self nextTask:offset + chunkSize retriedTimes:retried host:host];
- };
- if (offset % kQNBlockSize == 0) {
- UInt32 blockSize = [self calcBlockSize:offset];
- [self makeBlock:host offset:offset blockSize:blockSize chunkSize:chunkSize progress:progressBlock complete:completionHandler];
- return;
- }
- NSString *context = _contexts[offset / kQNBlockSize];
- [self putChunk:host offset:offset size:chunkSize context:context progress:progressBlock complete:completionHandler];
- }
- - (UInt32)calcPutSize:(UInt32)offset {
- UInt32 left = self.size - offset;
- return left < _config.chunkSize ? left : _config.chunkSize;
- }
- - (UInt32)calcBlockSize:(UInt32)offset {
- UInt32 left = self.size - offset;
- return left < kQNBlockSize ? left : kQNBlockSize;
- }
- - (void)makeBlock:(NSString *)uphost
- offset:(UInt32)offset
- blockSize:(UInt32)blockSize
- chunkSize:(UInt32)chunkSize
- progress:(QNInternalProgressBlock)progressBlock
- complete:(QNCompleteBlock)complete {
- NSData *data = [self.file read:offset size:chunkSize];
- NSString *url = [[NSString alloc] initWithFormat:@"%@/mkblk/%u", uphost, (unsigned int)blockSize];
- _chunkCrc = [QNCrc32 data:data];
- [self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
- }
- - (void)putChunk:(NSString *)uphost
- offset:(UInt32)offset
- size:(UInt32)size
- context:(NSString *)context
- progress:(QNInternalProgressBlock)progressBlock
- complete:(QNCompleteBlock)complete {
- NSData *data = [self.file read:offset size:size];
- UInt32 chunkOffset = offset % kQNBlockSize;
- NSString *url = [[NSString alloc] initWithFormat:@"%@/bput/%@/%u", uphost, context, (unsigned int)chunkOffset];
- _chunkCrc = [QNCrc32 data:data];
- [self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
- }
- - (void)makeFile:(NSString *)uphost
- complete:(QNCompleteBlock)complete {
- NSString *mime = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.option.mimeType]];
- __block NSString *url = [[NSString alloc] initWithFormat:@"%@/mkfile/%u%@", uphost, (unsigned int)self.size, mime];
- if (self.key != nil) {
- NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
- url = [NSString stringWithFormat:@"%@%@", url, keyStr];
- }
- [self.option.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
- url = [NSString stringWithFormat:@"%@/%@/%@", url, key, [QNUrlSafeBase64 encodeString:obj]];
- }];
- //添加路径
- NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:[self fileBaseName]]];
- url = [NSString stringWithFormat:@"%@%@", url, fname];
- NSMutableData *postData = [NSMutableData data];
- NSString *bodyStr = [self.contexts componentsJoinedByString:@","];
- [postData appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
- [self post:url withData:postData withCompleteBlock:complete withProgressBlock:nil];
- }
- #pragma mark - 处理文件路径
- - (NSString *)fileBaseName {
- return [[_file path] lastPathComponent];
- }
- - (void)post:(NSString *)url
- withData:(NSData *)data
- withCompleteBlock:(QNCompleteBlock)completeBlock
- withProgressBlock:(QNInternalProgressBlock)progressBlock {
- [_httpManager post:url withData:data withParams:nil withHeaders:_headers withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:_option.cancellationSignal withAccess:_access];
- }
- - (void)run {
- @autoreleasepool {
- UInt32 offset = [self recoveryFromRecord];
- [self nextTask:offset retriedTimes:0 host:[_config.zone up:_token isHttps:_config.useHttps frozenDomain:nil]];
- }
- }
- @end
|