123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- //
- // QNResumeUpload.m
- // QiniuSDK
- //
- // Created by bailong on 14/10/1.
- // Copyright (c) 2014年 Qiniu. All rights reserved.
- //
- #import "QNResumeUpload.h"
- @interface QNResumeUpload ()
- @property (nonatomic, copy) NSString *recorderKey;
- @property (nonatomic, strong) NSDictionary *headers;
- @property (nonatomic, strong) NSMutableArray *contexts;
- @property (nonatomic, strong) id<QNRecorderDelegate> recorder;
- @property (nonatomic, strong) id<QNFileDelegate> file;
- @property (nonatomic, copy) NSString *recordHost; // upload host in last recorder file
- @property (nonatomic, assign) UInt32 chunkCrc;
- @property (nonatomic, assign) float previousPercent;
- @property (nonatomic, assign) int64_t modifyTime;
- @end
- @implementation QNResumeUpload
- - (instancetype)initWithFile:(id<QNFileDelegate>)file
- withKey:(NSString *)key
- withToken:(QNUpToken *)token
- withIdentifier:(NSString *)identifier
- withCompletionHandler:(QNUpCompletionHandler)block
- withOption:(QNUploadOption *)option
- withRecorder:(id<QNRecorderDelegate>)recorder
- withRecorderKey:(NSString *)recorderKey
- withSessionManager:(QNSessionManager *)sessionManager
- withConfiguration:(QNConfiguration *)config;
- {
- if (self = [super init]) {
- self.file = file;
- self.size = (UInt32)[file size];
- self.key = key;
- NSString *tokenUp = [NSString stringWithFormat:@"UpToken %@", token.token];
- self.option = option != nil ? option : [QNUploadOption defaultOptions];
- self.complete = block;
- self.headers = @{@"Authorization" : tokenUp, @"Content-Type" : @"application/octet-stream"};
- self.recorder = recorder;
- self.sessionManager = sessionManager;
- self.modifyTime = [file modifyTime];
- self.recorderKey = recorderKey;
- self.contexts = [[NSMutableArray alloc] initWithCapacity:(self.size + kQNBlockSize - 1) / kQNBlockSize];
- self.config = config;
- self.currentZoneType = QNZoneInfoTypeMain;
- self.token = token;
- self.previousPercent = 0;
- self.access = token.access;
- self.identifier = identifier;
- [Collector update:CK_blockApiVersion value:@1 identifier:self.identifier];
- }
- return self;
- }
- - (void)record:(UInt32)offset host:(NSString *)host {
- NSString *key = self.recorderKey;
- if (offset == 0 || self.recorder == nil || key == nil || [key isEqualToString:@""]) {
- return;
- }
- NSNumber *n_size = @(self.size);
- NSNumber *n_offset = @(offset);
- NSNumber *n_time = [NSNumber numberWithLongLong:self.modifyTime];
- NSMutableDictionary *rec = [NSMutableDictionary dictionaryWithObjectsAndKeys:n_size, @"size", n_offset, @"offset", n_time, @"modify_time", host, @"host", self.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 = [self.recorder set:key data:data];
- if (error != nil) {
- NSLog(@"up record set error %@ %@", key, error);
- }
- }
- - (void)removeRecord {
- if (self.recorder == nil) {
- return;
- }
- self.recordHost = nil;
- [self.contexts removeAllObjects];
- [self.recorder del:self.recorderKey];
- }
- - (UInt32)recoveryFromRecord {
- NSString *key = self.recorderKey;
- if (self.recorder == nil || key == nil || [key isEqualToString:@""]) {
- return 0;
- }
- NSData *data = [self.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);
- [self.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 != self.modifyTime) {
- NSLog(@"modify time changed %llu, %llu", t, self.modifyTime);
- return 0;
- }
- self.recordHost = info[@"host"];
- self.contexts = [[NSMutableArray alloc] initWithArray:contexts copyItems:true];
- return offset;
- }
- - (void)nextTask:(UInt32)offset needDelay:(BOOL)needDelay retriedTimes:(int)retried host:(NSString *)host {
- if (needDelay) {
- QNAsyncRunAfter(self.config.retryInterval, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- [self nextTask:offset retriedTimes:retried host:host];
- });
- } else {
- [self nextTask:offset retriedTimes:retried host:host];
- }
- }
- - (void)nextTask:(UInt32)offset retriedTimes:(int)retried host:(NSString *)host {
- if (self.option.cancellationSignal()) {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector userCancel:self.identifier];
- self.complete(info, self.key, nil);
- return;
- }
- if (offset == self.size) {
- QNCompleteBlock completionHandler = ^(QNHttpResponseInfo *httpResponseInfo, NSDictionary *respBody) {
- [self collectHttpResponseInfo:httpResponseInfo fileOffset:offset];
-
- if (httpResponseInfo.isOK) {
- [self removeRecord];
- self.option.progressHandler(self.key, 1.0);
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
- self.complete(info, self.key, respBody);
- } else if (httpResponseInfo.couldRetry) {
- if (retried < self.config.retryMax) {
- [self nextTask:offset needDelay:YES retriedTimes:retried + 1 host:host];
- } else {
- if (self.config.allowBackupHost) {
- NSString *nextHost = nil;
- UInt32 nextOffset = 0;
- if (self.recordHost) {
- self.previousPercent = 0;
- [self removeRecord];
- self.currentZoneType = QNZoneInfoTypeMain;
- nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil];
- nextOffset = 0;
- } else {
- nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:host];
- nextOffset = offset;
- }
- if (nextHost) {
- [self nextTask:nextOffset needDelay:YES retriedTimes:0 host:nextHost];
- } else {
- QNZonesInfo *zonesInfo = [self.config.zone getZonesInfoWithToken:self.token];
- if (self.currentZoneType == QNZoneInfoTypeMain && zonesInfo.hasBackupZone) {
- self.currentZoneType = QNZoneInfoTypeBackup;
- self.previousPercent = 0;
- [self removeRecord];
- [self nextTask:0 needDelay:YES retriedTimes:0 host:[self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil]];
- } else {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
- self.complete(info, self.key, respBody);
- }
- }
- } else {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
- self.complete(info, self.key, respBody);
- }
- }
- } else {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
- self.complete(info, self.key, respBody);
- }
- };
- [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 > self.previousPercent) {
- self.previousPercent = percent;
- } else {
- percent = self.previousPercent;
- }
- self.option.progressHandler(self.key, percent);
- };
- QNCompleteBlock completionHandler = ^(QNHttpResponseInfo *httpResponseInfo, NSDictionary *respBody) {
- [self collectHttpResponseInfo:httpResponseInfo fileOffset:offset];
-
- NSString *ctx = respBody[@"ctx"];
- NSNumber *crc = respBody[@"crc32"];
- if (httpResponseInfo.isOK && ctx && crc && [crc unsignedLongValue] == self.chunkCrc) {
- self.contexts[offset / kQNBlockSize] = ctx;
- [self record:offset + chunkSize host:host];
- [self nextTask:offset + chunkSize needDelay:NO retriedTimes:retried host:host];
- } else if (httpResponseInfo.couldRetry) {
- if (retried < self.config.retryMax) {
- [self nextTask:offset needDelay:YES retriedTimes:retried + 1 host:host];
- } else {
- if (self.config.allowBackupHost) {
- NSString *nextHost = nil;
- UInt32 nextOffset = 0;
- if (self.recordHost) {
- self.previousPercent = 0;
- [self removeRecord];
- self.currentZoneType = QNZoneInfoTypeMain;
- nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil];
- nextOffset = 0;
- } else {
- nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:host];
- nextOffset = offset;
- }
- if (nextHost) {
- [self nextTask:nextOffset needDelay:YES retriedTimes:0 host:nextHost];
- } else {
- QNZonesInfo *zonesInfo = [self.config.zone getZonesInfoWithToken:self.token];
- if (self.currentZoneType == QNZoneInfoTypeMain && zonesInfo.hasBackupZone) {
- self.currentZoneType = QNZoneInfoTypeBackup;
- self.previousPercent = 0;
- [self removeRecord];
- [self nextTask:0 needDelay:YES retriedTimes:0 host:[self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil]];
- } else {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
- self.complete(info, self.key, respBody);
- }
- }
- } else {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
- self.complete(info, self.key, respBody);
- }
- }
- } else {
- if (httpResponseInfo.statusCode == 701) {
- [self nextTask:(offset / kQNBlockSize) * kQNBlockSize needDelay:YES retriedTimes:0 host:host];
- } else {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
- self.complete(info, self.key, respBody);
- }
- }
- };
- 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 = self.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 < self.config.chunkSize ? left : self.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 {
- self.requestType = QNRequestType_mkblk;
- NSError *error;
- NSData *data = [self.file read:offset size:chunkSize error:&error];
- if (error) {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithLocalIOError:error identifier:self.identifier];
- self.complete(info, self.key, nil);
- return;
- }
- NSString *url = [[NSString alloc] initWithFormat:@"%@/mkblk/%u", uphost, (unsigned int)blockSize];
- self.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 {
- self.requestType = QNRequestType_bput;
- NSError *error;
- NSData *data = [self.file read:offset size:size error:&error];
- if (error) {
- [self collectUploadQualityInfo];
- QNResponseInfo *info = [Collector completeWithLocalIOError:error identifier:self.identifier];
- self.complete(info, self.key, nil);
- return;
- }
- UInt32 chunkOffset = offset % kQNBlockSize;
- NSString *url = [[NSString alloc] initWithFormat:@"%@/bput/%@/%u", uphost, context, (unsigned int)chunkOffset];
- self.chunkCrc = [QNCrc32 data:data];
- [self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
- }
- - (void)makeFile:(NSString *)uphost
- complete:(QNCompleteBlock)complete {
- self.requestType = QNRequestType_mkfile;
- 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 [[self.file path] lastPathComponent];
- }
- - (void)post:(NSString *)url
- withData:(NSData *)data
- withCompleteBlock:(QNCompleteBlock)completeBlock
- withProgressBlock:(QNInternalProgressBlock)progressBlock {
- [self.sessionManager post:url withData:data withParams:nil withHeaders:self.headers withIdentifier:self.identifier withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:self.option.cancellationSignal withAccess:self.access];
- }
- - (void)run {
- @autoreleasepool {
- UInt32 offset = [self recoveryFromRecord];
- [Collector update:CK_recoveredFrom value:@(offset) identifier:self.identifier];
-
- if (offset > 0) {
- [self nextTask:offset needDelay:NO retriedTimes:0 host:self.recordHost];
- } else {
- [self nextTask:offset needDelay:NO retriedTimes:0 host:[self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil]];
- }
- }
- }
- @end
|