QNResumeUpload.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. //
  2. // QNResumeUpload.m
  3. // QiniuSDK
  4. //
  5. // Created by bailong on 14/10/1.
  6. // Copyright (c) 2014年 Qiniu. All rights reserved.
  7. //
  8. #import "QNResumeUpload.h"
  9. #import "QNConfiguration.h"
  10. #import "QNCrc32.h"
  11. #import "QNRecorderDelegate.h"
  12. #import "QNResponseInfo.h"
  13. #import "QNUploadManager.h"
  14. #import "QNUploadOption+Private.h"
  15. #import "QNUrlSafeBase64.h"
  16. typedef void (^task)(void);
  17. @interface QNResumeUpload ()
  18. @property (nonatomic, strong) id<QNHttpDelegate> httpManager;
  19. @property UInt32 size;
  20. @property (nonatomic) int retryTimes;
  21. @property (nonatomic, strong) NSString *key;
  22. @property (nonatomic, strong) NSString *recorderKey;
  23. @property (nonatomic) NSDictionary *headers;
  24. @property (nonatomic, strong) QNUploadOption *option;
  25. @property (nonatomic, strong) QNUpToken *token;
  26. @property (nonatomic, strong) QNUpCompletionHandler complete;
  27. @property (nonatomic, strong) NSMutableArray *contexts;
  28. @property int64_t modifyTime;
  29. @property (nonatomic, strong) id<QNRecorderDelegate> recorder;
  30. @property (nonatomic, strong) QNConfiguration *config;
  31. @property UInt32 chunkCrc;
  32. @property (nonatomic, strong) id<QNFileDelegate> file;
  33. //@property (nonatomic, strong) NSArray *fileAry;
  34. @property (nonatomic) float previousPercent;
  35. @property (nonatomic, strong) NSString *access; //AK
  36. - (void)makeBlock:(NSString *)uphost
  37. offset:(UInt32)offset
  38. blockSize:(UInt32)blockSize
  39. chunkSize:(UInt32)chunkSize
  40. progress:(QNInternalProgressBlock)progressBlock
  41. complete:(QNCompleteBlock)complete;
  42. - (void)putChunk:(NSString *)uphost
  43. offset:(UInt32)offset
  44. size:(UInt32)size
  45. context:(NSString *)context
  46. progress:(QNInternalProgressBlock)progressBlock
  47. complete:(QNCompleteBlock)complete;
  48. - (void)makeFile:(NSString *)uphost
  49. complete:(QNCompleteBlock)complete;
  50. @end
  51. @implementation QNResumeUpload
  52. - (instancetype)initWithFile:(id<QNFileDelegate>)file
  53. withKey:(NSString *)key
  54. withToken:(QNUpToken *)token
  55. withCompletionHandler:(QNUpCompletionHandler)block
  56. withOption:(QNUploadOption *)option
  57. withRecorder:(id<QNRecorderDelegate>)recorder
  58. withRecorderKey:(NSString *)recorderKey
  59. withHttpManager:(id<QNHttpDelegate>)http
  60. withConfiguration:(QNConfiguration *)config;
  61. {
  62. if (self = [super init]) {
  63. _file = file;
  64. _size = (UInt32)[file size];
  65. _key = key;
  66. NSString *tokenUp = [NSString stringWithFormat:@"UpToken %@", token.token];
  67. _option = option != nil ? option : [QNUploadOption defaultOptions];
  68. _complete = block;
  69. _headers = @{@"Authorization" : tokenUp, @"Content-Type" : @"application/octet-stream"};
  70. _recorder = recorder;
  71. _httpManager = http;
  72. _modifyTime = [file modifyTime];
  73. _recorderKey = recorderKey;
  74. _contexts = [[NSMutableArray alloc] initWithCapacity:(_size + kQNBlockSize - 1) / kQNBlockSize];
  75. _config = config;
  76. _token = token;
  77. _previousPercent = 0;
  78. _access = token.access;
  79. }
  80. return self;
  81. }
  82. // save json value
  83. //{
  84. // "size":filesize,
  85. // "offset":lastSuccessOffset,
  86. // "modify_time": lastFileModifyTime,
  87. // "contexts": contexts
  88. //}
  89. - (void)record:(UInt32)offset {
  90. NSString *key = self.recorderKey;
  91. if (offset == 0 || _recorder == nil || key == nil || [key isEqualToString:@""]) {
  92. return;
  93. }
  94. NSNumber *n_size = @(self.size);
  95. NSNumber *n_offset = @(offset);
  96. NSNumber *n_time = [NSNumber numberWithLongLong:_modifyTime];
  97. NSMutableDictionary *rec = [NSMutableDictionary dictionaryWithObjectsAndKeys:n_size, @"size", n_offset, @"offset", n_time, @"modify_time", _contexts, @"contexts", nil];
  98. NSError *error;
  99. NSData *data = [NSJSONSerialization dataWithJSONObject:rec options:NSJSONWritingPrettyPrinted error:&error];
  100. if (error != nil) {
  101. NSLog(@"up record json error %@ %@", key, error);
  102. return;
  103. }
  104. error = [_recorder set:key data:data];
  105. if (error != nil) {
  106. NSLog(@"up record set error %@ %@", key, error);
  107. }
  108. }
  109. - (void)removeRecord {
  110. if (_recorder == nil) {
  111. return;
  112. }
  113. [_recorder del:self.recorderKey];
  114. }
  115. - (UInt32)recoveryFromRecord {
  116. NSString *key = self.recorderKey;
  117. if (_recorder == nil || key == nil || [key isEqualToString:@""]) {
  118. return 0;
  119. }
  120. NSData *data = [_recorder get:key];
  121. if (data == nil) {
  122. return 0;
  123. }
  124. NSError *error;
  125. NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
  126. if (error != nil) {
  127. NSLog(@"recovery error %@ %@", key, error);
  128. [_recorder del:self.key];
  129. return 0;
  130. }
  131. NSNumber *n_offset = info[@"offset"];
  132. NSNumber *n_size = info[@"size"];
  133. NSNumber *time = info[@"modify_time"];
  134. NSArray *contexts = info[@"contexts"];
  135. if (n_offset == nil || n_size == nil || time == nil || contexts == nil) {
  136. return 0;
  137. }
  138. UInt32 offset = [n_offset unsignedIntValue];
  139. UInt32 size = [n_size unsignedIntValue];
  140. if (offset > size || size != self.size) {
  141. return 0;
  142. }
  143. UInt64 t = [time unsignedLongLongValue];
  144. if (t != _modifyTime) {
  145. NSLog(@"modify time changed %llu, %llu", t, _modifyTime);
  146. return 0;
  147. }
  148. _contexts = [[NSMutableArray alloc] initWithArray:contexts copyItems:true];
  149. return offset;
  150. }
  151. - (void)nextTask:(UInt32)offset retriedTimes:(int)retried host:(NSString *)host {
  152. if (self.option.cancellationSignal()) {
  153. self.complete([QNResponseInfo cancel], self.key, nil);
  154. return;
  155. }
  156. if (offset == self.size) {
  157. QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
  158. if (info.isOK) {
  159. [self removeRecord];
  160. self.option.progressHandler(self.key, 1.0);
  161. } else if (info.couldRetry && retried < _config.retryMax) {
  162. [self nextTask:offset retriedTimes:retried + 1 host:host];
  163. return;
  164. }
  165. self.complete(info, self.key, resp);
  166. };
  167. [self makeFile:host complete:completionHandler];
  168. return;
  169. }
  170. UInt32 chunkSize = [self calcPutSize:offset];
  171. QNInternalProgressBlock progressBlock = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
  172. float percent = (float)(offset + totalBytesWritten) / (float)self.size;
  173. if (percent > 0.95) {
  174. percent = 0.95;
  175. }
  176. if (percent > _previousPercent) {
  177. _previousPercent = percent;
  178. } else {
  179. percent = _previousPercent;
  180. }
  181. self.option.progressHandler(self.key, percent);
  182. };
  183. QNCompleteBlock completionHandler = ^(QNResponseInfo *info, NSDictionary *resp) {
  184. if (info.error != nil) {
  185. if (info.statusCode == 701) {
  186. [self nextTask:(offset / kQNBlockSize) * kQNBlockSize retriedTimes:0 host:host];
  187. return;
  188. }
  189. if (retried >= _config.retryMax || !info.couldRetry) {
  190. self.complete(info, self.key, resp);
  191. return;
  192. }
  193. NSString *nextHost = host;
  194. if (info.isConnectionBroken || info.needSwitchServer) {
  195. nextHost = [_config.zone up:_token isHttps:_config.useHttps frozenDomain:nextHost];
  196. }
  197. [self nextTask:offset retriedTimes:retried + 1 host:nextHost];
  198. return;
  199. }
  200. if (resp == nil) {
  201. [self nextTask:offset retriedTimes:retried host:host];
  202. return;
  203. }
  204. NSString *ctx = resp[@"ctx"];
  205. NSNumber *crc = resp[@"crc32"];
  206. if (ctx == nil || crc == nil || [crc unsignedLongValue] != _chunkCrc) {
  207. [self nextTask:offset retriedTimes:retried host:host];
  208. return;
  209. }
  210. _contexts[offset / kQNBlockSize] = ctx;
  211. [self record:offset + chunkSize];
  212. [self nextTask:offset + chunkSize retriedTimes:retried host:host];
  213. };
  214. if (offset % kQNBlockSize == 0) {
  215. UInt32 blockSize = [self calcBlockSize:offset];
  216. [self makeBlock:host offset:offset blockSize:blockSize chunkSize:chunkSize progress:progressBlock complete:completionHandler];
  217. return;
  218. }
  219. NSString *context = _contexts[offset / kQNBlockSize];
  220. [self putChunk:host offset:offset size:chunkSize context:context progress:progressBlock complete:completionHandler];
  221. }
  222. - (UInt32)calcPutSize:(UInt32)offset {
  223. UInt32 left = self.size - offset;
  224. return left < _config.chunkSize ? left : _config.chunkSize;
  225. }
  226. - (UInt32)calcBlockSize:(UInt32)offset {
  227. UInt32 left = self.size - offset;
  228. return left < kQNBlockSize ? left : kQNBlockSize;
  229. }
  230. - (void)makeBlock:(NSString *)uphost
  231. offset:(UInt32)offset
  232. blockSize:(UInt32)blockSize
  233. chunkSize:(UInt32)chunkSize
  234. progress:(QNInternalProgressBlock)progressBlock
  235. complete:(QNCompleteBlock)complete {
  236. NSData *data = [self.file read:offset size:chunkSize];
  237. NSString *url = [[NSString alloc] initWithFormat:@"%@/mkblk/%u", uphost, (unsigned int)blockSize];
  238. _chunkCrc = [QNCrc32 data:data];
  239. [self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
  240. }
  241. - (void)putChunk:(NSString *)uphost
  242. offset:(UInt32)offset
  243. size:(UInt32)size
  244. context:(NSString *)context
  245. progress:(QNInternalProgressBlock)progressBlock
  246. complete:(QNCompleteBlock)complete {
  247. NSData *data = [self.file read:offset size:size];
  248. UInt32 chunkOffset = offset % kQNBlockSize;
  249. NSString *url = [[NSString alloc] initWithFormat:@"%@/bput/%@/%u", uphost, context, (unsigned int)chunkOffset];
  250. _chunkCrc = [QNCrc32 data:data];
  251. [self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
  252. }
  253. - (void)makeFile:(NSString *)uphost
  254. complete:(QNCompleteBlock)complete {
  255. NSString *mime = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.option.mimeType]];
  256. __block NSString *url = [[NSString alloc] initWithFormat:@"%@/mkfile/%u%@", uphost, (unsigned int)self.size, mime];
  257. if (self.key != nil) {
  258. NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
  259. url = [NSString stringWithFormat:@"%@%@", url, keyStr];
  260. }
  261. [self.option.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  262. url = [NSString stringWithFormat:@"%@/%@/%@", url, key, [QNUrlSafeBase64 encodeString:obj]];
  263. }];
  264. //添加路径
  265. NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:[self fileBaseName]]];
  266. url = [NSString stringWithFormat:@"%@%@", url, fname];
  267. NSMutableData *postData = [NSMutableData data];
  268. NSString *bodyStr = [self.contexts componentsJoinedByString:@","];
  269. [postData appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
  270. [self post:url withData:postData withCompleteBlock:complete withProgressBlock:nil];
  271. }
  272. #pragma mark - 处理文件路径
  273. - (NSString *)fileBaseName {
  274. return [[_file path] lastPathComponent];
  275. }
  276. - (void)post:(NSString *)url
  277. withData:(NSData *)data
  278. withCompleteBlock:(QNCompleteBlock)completeBlock
  279. withProgressBlock:(QNInternalProgressBlock)progressBlock {
  280. [_httpManager post:url withData:data withParams:nil withHeaders:_headers withCompleteBlock:completeBlock withProgressBlock:progressBlock withCancelBlock:_option.cancellationSignal withAccess:_access];
  281. }
  282. - (void)run {
  283. @autoreleasepool {
  284. UInt32 offset = [self recoveryFromRecord];
  285. [self nextTask:offset retriedTimes:0 host:[_config.zone up:_token isHttps:_config.useHttps frozenDomain:nil]];
  286. }
  287. }
  288. @end