Files
youle_app_ios/msext/QiniuSDK/Storage/QNResumeUpload.m
2023-12-27 20:38:37 +08:00

338 lines
12 KiB
Objective-C
Executable File

//
// 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 upBackup:_token].address;
}
[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].address];
}
}
@end