// // KMCloudOperation.m // PDF Reader // // Created by wanjun on 2020/7/14. // Copyright © 2020 Kdan Mobile. All rights reserved. // #import "KMCloudOperation.h" #import #import "GTMSessionFetcher.h" #import "KMGoogleDriveManager.h" @interface KMCloudOperation () @property (assign, nonatomic, getter = isExecuting) BOOL executing; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (assign, nonatomic, getter = isCancelled) BOOL cancelled; @property (nonatomic, strong) KMGoogleDriveManager *cloudModel; @property (nonatomic, assign) BOOL isDownload; @property (nonatomic, strong) NSURL *localPath; /* Google Drive */ @property (nonatomic, strong) GTLRServiceTicket *uploadFileTicket; @property (nonatomic, strong) GTLRServiceTicket *downloadFileTicket; /* DropBox */ @property (nonatomic, retain) DBUploadTask *uploadTask; @property (nonatomic, retain) DBDownloadUrlTask *downloadTask; @property (nonatomic, strong) KMServicesCloudFile *fileData; @property (nonatomic, copy) CurrentProgressCallBack progressCallBack; @property (nonatomic, copy) CompletionCallBack callback; @property (nonatomic,assign) long long downloadSize; @property (nonatomic,assign) long long downloadTotalSize; @end @implementation KMCloudOperation @synthesize executing = _executing; @synthesize finished = _finished; @synthesize cancelled = _cancelled; - (void)dealloc {} #pragma mark - init - (instancetype)initWithLoadCloudPath:(KMServicesCloudFile *)cloudPath serverType:(KMServerType)serverType localPath:(NSURL *)localPath loadState:(KMCloudLoadState)state currentConvetProgress:(CurrentProgressCallBack)currentProgress completion:(CompletionCallBack)completion { self = [super init]; if (self) { // if (state) { // if (serverType == KMDropbox) { // self.fromPath = cloudPath.displayName; // self.toPath = [localPath path]; // } else if (serverType == KMGoogleDrive) { // self.fromPath = cloudPath.fileId; // self.toPath = [localPath path]; // } // } else { // if (serverType == KMDropbox) { // self.fromPath = [localPath path]; // self.toPath = cloudPath.displayName; // } else if (serverType == KMGoogleDrive) { // self.fromPath = [localPath path]; // self.toPath = cloudPath.fileId; // } // } self.fromPath = cloudPath; self.toPath = [localPath path]; self.cloudLoadState = state; self.filePath = cloudPath.displayName; _serverType = serverType; _localPath = localPath; _isDownload = state; self.queuePriority = NSOperationQueuePriorityNormal; if (serverType == KMGoogleDrive) { _cloudModel = [KMGoogleDriveManager shareInstance]; } self.fileData = cloudPath; self.name = self.filePath; self.executing = NO; self.finished = NO; self.progressCallBack = currentProgress; self.callback = completion; self.downloadSize = 0; } return self; } #pragma mark - setter - (void)setExecuting:(BOOL)executing { [self willChangeValueForKey:@"isExecuting"]; _executing = executing; [self didChangeValueForKey:@"isExecuting"]; } - (void)setFinished:(BOOL)finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"]; } - (void)setCancelled:(BOOL)cancelled { [self willChangeValueForKey:@"isCancelled"]; _cancelled = cancelled; [self didChangeValueForKey:@"isCancelled"]; } #pragma mark - overwrite - (void)start { if ([self p_checkCancelled]) { return; } if (self.cloudLoadState == KMCloudLoadState_Download) { dispatch_async(dispatch_get_main_queue(), ^{ self.state = KMCloudDownLoadOperationStateStart; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; }); } self.executing = YES; [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; } - (void)main { @try { if ([self p_checkCancelled]) { return; } if (_isDownload) { [self cloudDownloadDataWithCloudPath:self.fromPath localPath:_localPath]; } else { [self cloudUploadDataWithCloudPath:self.toPath localPath:_localPath]; } while (self.executing) { if ([self p_checkCancelled]) { return; } } } @catch (NSException * e) { NSLog(@"Exception %@", e); } } - (void)cancel { [super cancel]; if (_isDownload) { //取消下载 if (_serverType == KMDropbox) { [self.downloadTask cancel]; } else if (_serverType == KMGoogleDrive) { [_downloadFileTicket cancelTicket]; } } else { //取消上传 if (_serverType == KMDropbox) { [self.uploadTask cancel]; } else if (_serverType == KMGoogleDrive) { [_uploadFileTicket cancelTicket]; } } if (self.executing) { self.executing = NO; self.finished = YES; } else { self.finished = NO; } self.cancelled = YES; if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateCancel; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; } } #pragma mark - private methods - (void)p_done { self.executing = NO; self.finished = YES; } - (BOOL)p_checkCancelled { if (self.cancelled) { self.finished = YES; return YES; } return NO; } #pragma mark - updata //暂时处理成根据上传到指定文件夹FileID的位置,需要添加替换功能,当文件重名时 - (void)cloudUploadDataWithCloudPath:(NSString *)cloudPath localPath:(NSURL *)localPath { if (_serverType == KMDropbox) { [self dropboxUploadDataWithCloudPath:_fromPath.displayName localPath:localPath]; } else if (_serverType == KMGoogleDrive) { [self googleDriveUploadDataWithCloudPath:_fromPath.fileId localPath:localPath]; } } //DropBox - (void)dropboxUploadDataWithCloudPath:(NSString *)cloudPath localPath:(NSURL *)localPath { NSData *fileData = [[NSData alloc] initWithContentsOfURL:localPath]; NSString * path = [NSString stringWithFormat:@"%@/%@",cloudPath,[[localPath path] lastPathComponent]]; DBFILESWriteMode *mode = [[DBFILESWriteMode alloc] initWithOverwrite]; DBUserClient *client = [DBClientsManager authorizedClient]; // self.uploadTask = [client.filesRoutes uploadData:cloudPath mode:mode autorename:@(YES) clientModified:nil mute:@(NO) inputData:fileData]; // self.uploadTask = [client.filesRoutes uploadData:path mode:mode autorename:@(YES) clientModified:nil mute:@(NO) propertyGroups:nil strictConflict:nil inputData:fileData]; self.uploadTask = [client.filesRoutes uploadData:cloudPath mode:mode autorename:@(YES) clientModified:nil mute:@(NO) propertyGroups:nil strictConflict:nil contentHash:nil inputData:fileData]; [[self.uploadTask setResponseBlock:^(DBFILESFileMetadata *result, DBFILESUploadError *routeError, DBRequestError *networkError) { if (result) { KMServicesCloudFile *fileModel = [[KMServicesCloudFile alloc] init]; fileModel.client_modified = result.clientModified; fileModel.fileModiDate = result.serverModified; fileModel.fileId = result.id_; fileModel.fileName = result.name; fileModel.displayName = result.pathDisplay; fileModel.path_lower = result.pathLower; fileModel.rev = result.rev; fileModel.fileSize = result.size.integerValue; fileModel.content_hash = result.contentHash; fileModel.filetype = KMCloudServiceFileType_File; if (_callback) { _callback(self.fileData,YES); } NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:fileModel,@"CloudFile",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerUploadSuccessfulNotification object:self userInfo:dict]; [self p_done]; } else { [self cancel]; KMDropboxErrorMetadata *errorModel = [[KMDropboxErrorMetadata alloc] init]; errorModel.routeError = [NSNumber numberWithInteger:routeError.tag]; errorModel.networkError = [NSNumber numberWithInteger:networkError.statusCode.integerValue]; if (self.callback) { self.callback(self.fileData,NO); } NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:errorModel,@"UploadError",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerUploadFailureNotification object:self userInfo:dict]; } }] setProgressBlock:^(int64_t bytesUploaded, int64_t totalBytesUploaded, int64_t totalBytesExpectedToUploaded) { CGFloat loadProgress = totalBytesUploaded/(CGFloat)(totalBytesExpectedToUploaded); if (_progressCallBack) { _progressCallBack(self.fileData,loadProgress); } }]; } //Goolge Drive - (void)googleDriveUploadDataWithCloudPath:(NSString *)cloudPath localPath:(NSURL *)localPath { NSError *fileError; if (![localPath checkPromisedItemIsReachableAndReturnError:&fileError]) { return; } GTLRDriveService *service = _cloudModel.driveService; NSString *filename = [localPath lastPathComponent]; NSString *mimeType = [self MIMETypeFileName:filename defaultMIMEType:@"binary/octet-stream"]; NSData *fileData = [NSData dataWithContentsOfURL:localPath]; GTLRUploadParameters *uploadParameters = [GTLRUploadParameters uploadParametersWithData:fileData MIMEType:mimeType]; GTLRDrive_File *metadata = [GTLRDrive_File object]; metadata.name = localPath.lastPathComponent; // if ([cloudPath lengthOfBytesUsingEncoding:NSASCIIStringEncoding] > 0) { // metadata.parents = @[cloudPath]; // } if (self.fromPath.parensID) { metadata.parents = @[self.fromPath.parensID]; } GTLRDriveQuery_FilesCreate *query = [GTLRDriveQuery_FilesCreate queryWithObject:metadata uploadParameters:uploadParameters]; query.fields = @"id"; query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *callbackTicket, unsigned long long numberOfBytesRead, unsigned long long dataLength) { CGFloat loadProgress = numberOfBytesRead/(CGFloat)(dataLength); if (_progressCallBack) { _progressCallBack(self.fileData,loadProgress); } }; self.uploadFileTicket = [service executeQuery:query completionHandler:^(GTLRServiceTicket *ticket, GTLRDrive_File *uploadedFile, NSError *callbackError) { self.uploadFileTicket = nil; if (callbackError == nil) { // Succeeded KMServicesCloudFile *fileModel = [[KMServicesCloudFile alloc] init]; fileModel.mimeType = uploadedFile.mimeType; fileModel.fileId = uploadedFile.identifier; fileModel.kind = uploadedFile.kind; fileModel.fileName = uploadedFile.name; fileModel.ownedByMe = uploadedFile.ownedByMe; fileModel.fileSize = uploadedFile.size.integerValue; fileModel.fileModiDate = uploadedFile.modifiedByMeTime.date; fileModel.lastUserName = uploadedFile.lastModifyingUser.displayName; fileModel.createdTime = uploadedFile.createdTime.date; fileModel.parensID = uploadedFile.parents[0]; if (_callback) { _callback(self.fileData,YES); } NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:self.fileData,@"CloudFile",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerUploadSuccessfulNotification object:self userInfo:dict]; [self p_done]; } else { NSLog(@"Upload Failed callbackError==%@",callbackError); [self cancel]; if (self.callback) { self.callback(self.fileData,NO); } [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:callbackError,@"UploadError",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerUploadFailureNotification object:self userInfo:dict]; } }]; } - (NSString *)MIMETypeFileName:(NSString *)path defaultMIMEType:(NSString *)defaultType { NSString *result = defaultType; NSString *extension = [path pathExtension]; CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); if (uti) { CFStringRef cfMIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType); if (cfMIMEType) { result = CFBridgingRelease(cfMIMEType); } CFRelease(uti); } return result; } #pragma mark - downdata - (void)cloudDownloadDataWithCloudPath:(KMServicesCloudFile *)cloudPath localPath:(NSURL *)localPath { if (_serverType == KMDropbox) { [self dropboxDownloadDataWithCloudPath:cloudPath localPath:localPath]; } else if (_serverType == KMGoogleDrive) { [self googleDriveDownloadDataWithCloudPath:cloudPath localPath:localPath]; } } //DropBox - (void)dropboxDownloadDataWithCloudPath:(KMServicesCloudFile *)cloudPath localPath:(NSURL *)localPath { NSString *formString = cloudPath.displayName; NSString *fileType = [formString pathExtension]; //判断文件是文件还是文件夹 if ([fileType isEqualToString:@""]) { [self dropboxMultipleFilesDownloadWithPath:formString toPath:localPath]; } else { [self dropboxSingleFileDownloadWithPath:formString toPath:localPath]; } } //单个文件下载 - (void)dropboxSingleFileDownloadWithPath:(NSString *)fromPath toPath:(NSURL *)toPath { DBUserClient *client = [DBClientsManager authorizedClient]; self.downloadTask = [client.filesRoutes downloadUrl:fromPath overwrite:NO destination:toPath]; [[self.downloadTask setResponseBlock:^(DBFILESFileMetadata * _Nullable result, DBFILESDownloadError * _Nullable routeError, DBRequestError * _Nullable networkError, NSURL * _Nonnull destination) { if (result) { KMServicesCloudFile *fileModel = [[KMServicesCloudFile alloc] init]; fileModel.client_modified = result.clientModified; fileModel.fileModiDate = result.serverModified; fileModel.fileId = result.id_; fileModel.fileName = result.name; fileModel.displayName = result.pathDisplay; fileModel.path_lower = result.pathLower; fileModel.rev = result.rev; fileModel.fileSize = result.size.integerValue; fileModel.content_hash = result.contentHash; fileModel.filetype = KMCloudServiceFileType_File; if (_callback) { _callback(self.fileData,YES); } if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateSuccess; NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:fileModel,@"CloudFile",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadSuccessfulNotification object:self userInfo:dict]; } [self p_done]; } else { [self cancel]; KMDropboxErrorMetadata *errorModel = [[KMDropboxErrorMetadata alloc] init]; errorModel.routeError = [NSNumber numberWithInteger:routeError.tag]; if (self.callback) { self.callback(self.fileData,NO); } if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateFail; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:errorModel,@"DownloadError",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadFailureNotification object:self userInfo:dict]; } } }] setProgressBlock:^(int64_t bytesDownloaded, int64_t totalBytesDownloaded, int64_t totalBytesExpectedToDownload) { if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateProgress; self.downloadSize = (CGFloat)totalBytesDownloaded; self.downloadTotalSize = (CGFloat)totalBytesExpectedToDownload; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; } CGFloat loadProgress = totalBytesDownloaded/(CGFloat)(totalBytesExpectedToDownload); if (_progressCallBack) { _progressCallBack(self.fileData,loadProgress); } }]; } //多个文件下载为 Zip - (void)dropboxMultipleFilesDownloadWithPath:(NSString *)fromPath toPath:(NSURL *)toPath { DBUserClient *client = [DBClientsManager authorizedClient]; // self.downloadTask = [client.filesRoutes downloadZipUrl:fromPath overwrite:NO destination:toPath]; [[self.downloadTask setResponseBlock:^(DBFILESFileMetadata * _Nullable result, DBFILESDownloadError * _Nullable routeError, DBRequestError * _Nullable networkError, NSURL * _Nonnull destination) { if (result) { KMServicesCloudFile *fileModel = [[KMServicesCloudFile alloc] init]; fileModel.client_modified = result.clientModified; fileModel.fileModiDate = result.serverModified; fileModel.fileId = result.id_; fileModel.fileName = result.name; fileModel.displayName = result.pathDisplay; fileModel.path_lower = result.pathLower; fileModel.rev = result.rev; fileModel.fileSize = result.size.integerValue; fileModel.content_hash = result.contentHash; fileModel.filetype = KMCloudServiceFileType_File; if (_callback) { _callback(self.fileData,YES); } if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateSuccess; NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:fileModel,@"CloudFile",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadSuccessfulNotification object:self userInfo:dict]; } [self p_done]; } else { [self cancel]; KMDropboxErrorMetadata *errorModel = [[KMDropboxErrorMetadata alloc] init]; errorModel.routeError = [NSNumber numberWithInteger:routeError.tag]; if (self.callback) { self.callback(self.fileData,NO); } if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateFail; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:errorModel,@"UploadError",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadFailureNotification object:self userInfo:dict]; } } }] setProgressBlock:^(int64_t bytesDownloaded, int64_t totalBytesDownloaded, int64_t totalBytesExpectedToDownload) { if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateProgress; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; } CGFloat loadProgress = totalBytesDownloaded/(CGFloat)(totalBytesExpectedToDownload); if (_progressCallBack) { _progressCallBack(self.fileData,loadProgress); } }]; } //Google Drive - (void)googleDriveDownloadDataWithCloudPath:(KMServicesCloudFile *)cloudPath localPath:(NSURL *)localPath { NSString *fileType = @""; if ([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.presentation"]) { fileType = @"application/vnd.openxmlformats-officedocument.presentationml.presentation"; } else if ([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.document"]) { fileType = @"application/vnd.openxmlformats-officedocument.wordprocessingml.document"; } else if ([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.spreadsheet"]) { fileType = @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; } else if ([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.form"]) { fileType = @"text/csv"; } else if ([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.map"]) { fileType = @"application/vnd.google-apps.script+json"; } else if ([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.drawing"]) { fileType = @"image/jpeg"; } else if ([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.site"]) { fileType = @"text/html"; } else if([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.script"]) { fileType = @"application/vnd.google-apps.script+json"; } else if([cloudPath.mimeType isEqualToString:@"application/vnd.google-apps.photo"]) { fileType = @"image/jpeg"; } NSString *formString = cloudPath.fileId; GTLRDriveService *service = _cloudModel.driveService; GTLRQuery *query = [GTLRDriveQuery_FilesExport queryForMediaWithFileId:formString mimeType:fileType]; if ([fileType isEqualToString:@""]) { query = [GTLRDriveQuery_FilesGet queryForMediaWithFileId:formString]; } else { query = [GTLRDriveQuery_FilesExport queryForMediaWithFileId:formString mimeType:fileType]; } //下载进度 self.downloadFileTicket = [service executeQuery:query completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRDataObject *object, NSError *callbackError) { NSError *errorToReport = callbackError; NSError *writeError; self.downloadFileTicket = nil; if (callbackError == nil) { NSLog(@"object == %@ toPath==%@",object,[localPath path]); BOOL didSave = [object.data writeToURL:localPath options:NSDataWritingAtomic error:&writeError]; if (!didSave) { errorToReport = writeError; } } if (errorToReport == nil) { NSLog(@"Downloaded == %@", [localPath path]); KMServicesCloudFile *fileModel = [[KMServicesCloudFile alloc] init]; fileModel.fileId = formString; if (_callback) { _callback(self.fileData,YES); } if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateSuccess; NSDictionary *dict =[[NSDictionary alloc] initWithObjectsAndKeys:self.fileData,@"CloudFile",nil]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadSuccessfulNotification object:self userInfo:dict]; } [self p_done]; } else { NSLog(@"Error Downloading File == %@", errorToReport); [self cancel]; if (self.cloudLoadState == KMCloudLoadState_Download) { self.state = KMCloudDownLoadOperationStateFail; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadStateChangeNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:KMServerCloudFileManagerDownloadFailureNotification object:self userInfo:nil]; } if (_callback) { _callback(self.fileData,NO); } } }]; } @end