QNResumeUpload.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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. @interface QNResumeUpload ()
  10. @property (nonatomic, copy) NSString *recorderKey;
  11. @property (nonatomic, strong) NSDictionary *headers;
  12. @property (nonatomic, strong) NSMutableArray *contexts;
  13. @property (nonatomic, strong) id<QNRecorderDelegate> recorder;
  14. @property (nonatomic, strong) id<QNFileDelegate> file;
  15. @property (nonatomic, copy) NSString *recordHost; // upload host in last recorder file
  16. @property (nonatomic, assign) UInt32 chunkCrc;
  17. @property (nonatomic, assign) float previousPercent;
  18. @property (nonatomic, assign) int64_t modifyTime;
  19. @end
  20. @implementation QNResumeUpload
  21. - (instancetype)initWithFile:(id<QNFileDelegate>)file
  22. withKey:(NSString *)key
  23. withToken:(QNUpToken *)token
  24. withIdentifier:(NSString *)identifier
  25. withCompletionHandler:(QNUpCompletionHandler)block
  26. withOption:(QNUploadOption *)option
  27. withRecorder:(id<QNRecorderDelegate>)recorder
  28. withRecorderKey:(NSString *)recorderKey
  29. withSessionManager:(QNSessionManager *)sessionManager
  30. withConfiguration:(QNConfiguration *)config;
  31. {
  32. if (self = [super init]) {
  33. self.file = file;
  34. self.size = (UInt32)[file size];
  35. self.key = key;
  36. NSString *tokenUp = [NSString stringWithFormat:@"UpToken %@", token.token];
  37. self.option = option != nil ? option : [QNUploadOption defaultOptions];
  38. self.complete = block;
  39. self.headers = @{@"Authorization" : tokenUp, @"Content-Type" : @"application/octet-stream"};
  40. self.recorder = recorder;
  41. self.sessionManager = sessionManager;
  42. self.modifyTime = [file modifyTime];
  43. self.recorderKey = recorderKey;
  44. self.contexts = [[NSMutableArray alloc] initWithCapacity:(self.size + kQNBlockSize - 1) / kQNBlockSize];
  45. self.config = config;
  46. self.currentZoneType = QNZoneInfoTypeMain;
  47. self.token = token;
  48. self.previousPercent = 0;
  49. self.access = token.access;
  50. self.identifier = identifier;
  51. [Collector update:CK_blockApiVersion value:@1 identifier:self.identifier];
  52. }
  53. return self;
  54. }
  55. - (void)record:(UInt32)offset host:(NSString *)host {
  56. NSString *key = self.recorderKey;
  57. if (offset == 0 || self.recorder == nil || key == nil || [key isEqualToString:@""]) {
  58. return;
  59. }
  60. NSNumber *n_size = @(self.size);
  61. NSNumber *n_offset = @(offset);
  62. NSNumber *n_time = [NSNumber numberWithLongLong:self.modifyTime];
  63. NSMutableDictionary *rec = [NSMutableDictionary dictionaryWithObjectsAndKeys:n_size, @"size", n_offset, @"offset", n_time, @"modify_time", host, @"host", self.contexts, @"contexts", nil];
  64. NSError *error;
  65. NSData *data = [NSJSONSerialization dataWithJSONObject:rec options:NSJSONWritingPrettyPrinted error:&error];
  66. if (error != nil) {
  67. NSLog(@"up record json error %@ %@", key, error);
  68. return;
  69. }
  70. error = [self.recorder set:key data:data];
  71. if (error != nil) {
  72. NSLog(@"up record set error %@ %@", key, error);
  73. }
  74. }
  75. - (void)removeRecord {
  76. if (self.recorder == nil) {
  77. return;
  78. }
  79. self.recordHost = nil;
  80. [self.contexts removeAllObjects];
  81. [self.recorder del:self.recorderKey];
  82. }
  83. - (UInt32)recoveryFromRecord {
  84. NSString *key = self.recorderKey;
  85. if (self.recorder == nil || key == nil || [key isEqualToString:@""]) {
  86. return 0;
  87. }
  88. NSData *data = [self.recorder get:key];
  89. if (data == nil) {
  90. return 0;
  91. }
  92. NSError *error;
  93. NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
  94. if (error != nil) {
  95. NSLog(@"recovery error %@ %@", key, error);
  96. [self.recorder del:self.key];
  97. return 0;
  98. }
  99. NSNumber *n_offset = info[@"offset"];
  100. NSNumber *n_size = info[@"size"];
  101. NSNumber *time = info[@"modify_time"];
  102. NSArray *contexts = info[@"contexts"];
  103. if (n_offset == nil || n_size == nil || time == nil || contexts == nil) {
  104. return 0;
  105. }
  106. UInt32 offset = [n_offset unsignedIntValue];
  107. UInt32 size = [n_size unsignedIntValue];
  108. if (offset > size || size != self.size) {
  109. return 0;
  110. }
  111. UInt64 t = [time unsignedLongLongValue];
  112. if (t != self.modifyTime) {
  113. NSLog(@"modify time changed %llu, %llu", t, self.modifyTime);
  114. return 0;
  115. }
  116. self.recordHost = info[@"host"];
  117. self.contexts = [[NSMutableArray alloc] initWithArray:contexts copyItems:true];
  118. return offset;
  119. }
  120. - (void)nextTask:(UInt32)offset needDelay:(BOOL)needDelay retriedTimes:(int)retried host:(NSString *)host {
  121. if (needDelay) {
  122. QNAsyncRunAfter(self.config.retryInterval, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  123. [self nextTask:offset retriedTimes:retried host:host];
  124. });
  125. } else {
  126. [self nextTask:offset retriedTimes:retried host:host];
  127. }
  128. }
  129. - (void)nextTask:(UInt32)offset retriedTimes:(int)retried host:(NSString *)host {
  130. if (self.option.cancellationSignal()) {
  131. [self collectUploadQualityInfo];
  132. QNResponseInfo *info = [Collector userCancel:self.identifier];
  133. self.complete(info, self.key, nil);
  134. return;
  135. }
  136. if (offset == self.size) {
  137. QNCompleteBlock completionHandler = ^(QNHttpResponseInfo *httpResponseInfo, NSDictionary *respBody) {
  138. [self collectHttpResponseInfo:httpResponseInfo fileOffset:offset];
  139. if (httpResponseInfo.isOK) {
  140. [self removeRecord];
  141. self.option.progressHandler(self.key, 1.0);
  142. [self collectUploadQualityInfo];
  143. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  144. self.complete(info, self.key, respBody);
  145. } else if (httpResponseInfo.couldRetry) {
  146. if (retried < self.config.retryMax) {
  147. [self nextTask:offset needDelay:YES retriedTimes:retried + 1 host:host];
  148. } else {
  149. if (self.config.allowBackupHost) {
  150. NSString *nextHost = nil;
  151. UInt32 nextOffset = 0;
  152. if (self.recordHost) {
  153. self.previousPercent = 0;
  154. [self removeRecord];
  155. self.currentZoneType = QNZoneInfoTypeMain;
  156. nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil];
  157. nextOffset = 0;
  158. } else {
  159. nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:host];
  160. nextOffset = offset;
  161. }
  162. if (nextHost) {
  163. [self nextTask:nextOffset needDelay:YES retriedTimes:0 host:nextHost];
  164. } else {
  165. QNZonesInfo *zonesInfo = [self.config.zone getZonesInfoWithToken:self.token];
  166. if (self.currentZoneType == QNZoneInfoTypeMain && zonesInfo.hasBackupZone) {
  167. self.currentZoneType = QNZoneInfoTypeBackup;
  168. self.previousPercent = 0;
  169. [self removeRecord];
  170. [self nextTask:0 needDelay:YES retriedTimes:0 host:[self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil]];
  171. } else {
  172. [self collectUploadQualityInfo];
  173. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  174. self.complete(info, self.key, respBody);
  175. }
  176. }
  177. } else {
  178. [self collectUploadQualityInfo];
  179. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  180. self.complete(info, self.key, respBody);
  181. }
  182. }
  183. } else {
  184. [self collectUploadQualityInfo];
  185. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  186. self.complete(info, self.key, respBody);
  187. }
  188. };
  189. [self makeFile:host complete:completionHandler];
  190. return;
  191. }
  192. UInt32 chunkSize = [self calcPutSize:offset];
  193. QNInternalProgressBlock progressBlock = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
  194. float percent = (float)(offset + totalBytesWritten) / (float)self.size;
  195. if (percent > 0.95) {
  196. percent = 0.95;
  197. }
  198. if (percent > self.previousPercent) {
  199. self.previousPercent = percent;
  200. } else {
  201. percent = self.previousPercent;
  202. }
  203. self.option.progressHandler(self.key, percent);
  204. };
  205. QNCompleteBlock completionHandler = ^(QNHttpResponseInfo *httpResponseInfo, NSDictionary *respBody) {
  206. [self collectHttpResponseInfo:httpResponseInfo fileOffset:offset];
  207. NSString *ctx = respBody[@"ctx"];
  208. NSNumber *crc = respBody[@"crc32"];
  209. if (httpResponseInfo.isOK && ctx && crc && [crc unsignedLongValue] == self.chunkCrc) {
  210. self.contexts[offset / kQNBlockSize] = ctx;
  211. [self record:offset + chunkSize host:host];
  212. [self nextTask:offset + chunkSize needDelay:NO retriedTimes:retried host:host];
  213. } else if (httpResponseInfo.couldRetry) {
  214. if (retried < self.config.retryMax) {
  215. [self nextTask:offset needDelay:YES retriedTimes:retried + 1 host:host];
  216. } else {
  217. if (self.config.allowBackupHost) {
  218. NSString *nextHost = nil;
  219. UInt32 nextOffset = 0;
  220. if (self.recordHost) {
  221. self.previousPercent = 0;
  222. [self removeRecord];
  223. self.currentZoneType = QNZoneInfoTypeMain;
  224. nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil];
  225. nextOffset = 0;
  226. } else {
  227. nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:host];
  228. nextOffset = offset;
  229. }
  230. if (nextHost) {
  231. [self nextTask:nextOffset needDelay:YES retriedTimes:0 host:nextHost];
  232. } else {
  233. QNZonesInfo *zonesInfo = [self.config.zone getZonesInfoWithToken:self.token];
  234. if (self.currentZoneType == QNZoneInfoTypeMain && zonesInfo.hasBackupZone) {
  235. self.currentZoneType = QNZoneInfoTypeBackup;
  236. self.previousPercent = 0;
  237. [self removeRecord];
  238. [self nextTask:0 needDelay:YES retriedTimes:0 host:[self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil]];
  239. } else {
  240. [self collectUploadQualityInfo];
  241. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  242. self.complete(info, self.key, respBody);
  243. }
  244. }
  245. } else {
  246. [self collectUploadQualityInfo];
  247. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  248. self.complete(info, self.key, respBody);
  249. }
  250. }
  251. } else {
  252. if (httpResponseInfo.statusCode == 701) {
  253. [self nextTask:(offset / kQNBlockSize) * kQNBlockSize needDelay:YES retriedTimes:0 host:host];
  254. } else {
  255. [self collectUploadQualityInfo];
  256. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  257. self.complete(info, self.key, respBody);
  258. }
  259. }
  260. };
  261. if (offset % kQNBlockSize == 0) {
  262. UInt32 blockSize = [self calcBlockSize:offset];
  263. [self makeBlock:host offset:offset blockSize:blockSize chunkSize:chunkSize progress:progressBlock complete:completionHandler];
  264. return;
  265. }
  266. NSString *context = self.contexts[offset / kQNBlockSize];
  267. [self putChunk:host offset:offset size:chunkSize context:context progress:progressBlock complete:completionHandler];
  268. }
  269. - (UInt32)calcPutSize:(UInt32)offset {
  270. UInt32 left = self.size - offset;
  271. return left < self.config.chunkSize ? left : self.config.chunkSize;
  272. }
  273. - (UInt32)calcBlockSize:(UInt32)offset {
  274. UInt32 left = self.size - offset;
  275. return left < kQNBlockSize ? left : kQNBlockSize;
  276. }
  277. - (void)makeBlock:(NSString *)uphost
  278. offset:(UInt32)offset
  279. blockSize:(UInt32)blockSize
  280. chunkSize:(UInt32)chunkSize
  281. progress:(QNInternalProgressBlock)progressBlock
  282. complete:(QNCompleteBlock)complete {
  283. self.requestType = QNRequestType_mkblk;
  284. NSError *error;
  285. NSData *data = [self.file read:offset size:chunkSize error:&error];
  286. if (error) {
  287. [self collectUploadQualityInfo];
  288. QNResponseInfo *info = [Collector completeWithLocalIOError:error identifier:self.identifier];
  289. self.complete(info, self.key, nil);
  290. return;
  291. }
  292. NSString *url = [[NSString alloc] initWithFormat:@"%@/mkblk/%u", uphost, (unsigned int)blockSize];
  293. self.chunkCrc = [QNCrc32 data:data];
  294. [self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
  295. }
  296. - (void)putChunk:(NSString *)uphost
  297. offset:(UInt32)offset
  298. size:(UInt32)size
  299. context:(NSString *)context
  300. progress:(QNInternalProgressBlock)progressBlock
  301. complete:(QNCompleteBlock)complete {
  302. self.requestType = QNRequestType_bput;
  303. NSError *error;
  304. NSData *data = [self.file read:offset size:size error:&error];
  305. if (error) {
  306. [self collectUploadQualityInfo];
  307. QNResponseInfo *info = [Collector completeWithLocalIOError:error identifier:self.identifier];
  308. self.complete(info, self.key, nil);
  309. return;
  310. }
  311. UInt32 chunkOffset = offset % kQNBlockSize;
  312. NSString *url = [[NSString alloc] initWithFormat:@"%@/bput/%@/%u", uphost, context, (unsigned int)chunkOffset];
  313. self.chunkCrc = [QNCrc32 data:data];
  314. [self post:url withData:data withCompleteBlock:complete withProgressBlock:progressBlock];
  315. }
  316. - (void)makeFile:(NSString *)uphost
  317. complete:(QNCompleteBlock)complete {
  318. self.requestType = QNRequestType_mkfile;
  319. NSString *mime = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.option.mimeType]];
  320. __block NSString *url = [[NSString alloc] initWithFormat:@"%@/mkfile/%u%@", uphost, (unsigned int)self.size, mime];
  321. if (self.key != nil) {
  322. NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
  323. url = [NSString stringWithFormat:@"%@%@", url, keyStr];
  324. }
  325. [self.option.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  326. url = [NSString stringWithFormat:@"%@/%@/%@", url, key, [QNUrlSafeBase64 encodeString:obj]];
  327. }];
  328. //添加路径
  329. NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:[self fileBaseName]]];
  330. url = [NSString stringWithFormat:@"%@%@", url, fname];
  331. NSMutableData *postData = [NSMutableData data];
  332. NSString *bodyStr = [self.contexts componentsJoinedByString:@","];
  333. [postData appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
  334. [self post:url withData:postData withCompleteBlock:complete withProgressBlock:nil];
  335. }
  336. #pragma mark - 处理文件路径
  337. - (NSString *)fileBaseName {
  338. return [[self.file path] lastPathComponent];
  339. }
  340. - (void)post:(NSString *)url
  341. withData:(NSData *)data
  342. withCompleteBlock:(QNCompleteBlock)completeBlock
  343. withProgressBlock:(QNInternalProgressBlock)progressBlock {
  344. [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];
  345. }
  346. - (void)run {
  347. @autoreleasepool {
  348. UInt32 offset = [self recoveryFromRecord];
  349. [Collector update:CK_recoveredFrom value:@(offset) identifier:self.identifier];
  350. if (offset > 0) {
  351. [self nextTask:offset needDelay:NO retriedTimes:0 host:self.recordHost];
  352. } else {
  353. [self nextTask:offset needDelay:NO retriedTimes:0 host:[self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil]];
  354. }
  355. }
  356. }
  357. @end