// // RAUploadManager.m // test_autolayout // // Created by Ray on 02/05/2017. // Copyright © 2017 USAI. All rights reserved. // #import #import "RAUploadManager.h" #import "NetworkUtils.h" #import "RAUtils.h" NSString *const kUploadSetting = @"UploadSettingKey"; NSString *const UPLOAD_FINISH_NOTIFICATION = @"upload_finish_notification"; NSString *const UPLOAD_COUNT_CHANGE_NOTIFICATION = @"upload_count_change_notification"; NSString *const UPLOAD_REFRESH_UI_NOTIFICATION = @"upload_refresh_ui_notification"; static RAUploadManager *sharedInstance; @interface RAUploadManager () @property (nonatomic,strong) NSTimer *retryTimer; @property (nonatomic,strong) NSMutableArray *retryArr; @property (nonatomic,assign) BOOL isNotReachable; @end @interface RAUPloadManagerConfigure () @end @implementation RAUPloadManagerConfigure - (instancetype)init { if (self = [super init]) { NSDictionary *param = [[NSUserDefaults standardUserDefaults] objectForKey:kUploadSetting]; if (param) { BOOL autoUpload = [[param objectForKey:@"auto_upload"] boolValue]; BOOL autoRmFinish = [[param objectForKey:@"auto_rm_finish"] boolValue]; BOOL autoRmErr = [[param objectForKey:@"auto_rm_error"] boolValue]; BOOL onlyWiFi = [[param objectForKey:@"only_wifi"] boolValue]; BOOL monitorNetwork = [[param objectForKey:@"monitor_network"] boolValue]; id compressImage_id = [param objectForKey:@"compress_img"]; int retryCount = [[param objectForKey:@"retry_count"] intValue]; NSNumber *timeInterval = [param objectForKey:@"retry_time_interval"]; BOOL compressImage = YES; if (compressImage_id) { compressImage = [compressImage_id boolValue]; } if (timeInterval != nil) { self.retryTimeIntetval = [timeInterval intValue]; } else { self.retryTimeIntetval = 300; } self.autoUpload = autoUpload; self.autoRemoveFinish = autoRmFinish; self.autoRemoveError = autoRmErr; self.retryCount = retryCount; self.onlyWIFI = onlyWiFi; self.monitorNetwork = monitorNetwork; self.compressImage = compressImage; } else { self.autoUpload = YES; self.autoRemoveFinish = NO; self.autoRemoveError = NO; self.retryCount = 5; self.onlyWIFI = NO; self.retryTimeIntetval = 300; self.monitorNetwork = YES; self.compressImage = YES; } } return self; } - (void)save { NSDictionary *param = @{ @"auto_upload" : @(self.autoUpload), @"auto_rm_finish" : @(self.autoRemoveFinish), @"auto_rm_error" : @(self.autoRemoveError), @"retry_count" : @(self.retryCount), @"only_wifi" : @(self.onlyWIFI), @"retry_time_interval" : @(self.retryTimeIntetval), @"monitor_network" : @(self.monitorNetwork), @"compress_img" : @(self.compressImage) }; [[NSUserDefaults standardUserDefaults] setObject:param forKey:kUploadSetting]; [[NSUserDefaults standardUserDefaults] synchronize]; if (sharedInstance) { sharedInstance.autoStart = self.autoUpload; sharedInstance.removeFinish = self.autoRemoveFinish; // sharedInstance.removeError = self.autoRemoveError; sharedInstance.maxRetry = self.retryCount; sharedInstance.onlyWiFi = self.onlyWIFI; sharedInstance.monitorNetwork = self.monitorNetwork; sharedInstance.retryTimeInterval = self.retryTimeIntetval; } } @end @implementation RAUploadManager + (void)configureUploadManager:(void(^)(RAUPloadManagerConfigure *configure))configure { if (configure) { RAUPloadManagerConfigure *cfg = [[RAUPloadManagerConfigure alloc] init]; configure(cfg); [cfg save]; } } + (instancetype)sharedManager { static dispatch_once_t token; dispatch_once(&token, ^{ sharedInstance = [[RAUploadManager alloc] init]; }); return sharedInstance; } /*! * Called by Reachability whenever status changes. */ - (void) reachabilityChanged:(NSNotification *)note { [self handleReachability:self.reach]; } - (void)handleReachability:(Reachability *)reachability { NetworkStatus netStatus = [reachability currentReachabilityStatus]; if (self.monitorNetwork) { if (netStatus == NotReachable) { // 网络断开 self.isNotReachable = YES; [self.retryArr removeAllObjects]; [self stopAllTasks]; } else if ((self.onlyWiFi && netStatus != ReachableViaWiFi)) { // 网络连接正常,但是onlyWifi self.isNotReachable = NO; [self.retryArr removeAllObjects]; [self stopAllTasks]; } else { // 网络正常连接 if (self.isNotReachable) { // 重启任务 [self restartAllTask]; } self.isNotReachable = NO; } } else { if ((self.onlyWiFi && netStatus != ReachableViaWiFi)) { DebugLog(@"not wifi & stop task"); [self.retryArr removeAllObjects]; [self stopAllTasks]; } } } - (instancetype)init { if (self = [super init]) { /* Observe the kNetworkReachabilityChangedNotification. When that notification is posted, the method reachabilityChanged will be called. */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil]; NSString *remoteHostName = @"www.apple.com"; self.reach = [Reachability reachabilityWithHostName:remoteHostName]; [self.reach startNotifier]; [self handleReachability:self.reach]; self.queue_status = QueueStatusDefault; NSDictionary *param = [[NSUserDefaults standardUserDefaults] objectForKey:kUploadSetting]; if (param) { BOOL autoUpload = [[param objectForKey:@"auto_upload"] boolValue]; BOOL autoRmFinish = [[param objectForKey:@"auto_rm_finish"] boolValue]; // BOOL autoRmErr = [[param objectForKey:@"auto_rm_error"] boolValue]; BOOL onlyWiFi = [[param objectForKey:@"only_wifi"] boolValue]; int retryCount = [[param objectForKey:@"retry_count"] intValue]; NSNumber *timeInterval = [param objectForKey:@"retry_time_interval"]; BOOL monitorNetwork = [[param objectForKey:@"monitor_network"] boolValue]; if (timeInterval != nil) { self.retryTimeInterval = [timeInterval intValue]; } else { self.retryTimeInterval = 300; } self.autoStart = autoUpload; self.removeFinish = autoRmFinish; // self.removeError = autoRmErr; self.maxRetry = retryCount; self.onlyWiFi = onlyWiFi; self.monitorNetwork = monitorNetwork; } else { self.autoStart = YES; self.removeFinish = NO; // self.removeError = NO; self.maxRetry = 5; self.onlyWiFi = NO; self.retryTimeInterval = 300; self.monitorNetwork = YES; } self.newtaskStatus = TaskStatusWait; self.maxThread = 3; // self.autoStart = true; // self.removeError=true; // self.removeFinish = true; self.operation_queue = [NSOperationQueue new]; self.operation_queue.maxConcurrentOperationCount = self.maxThread; [self.operation_queue addObserver:self forKeyPath:@"operationCount" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:@"queueFinished changed"]; //load task 要在监听arr_queue之前 [self loadTasks]; [self addObserver:self forKeyPath:@"arr_queue" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:@"arr_queue changed"]; // self.arr_queue_lock = [NSLock new]; // [self.arr_queue_lock lock]; // [self.arr_queue_lock lock]; // [self.arr_queue_lock unlock]; // [self.arr_queue_lock unlock]; // self.backgroundColor = [UIColor clearColor]; } return self; } -(void) dealloc { [self destructRetryTimer]; [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil]; [self.operation_queue removeObserver:self forKeyPath:@"operationCount"]; [self.operation_queue removeObserver:self forKeyPath:@"arr_queue"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"ovserver!!! %@",keyPath); // [self saveTasks]; if([keyPath isEqualToString:@"operationCount"]) { // [self checkRetryOperations]; if(self.operation_queue.operationCount == 0 && self.retryArr.count == 0) { for(NSMutableDictionary* task in self.arr_queue) { if([task[@"status"] intValue] == TaskStatusError) { self.queue_status = QueueStatusFinishWithError; return; } } self.queue_status = QueueStatusFinish; } } if([keyPath isEqualToString:@"arr_queue"]) { NSArray* newqueue=[change objectForKey:NSKeyValueChangeNewKey]; NSArray* oldqueue=[change objectForKey:NSKeyValueChangeOldKey]; if([newqueue isKindOfClass:[NSNull class]]) newqueue=nil; if([oldqueue isKindOfClass:[NSNull class]]) oldqueue=nil; if(newqueue.count>oldqueue.count) { self.queue_status = QueueStatusAdd; return; } [[NSNotificationCenter defaultCenter] postNotificationName:UPLOAD_COUNT_CHANGE_NOTIFICATION object:nil]; } } //-(void) upload:(NSString*) url //{ // while(self.activeThreadAtIndex // [ insertObjects:tasks atIndexes:indexSet]; } // if (self.delegate && [self.delegate respondsToSelector:@selector(uploadManager:didRemoveTasks:)]) { // [self.delegate uploadManager:self didRemoveTasks:tasks]; // } } -(void) removeTask:(NSMutableDictionary*) task { // [self.arr_queue addObject:task]; [self stopTask:task]; // 上传成功才删除文件 // NSFileManager* fmanager = [NSFileManager new]; // NSError* error = nil; // bool bdel=[fmanager removeItemAtPath:[self filePath:task] error:&error]; // if(!bdel) // DebugLog(@"file delete failed path:%@",[self filePath:task]); // [self.arr_queue_lock lock]; // DebugLog(@"lock"); // [[self mutableArrayValueForKey:@"arr_queue"] removeObject:task]; // DebugLog(@"unlock"); // [self.arr_queue_lock unlock]; // [self.arr_queue_lock lock]; // [[self mutableArrayValueForKey:@"arr_queue"] removeObject:task]; // [self.arr_queue_lock unlock]; @synchronized(self) { [[self mutableArrayValueForKey:@"arr_queue"] removeObject:task]; // [self saveTasks]; } // if (self.delegate && [self.delegate respondsToSelector:@selector(uploadManager:didRemoveTasks:)]) { // [self.delegate uploadManager:self didRemoveTasks:@[task]]; // } } //-(NSString*)filePath:(NSMutableDictionary*)task //{ // // NSString *path = [RAUtils appCacheDirectory]; // path= [path stringByAppendingPathComponent:task[@"path"]]; // path= [path stringByAppendingPathComponent:task[@"file"]]; // return path; //} -(void) saveTasks { @synchronized(self) { NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults]; // NSMutableArray* save_arr = [NSMutableArray new]; // for(NSMutableDictionary* task in self.arr_queue) // { // NSMutableDictionary* savetask = [task mutableCopy]; // savetask[@"operation"] = nil; // if([savetask[@"status"] intValue]==TaskStatusWait) // savetask[@"status"]=[NSNumber numberWithInteger:TaskStatusStop]; // [save_arr addObject:savetask]; // } [defaults setObject:self.arr_queue forKey:@"upload_queue"]; [defaults synchronize]; } } -(void) loadTasks { [self stopAllTasks]; self.arr_queue = [NSMutableArray new]; NSMutableArray* arr = [[[NSUserDefaults standardUserDefaults] objectForKey:@"upload_queue"] mutableCopy]; // if(arr==nil) for(NSDictionary* task in arr) { NSMutableDictionary* newtask =[task mutableCopy]; [self.arr_queue addObject:newtask]; if([newtask[@"status"] intValue]==TaskStatusWait) newtask[@"status"] =[NSNumber numberWithInteger:TaskStatusStop]; if(self.autoStart&&[task[@"status"] intValue]!=TaskStatusError&&[task[@"status"] intValue]!=TaskStatusFinish) [self startTask:newtask]; } // [self.operation_queue start]; // NSDictionary* ddd = arr[0] ; // NSMutableDictionary* eee = [ddd mutableCopy]; // // eee=[NSMutableDictionary dictionaryWithDictionary:ddd]; // if(arr!=nil) // self.arr_queue=arr; // else // self.arr_queue= [[NSMutableArray alloc] init]; // // for(__strong NSMutableDictionary* task in arr) // { // // task=[NSMutableDictionary dictionaryWithDictionary:task]; //// if([task[@"status"] intValue]==TaskStatusStart) //// task[@"status"]=[NSNumber numberWithInteger:TaskStatusWait]; // // NSMutableDictionary *new_task = [task mutableCopy]; // if(self.autoStart&&[task[@"status"] intValue]!=TaskStatusError&&[task[@"status"] intValue]!=TaskStatusFinish) // [self startTask:task]; // } } -(void) stopAllTasks { for(NSMutableDictionary* task in self.arr_queue) { [self stopTask:task]; } } -(void) stopTask:(NSMutableDictionary*) task { if([task[@"status"] intValue]!=TaskStatusStart&&[task[@"status"] intValue]!=TaskStatusWait) { task[@"operation"]=nil; return; // task 已停止,返回 } DebugLog(@"stop task"); RAUploadOperation* operation = task[@"operation"]; [operation cancel]; task[@"operation"]=nil; task[@"status"] = [NSNumber numberWithInt:TaskStatusStop] ; task[@"progress"] = [NSNumber numberWithDouble:0.0]; task[@"msg"]= @""; // [self saveTasks]; if (operation.updateUI) { // 网络状态改变时,首先是正在进行的网络活动中断,然后才通知网络改变 operation.updateUI(); } } -(void) startAllTasks { for(NSMutableDictionary* task in self.arr_queue) [self startTask:task]; } -(void) startTask:(NSMutableDictionary*) task { if (self.onlyWiFi && self.reach.currentReachabilityStatus != ReachableViaWiFi) { return; } /*__block*/ NSMutableDictionary* block_task = task; __weak typeof(self) weakSelf = self; if([task[@"status"] intValue]==TaskStatusStart||[task[@"status"] intValue]==TaskStatusWait) return; // task 已在执行队列,返回 RAUploadOperation* operation = [[RAUploadOperation alloc] initWithTaskinfo:task retry:self.maxRetry]; [operation setCompletionBlock:^{ // [self saveTasks]; NSLog(@"%@ %@",block_task[@"name"],block_task[@"status"]); switch ([block_task[@"status"] intValue]) { case TaskStatusFinish: { if(weakSelf.removeFinish) [self removeTask:block_task]; [[NSNotificationCenter defaultCenter] postNotificationName:UPLOAD_FINISH_NOTIFICATION object:nil userInfo:block_task]; } break; case TaskStatusError: { // if(weakSelf.removeError) // [self removeTask:block_task]; } break; default: break; } }]; task[@"msg"] = nil; task[@"status"] = [NSNumber numberWithInt:self.newtaskStatus] ; // [self saveTasks]; task[@"operation"] = operation; [self.operation_queue addOperation:operation]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:UPLOAD_REFRESH_UI_NOTIFICATION object:nil]; }); } - (void)restartAllTask { for(NSMutableDictionary* task in self.arr_queue) { TaskStatus status = [task[@"status"] intValue]; if (status == TaskStatusWait || status == TaskStatusStop) { [self startTask:task]; } } } //- (void) uploadTask:(NSMutableDictionary*) task url:(NSString*)url{ // //// __weak typeof(self) weakself = self; // // // // //} - (BOOL)canUpload { if (self.onlyWiFi && self.reach.currentReachabilityStatus != ReachableViaWiFi) { return NO; } return YES; } - (BOOL)reachable { return self.reach.currentReachabilityStatus != NotReachable; } #pragma mark - Retry - (NSMutableArray *)retryArr { if (!_retryArr) { _retryArr = [NSMutableArray array]; } return _retryArr; } - (void)addRetryOperation:(RAUploadOperation *)operation { if (self.retryTimer == nil) { // self.retryTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(checkRetryOperations) userInfo:nil repeats:YES]; self.retryTimer = [NSTimer timerWithTimeInterval:60 target:self selector:@selector(checkRetryOperations) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:self.retryTimer forMode:NSRunLoopCommonModes]; } [self.retryArr addObject:operation]; // 升序 [self.retryArr sortUsingComparator:^NSComparisonResult(RAUploadOperation* _Nonnull operation1, RAUploadOperation* _Nonnull opertation2) { if (operation1.theRestOfWaitTimeInterval < opertation2.theRestOfWaitTimeInterval) { return NSOrderedAscending; } else { return NSOrderedDescending; } }]; } - (void)checkRetryOperations { // 检测是否有网络连接 if (![self reachable]) { // 当前task状态为Waiting,是否考虑 monitoreNetwork == YES 的条件下修改为Stop? return; } // 检查是否有需要重试的任务 if (self.retryArr.count == 0) { return; } // 检查Wi-Fi Only if (![self canUpload]) { return; } NSMutableArray *nextStartOperations = [NSMutableArray array]; // // 检查当前上传队列等待数量是否小于最大并发数 // if (self.operation_queue.operationCount < self.maxThread) { // //// // 根据最大并发数将剩余Operation全部添加进去 //// NSInteger minCount = MIN(self.retryArr.count, self.maxThread - self.operation_queue.operationCount); // for (int i = 0; i < self.retryArr.count; i++) { // RAUploadOperation *operation = [self.retryArr objectAtIndex:i]; // operation.queuePriority = NSOperationQueuePriorityVeryHigh; // [self.operation_queue addOperation:operation]; // [nextStartOperations addObject:operation]; // } // // } else { // 调度等待时间到达的Operation __weak typeof(self) weakSelf = self; [self.retryArr enumerateObjectsUsingBlock:^(RAUploadOperation * _Nonnull operation, NSUInteger idx, BOOL * _Nonnull stop) { if (operation.theRestOfWaitTimeInterval <= 0) { operation.queuePriority = NSOperationQueuePriorityVeryHigh; [weakSelf.operation_queue addOperation:operation]; [nextStartOperations addObject:operation]; } }]; // } [self.retryArr removeObjectsInArray:nextStartOperations]; // if (self.retryArr.count == 0) { // [self destructRetryTimer]; // } } - (void)destructRetryTimer { if (self.retryTimer != nil) { [self.retryTimer setFireDate:[NSDate distantFuture]]; [self.retryTimer invalidate]; self.retryTimer = nil; } } @end