QNConcurrentResumeUpload.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. //
  2. // QNConcurrentResumeUpload.m
  3. // QiniuSDK
  4. //
  5. // Created by WorkSpace_Sun on 2019/7/15.
  6. // Copyright © 2019 Qiniu. All rights reserved.
  7. //
  8. #import "QNConcurrentResumeUpload.h"
  9. @interface QNConcurrentRecorderInfo : NSObject
  10. @property (nonatomic, strong) NSNumber *totalSize; // total size of the file
  11. @property (nonatomic, strong) NSNumber *modifyTime; // modify time of the file
  12. @property (nonatomic, copy) NSString *host; // upload host used last time
  13. @property (nonatomic, strong) NSArray<NSDictionary *> *contextsInfo; // concurrent upload contexts info
  14. - (instancetype)init __attribute__((unavailable("use recorderInfoWithTotalSize:totalSize:modifyTime:host:contextsInfo: instead.")));
  15. @end
  16. @implementation QNConcurrentRecorderInfo
  17. + (instancetype)recorderInfoWithTotalSize:(NSNumber *)totalSize modifyTime:(NSNumber *)modifyTime host:(NSString *)host contextsInfo:(NSArray<NSDictionary *> *)contextsInfo {
  18. return [[QNConcurrentRecorderInfo alloc] initWithTotalSize:totalSize modifyTime:modifyTime host:host contextsInfo:contextsInfo];
  19. }
  20. - (instancetype)initWithTotalSize:(NSNumber *)totalSize modifyTime:(NSNumber *)modifyTime host:(NSString *)host contextsInfo:(NSArray<NSDictionary *> *)contextsInfo {
  21. self = [super init];
  22. if (self) {
  23. _totalSize = totalSize ? totalSize : @0;
  24. _modifyTime = modifyTime ? modifyTime : @0;
  25. _host = host ? host : @"";
  26. _contextsInfo = contextsInfo ? contextsInfo : @[];
  27. }
  28. return self;
  29. }
  30. - (NSData *)buildRecorderInfoJsonData:(NSError **)error {
  31. NSDictionary *recorderInfo = @{
  32. @"total_size": _totalSize,
  33. @"modify_time": _modifyTime,
  34. @"host": _host,
  35. @"contexts_info": _contextsInfo
  36. };
  37. NSData *data = [NSJSONSerialization dataWithJSONObject:recorderInfo options:NSJSONWritingPrettyPrinted error:error];
  38. return data;
  39. }
  40. + (QNConcurrentRecorderInfo *)buildRecorderInfoWithData:(NSData *)data error:(NSError **)error {
  41. NSDictionary *recorderInfo = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:error];
  42. return [[self class] recorderInfoWithTotalSize:recorderInfo[@"total_size"] modifyTime:recorderInfo[@"modify_time"] host:recorderInfo[@"host"] contextsInfo:recorderInfo[@"contexts_info"]];
  43. }
  44. @end
  45. @interface QNConcurrentTask: NSObject
  46. @property (nonatomic, assign) int index; // block index in the file
  47. @property (nonatomic, assign) UInt32 size; // total size of the block
  48. @property (atomic, assign) UInt32 uploadedSize; // uploaded size of the block
  49. @property (nonatomic, copy) NSString *context;
  50. @property (nonatomic, assign) BOOL isTaskCompleted;
  51. - (instancetype)init __attribute__((unavailable("use concurrentTaskWithBlockIndex:blockSize: instead.")));
  52. @end
  53. @implementation QNConcurrentTask
  54. + (instancetype)concurrentTaskWithBlockIndex:(int)index blockSize:(UInt32)size {
  55. return [[QNConcurrentTask alloc] initWithBlockIndex:index blockSize:size];
  56. }
  57. - (instancetype)initWithBlockIndex:(int)index blockSize:(UInt32)size
  58. {
  59. self = [super init];
  60. if (self) {
  61. _isTaskCompleted = NO;
  62. _uploadedSize = 0;
  63. _size = size;
  64. _index = index;
  65. }
  66. return self;
  67. }
  68. @end
  69. @interface QNConcurrentTaskQueue: NSObject
  70. @property (nonatomic, strong) id<QNFileDelegate> file;
  71. @property (nonatomic, strong) QNConfiguration *config;
  72. @property (nonatomic, strong) QNUpToken *token; // token
  73. @property (nonatomic, assign) UInt32 totalSize; // 文件总大小
  74. @property (nonatomic, strong) NSArray<NSDictionary *> *contextsInfo; // 续传context信息
  75. @property (nonatomic, strong) NSMutableArray<QNConcurrentTask *> *taskQueueArray; // block 任务队列
  76. @property (nonatomic, assign) UInt32 taskQueueCount; // 实际并发任务数量
  77. @property (atomic, assign) int nextTaskIndex; // 下一个任务的index
  78. @property (nonatomic, assign, getter=isAllCompleted) BOOL isAllCompleted; // completed
  79. @property (nonatomic, assign, getter=totalPercent) float totalPercent; // 上传总进度
  80. @property (nonatomic, assign) BOOL isConcurrentTaskError; // error
  81. @property (nonatomic, strong) QNResponseInfo *info; // errorInfo if error
  82. @property (nonatomic, strong) NSDictionary *resp; // errorResp if error
  83. - (instancetype)init __attribute__((unavailable("use taskQueueWithFile:config:totalSize:recordInfo:token: instead.")));
  84. @end
  85. @implementation QNConcurrentTaskQueue
  86. + (instancetype)taskQueueWithFile:(id<QNFileDelegate>)file
  87. config:(QNConfiguration *)config
  88. totalSize:(UInt32)totalSize
  89. contextsInfo:(NSArray<NSDictionary *> *)contextsInfo
  90. token:(QNUpToken *)token {
  91. return [[QNConcurrentTaskQueue alloc] initWithFile:file
  92. config:config
  93. totalSize:totalSize
  94. contextsInfo:contextsInfo
  95. token:token];
  96. }
  97. - (instancetype)initWithFile:(id<QNFileDelegate>)file
  98. config:(QNConfiguration *)config
  99. totalSize:(UInt32)totalSize
  100. contextsInfo:(NSArray<NSDictionary *> *)contextsInfo
  101. token:(QNUpToken *)token {
  102. self = [super init];
  103. if (self) {
  104. _file = file;
  105. _config = config;
  106. _totalSize = totalSize;
  107. _contextsInfo = contextsInfo;
  108. _token = token;
  109. _taskQueueArray = [NSMutableArray array];
  110. _isConcurrentTaskError = NO;
  111. _nextTaskIndex = 0;
  112. _taskQueueCount = 0;
  113. [self initTaskQueue];
  114. }
  115. return self;
  116. }
  117. - (void)initTaskQueue {
  118. // add recover task
  119. if (_contextsInfo.count > 0) {
  120. for (NSDictionary *info in _contextsInfo) {
  121. int block_index = [info[@"block_index"] intValue];
  122. UInt32 block_size = [info[@"block_size"] unsignedIntValue];
  123. NSString *context = info[@"context"];
  124. QNConcurrentTask *recoveryTask = [QNConcurrentTask concurrentTaskWithBlockIndex:block_index blockSize:block_size];
  125. recoveryTask.uploadedSize = block_size;
  126. recoveryTask.context = context;
  127. recoveryTask.isTaskCompleted = YES;
  128. [_taskQueueArray addObject:recoveryTask];
  129. }
  130. }
  131. int blockCount = _totalSize % kQNBlockSize == 0 ? _totalSize / kQNBlockSize : _totalSize / kQNBlockSize + 1;
  132. _taskQueueCount = blockCount > _config.concurrentTaskCount ? _config.concurrentTaskCount : blockCount;
  133. for (int i = 0; i < blockCount; i++) {
  134. BOOL isTaskExisted = NO;
  135. for (int j = 0; j < _taskQueueArray.count; j++) {
  136. if (_taskQueueArray[j].index == i) {
  137. isTaskExisted = YES;
  138. break;
  139. }
  140. }
  141. if (!isTaskExisted) {
  142. UInt32 left = _totalSize - i * kQNBlockSize;
  143. UInt32 blockSize = left < kQNBlockSize ? left : kQNBlockSize;
  144. QNConcurrentTask *task = [QNConcurrentTask concurrentTaskWithBlockIndex:i blockSize:blockSize];
  145. [_taskQueueArray addObject:task];
  146. }
  147. }
  148. }
  149. - (QNConcurrentTask *)getNextTask {
  150. QNConcurrentTask *nextTask = nil;
  151. while (_nextTaskIndex < _taskQueueArray.count) {
  152. QNConcurrentTask *task = _taskQueueArray[_nextTaskIndex];
  153. _nextTaskIndex++;
  154. if (!task.isTaskCompleted) {
  155. nextTask = task;
  156. break;
  157. }
  158. }
  159. return nextTask;
  160. }
  161. - (void)reset {
  162. // reset
  163. _contextsInfo = nil;
  164. _resp = nil;
  165. _info = nil;
  166. _nextTaskIndex = 0;
  167. _taskQueueCount = 0;
  168. _isConcurrentTaskError = NO;
  169. [_taskQueueArray removeAllObjects];
  170. [self initTaskQueue];
  171. }
  172. - (void)buildErrorWithInfo:(QNResponseInfo *)info resp:(NSDictionary *)resp {
  173. if (_isConcurrentTaskError) return;
  174. _isConcurrentTaskError = YES;
  175. _info = info;
  176. _resp = resp;
  177. }
  178. - (BOOL)completeTask:(QNConcurrentTask *)task withContext:(NSString *)context {
  179. task.uploadedSize = task.size;
  180. task.context = context;
  181. task.isTaskCompleted = YES;
  182. return _nextTaskIndex < _taskQueueArray.count;
  183. }
  184. - (NSArray *)getRecordInfo {
  185. NSMutableArray *infoArray = [NSMutableArray array];
  186. for (QNConcurrentTask *task in _taskQueueArray) {
  187. if (task.isTaskCompleted) {
  188. [infoArray addObject:@{
  189. @"block_index":@(task.index),
  190. @"block_size":@(task.size),
  191. @"context":task.context
  192. }];
  193. }
  194. }
  195. return infoArray;
  196. }
  197. - (NSArray *)getContexts {
  198. NSArray *sortedTaskQueueArray = [_taskQueueArray sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
  199. QNConcurrentTask *task1 = obj1;
  200. QNConcurrentTask *task2 = obj2;
  201. return task1.index > task2.index;
  202. }];
  203. NSMutableArray *contextArray = [NSMutableArray arrayWithCapacity:sortedTaskQueueArray.count];
  204. for (QNConcurrentTask *task in sortedTaskQueueArray) {
  205. if (task.isTaskCompleted) {
  206. [contextArray addObject:task.context];
  207. }
  208. }
  209. return contextArray;
  210. }
  211. - (BOOL)isAllCompleted {
  212. BOOL isAllTaskCompleted = YES;
  213. for (QNConcurrentTask *task in _taskQueueArray) {
  214. if (!task.isTaskCompleted) {
  215. isAllTaskCompleted = NO;
  216. break;
  217. }
  218. }
  219. return isAllTaskCompleted && !_isConcurrentTaskError && !_info && !_resp;
  220. }
  221. - (float)totalPercent {
  222. long long totalUploadSize = 0;
  223. for (QNConcurrentTask *task in _taskQueueArray) {
  224. totalUploadSize += task.uploadedSize;
  225. }
  226. return totalUploadSize / (float)_totalSize < 0.95 ? totalUploadSize / (float)_totalSize : 0.95;
  227. }
  228. @end
  229. @interface QNConcurrentResumeUpload ()
  230. @property (nonatomic, strong) id<QNRecorderDelegate> recorder;
  231. @property (nonatomic, strong) id<QNFileDelegate> file;
  232. @property (nonatomic, strong) QNConcurrentTaskQueue *taskQueue;
  233. @property (nonatomic, strong) QNConcurrentRecorderInfo *recordInfo; // 续传信息
  234. @property (nonatomic, copy) NSString *recorderKey;
  235. @property (nonatomic, strong) NSDictionary *headers;
  236. @property (nonatomic, strong) dispatch_group_t uploadGroup;
  237. @property (nonatomic, strong) dispatch_queue_t uploadQueue;
  238. @property (nonatomic, copy) NSString *upHost;
  239. @property (nonatomic, assign) UInt32 retriedTimes; // 当前域名重试次数
  240. @property (nonatomic, assign) int64_t modifyTime;
  241. @property (nonatomic, assign, getter=isResettingTaskQueue) BOOL resettingTaskQueue;
  242. @end
  243. @implementation QNConcurrentResumeUpload
  244. - (instancetype)initWithFile:(id<QNFileDelegate>)file
  245. withKey:(NSString *)key
  246. withToken:(QNUpToken *)token
  247. withIdentifier:(NSString *)identifier
  248. withRecorder:(id<QNRecorderDelegate>)recorder
  249. withRecorderKey:(NSString *)recorderKey
  250. withSessionManager:(QNSessionManager *)sessionManager
  251. withCompletionHandler:(QNUpCompletionHandler)block
  252. withOption:(QNUploadOption *)option
  253. withConfiguration:(QNConfiguration *)config {
  254. if (self = [super init]) {
  255. self.file = file;
  256. self.size = (UInt32)[file size];
  257. self.key = key;
  258. self.recorder = recorder;
  259. self.recorderKey = recorderKey;
  260. self.modifyTime = [file modifyTime];
  261. self.option = option != nil ? option : [QNUploadOption defaultOptions];
  262. self.complete = block;
  263. self.headers = @{@"Authorization" : [NSString stringWithFormat:@"UpToken %@", token.token], @"Content-Type" : @"application/octet-stream"};
  264. self.config = config;
  265. self.token = token;
  266. self.access = token.access;
  267. self.sessionManager = sessionManager;
  268. self.identifier = identifier;
  269. self.resettingTaskQueue = NO;
  270. self.retriedTimes = 0;
  271. self.currentZoneType = QNZoneInfoTypeMain;
  272. self.uploadGroup = dispatch_group_create();
  273. self.uploadQueue = dispatch_queue_create("com.qiniu.concurrentUpload", DISPATCH_QUEUE_SERIAL);
  274. self.recordInfo = [self recoveryFromRecord];
  275. self.taskQueue = [QNConcurrentTaskQueue
  276. taskQueueWithFile:file
  277. config:config
  278. totalSize:self.size
  279. contextsInfo:self.recordInfo.contextsInfo
  280. token:self.token];
  281. [Collector update:CK_blockApiVersion value:@1 identifier:self.identifier];
  282. }
  283. return self;
  284. }
  285. - (void)run {
  286. self.requestType = QNRequestType_mkblk;
  287. if (self.recordInfo.host && ![self.recordInfo.host isEqualToString:@""]) {
  288. self.upHost = self.recordInfo.host;
  289. } else {
  290. self.upHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:nil];
  291. }
  292. for (int i = 0; i < _taskQueue.taskQueueCount; i++) {
  293. dispatch_group_enter(_uploadGroup);
  294. dispatch_group_async(_uploadGroup, _uploadQueue, ^{
  295. [self putBlockWithTask:[self.taskQueue getNextTask] host:self.upHost];
  296. });
  297. }
  298. dispatch_group_notify(_uploadGroup, _uploadQueue, ^{
  299. if (self.taskQueue.isAllCompleted) {
  300. self.requestType = QNRequestType_mkfile;
  301. [self makeFile];
  302. } else {
  303. if (self.isResettingTaskQueue) {
  304. self.resettingTaskQueue = NO;
  305. [self removeRecord];
  306. [self.taskQueue reset];
  307. [self run];
  308. } else {
  309. self.complete(self.taskQueue.info, self.key, self.taskQueue.resp);
  310. }
  311. }
  312. });
  313. }
  314. - (void)retryWithDelay:(BOOL)needDelay task:(QNConcurrentTask *)task host:(NSString *)host {
  315. if (needDelay) {
  316. QNAsyncRunAfter(self.config.retryInterval, self.uploadQueue, ^{
  317. switch (self.requestType) {
  318. case QNRequestType_mkblk:
  319. [self putBlockWithTask:task host:host];
  320. break;
  321. case QNRequestType_mkfile:
  322. [self makeFile];
  323. break;
  324. default:
  325. break;
  326. }
  327. });
  328. } else {
  329. switch (self.requestType) {
  330. case QNRequestType_mkblk:
  331. [self putBlockWithTask:task host:host];
  332. break;
  333. case QNRequestType_mkfile:
  334. [self makeFile];
  335. break;
  336. default:
  337. break;
  338. }
  339. }
  340. }
  341. - (void)putBlockWithTask:(QNConcurrentTask *)task host:(NSString *)host {
  342. if (self.taskQueue.isConcurrentTaskError) {
  343. dispatch_group_leave(self.uploadGroup);
  344. return;
  345. }
  346. if (self.option.cancellationSignal()) {
  347. [self collectUploadQualityInfo];
  348. QNResponseInfo *info = [Collector userCancel:self.identifier];
  349. [self invalidateTasksWithErrorInfo:info resp:nil];
  350. dispatch_group_leave(self.uploadGroup);
  351. return;
  352. }
  353. NSError *error;
  354. NSData *data = [self.file read:task.index * kQNBlockSize size:task.size error:&error];
  355. if (error) {
  356. [self collectUploadQualityInfo];
  357. QNResponseInfo *info = [Collector completeWithLocalIOError:error identifier:self.identifier];
  358. [self invalidateTasksWithErrorInfo:info resp:nil];
  359. dispatch_group_leave(self.uploadGroup);
  360. return;
  361. }
  362. UInt32 blockCrc = [QNCrc32 data:data];
  363. QNInternalProgressBlock progressBlock = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite) {
  364. if (self.taskQueue.isConcurrentTaskError) return;
  365. if (totalBytesWritten >= task.uploadedSize) {
  366. task.uploadedSize = (unsigned int)totalBytesWritten;
  367. }
  368. self.option.progressHandler(self.key, self.taskQueue.totalPercent);
  369. };
  370. QNCompleteBlock completionHandler = ^(QNHttpResponseInfo *httpResponseInfo, NSDictionary *respBody) {
  371. dispatch_async(self.uploadQueue, ^{
  372. if (self.taskQueue.isConcurrentTaskError) {
  373. dispatch_group_leave(self.uploadGroup);
  374. return;
  375. }
  376. [self collectHttpResponseInfo:httpResponseInfo fileOffset:task.index * task.size];
  377. NSString *ctx = respBody[@"ctx"];
  378. NSNumber *crc = respBody[@"crc32"];
  379. if (httpResponseInfo.isOK && ctx && crc && [crc unsignedLongValue] == blockCrc) {
  380. self.option.progressHandler(self.key, self.taskQueue.totalPercent);
  381. BOOL hasMore = [self.taskQueue completeTask:task withContext:ctx];
  382. [self record];
  383. if (hasMore) {
  384. [self retryWithDelay:YES task:[self.taskQueue getNextTask] host:self.upHost];
  385. } else {
  386. dispatch_group_leave(self.uploadGroup);
  387. }
  388. } else if (httpResponseInfo.couldRetry) {
  389. if (self.retriedTimes < self.config.retryMax) {
  390. if ([host isEqualToString:self.upHost]) {
  391. self.retriedTimes++;
  392. }
  393. [self retryWithDelay:YES task:task host:self.upHost];
  394. } else {
  395. self.retriedTimes = 0;
  396. if (self.config.allowBackupHost) {
  397. if (self.recordInfo.host) {
  398. self.currentZoneType = QNZoneInfoTypeMain;
  399. [self invalidateTasksWithErrorInfo:nil resp:nil];
  400. self.resettingTaskQueue = YES;
  401. dispatch_group_leave(self.uploadGroup);
  402. } else {
  403. NSString *nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:host];
  404. if (nextHost) {
  405. self.upHost = nextHost;
  406. [self retryWithDelay:YES task:task host:self.upHost];
  407. } else {
  408. QNZonesInfo *zonesInfo = [self.config.zone getZonesInfoWithToken:self.token];
  409. if (self.currentZoneType == QNZoneInfoTypeMain && zonesInfo.hasBackupZone) {
  410. self.currentZoneType = QNZoneInfoTypeBackup;
  411. [self invalidateTasksWithErrorInfo:nil resp:nil];
  412. self.resettingTaskQueue = YES;
  413. } else {
  414. [self collectUploadQualityInfo];
  415. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  416. [self invalidateTasksWithErrorInfo:info resp:respBody];
  417. }
  418. dispatch_group_leave(self.uploadGroup);
  419. }
  420. }
  421. } else {
  422. [self collectUploadQualityInfo];
  423. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  424. [self invalidateTasksWithErrorInfo:info resp:respBody];
  425. dispatch_group_leave(self.uploadGroup);
  426. }
  427. }
  428. } else {
  429. [self collectUploadQualityInfo];
  430. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  431. [self invalidateTasksWithErrorInfo:info resp:respBody];
  432. dispatch_group_leave(self.uploadGroup);
  433. }
  434. });
  435. };
  436. NSString *url = [[NSString alloc] initWithFormat:@"%@/mkblk/%u", host, (unsigned int)task.size];
  437. [self post:url withData:data withCompleteBlock:completionHandler withProgressBlock:progressBlock];
  438. }
  439. - (void)makeFile {
  440. NSString *mime = [[NSString alloc] initWithFormat:@"/mimeType/%@", [QNUrlSafeBase64 encodeString:self.option.mimeType]];
  441. __block NSString *url = [[NSString alloc] initWithFormat:@"%@/mkfile/%u%@", self.upHost, (unsigned int)self.size, mime];
  442. if (self.key != nil) {
  443. NSString *keyStr = [[NSString alloc] initWithFormat:@"/key/%@", [QNUrlSafeBase64 encodeString:self.key]];
  444. url = [NSString stringWithFormat:@"%@%@", url, keyStr];
  445. }
  446. [self.option.params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
  447. url = [NSString stringWithFormat:@"%@/%@/%@", url, key, [QNUrlSafeBase64 encodeString:obj]];
  448. }];
  449. //添加路径
  450. NSString *fname = [[NSString alloc] initWithFormat:@"/fname/%@", [QNUrlSafeBase64 encodeString:[[_file path] lastPathComponent]]];
  451. url = [NSString stringWithFormat:@"%@%@", url, fname];
  452. NSArray *contextArray = [_taskQueue getContexts];
  453. NSString *bodyStr = [contextArray componentsJoinedByString:@","];
  454. NSMutableData *postData = [NSMutableData data];
  455. [postData appendData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
  456. QNCompleteBlock completionHandler = ^(QNHttpResponseInfo *httpResponseInfo, NSDictionary *respBody) {
  457. dispatch_async(self.uploadQueue, ^{
  458. [self collectHttpResponseInfo:httpResponseInfo fileOffset:self.size];
  459. if (httpResponseInfo.isOK) {
  460. [self removeRecord];
  461. self.option.progressHandler(self.key, 1.0);
  462. [self collectUploadQualityInfo];
  463. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  464. self.complete(info, self.key, respBody);
  465. } else if (httpResponseInfo.couldRetry) {
  466. if (self.retriedTimes < self.config.retryMax) {
  467. self.retriedTimes++;
  468. [self retryWithDelay:YES task:nil host:nil];
  469. } else {
  470. self.retriedTimes = 0;
  471. if (self.config.allowBackupHost) {
  472. if (self.recordInfo.host) {
  473. self.currentZoneType = QNZoneInfoTypeMain;
  474. [self.taskQueue reset];
  475. [self removeRecord];
  476. [self run];
  477. } else {
  478. NSString *nextHost = [self.config.zone up:self.token zoneInfoType:self.currentZoneType isHttps:self.config.useHttps frozenDomain:self.upHost];
  479. if (nextHost) {
  480. self.upHost = nextHost;
  481. [self retryWithDelay:YES task:nil host:nil];
  482. } else {
  483. QNZonesInfo *zonesInfo = [self.config.zone getZonesInfoWithToken:self.token];
  484. if (self.currentZoneType == QNZoneInfoTypeMain && zonesInfo.hasBackupZone) {
  485. self.currentZoneType = QNZoneInfoTypeBackup;
  486. [self.taskQueue reset];
  487. [self removeRecord];
  488. [self run];
  489. } else {
  490. [self collectUploadQualityInfo];
  491. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  492. self.complete(info, self.key, respBody);
  493. }
  494. }
  495. }
  496. } else {
  497. [self collectUploadQualityInfo];
  498. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  499. self.complete(info, self.key, respBody);
  500. }
  501. }
  502. } else {
  503. [self collectUploadQualityInfo];
  504. QNResponseInfo *info = [Collector completeWithHttpResponseInfo:httpResponseInfo identifier:self.identifier];
  505. self.complete(info, self.key, respBody);
  506. }
  507. });
  508. };
  509. [self post:url withData:postData withCompleteBlock:completionHandler withProgressBlock:nil];
  510. }
  511. - (void)record {
  512. NSString *key = self.recorderKey;
  513. if (self.recorder == nil || key == nil || [key isEqualToString:@""]) {
  514. return;
  515. }
  516. NSNumber *total_size = @(self.size);
  517. NSNumber *modify_time = [NSNumber numberWithLongLong:_modifyTime];
  518. QNConcurrentRecorderInfo *recorderInfo = [QNConcurrentRecorderInfo recorderInfoWithTotalSize:total_size
  519. modifyTime:modify_time
  520. host:self.upHost
  521. contextsInfo:[self.taskQueue getRecordInfo]];
  522. NSError *error;
  523. NSData *recorderInfoData = [recorderInfo buildRecorderInfoJsonData:&error];
  524. if (error != nil) {
  525. NSLog(@"up record json error %@ %@", key, error);
  526. return;
  527. }
  528. error = [self.recorder set:key data:recorderInfoData];
  529. if (error != nil) {
  530. NSLog(@"up record set error %@ %@", key, error);
  531. }
  532. }
  533. - (void)removeRecord {
  534. if (self.recorder == nil) {
  535. return;
  536. }
  537. self.recordInfo = nil;
  538. [self.recorder del:self.recorderKey];
  539. }
  540. - (QNConcurrentRecorderInfo *)recoveryFromRecord {
  541. NSString *key = self.recorderKey;
  542. if (self.recorder == nil || key == nil || [key isEqualToString:@""]) {
  543. return nil;
  544. }
  545. NSData *data = [self.recorder get:key];
  546. if (data == nil) {
  547. return nil;
  548. }
  549. NSError *error;
  550. QNConcurrentRecorderInfo *recordInfo = [QNConcurrentRecorderInfo buildRecorderInfoWithData:data error:&error];
  551. if (error != nil) {
  552. NSLog(@"recovery error %@ %@", key, error);
  553. [self.recorder del:self.key];
  554. return nil;
  555. }
  556. if (recordInfo.totalSize == nil || recordInfo.modifyTime == nil || recordInfo.contextsInfo == nil || recordInfo.contextsInfo.count == 0) {
  557. return nil;
  558. }
  559. UInt32 size = [recordInfo.totalSize unsignedIntValue];
  560. if (size != self.size) {
  561. return nil;
  562. }
  563. UInt32 t = [recordInfo.modifyTime unsignedIntValue];
  564. if (t != self.modifyTime) {
  565. NSLog(@"modify time changed %u, %llu", (unsigned int)t, self.modifyTime);
  566. return nil;
  567. }
  568. return recordInfo;
  569. }
  570. - (void)post:(NSString *)url
  571. withData:(NSData *)data
  572. withCompleteBlock:(QNCompleteBlock)completeBlock
  573. withProgressBlock:(QNInternalProgressBlock)progressBlock {
  574. [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];
  575. }
  576. - (void)invalidateTasksWithErrorInfo:(QNResponseInfo *)info resp:(NSDictionary *)resp {
  577. if (self.taskQueue.isConcurrentTaskError) return;
  578. [self.taskQueue buildErrorWithInfo:info resp:resp];
  579. [self.sessionManager invalidateSessionWithIdentifier:self.identifier];
  580. }
  581. @end