// // KMGoogleDriveManager.m // PDF Reader Pro Edition // // Created by 万军 on 2020/2/27. // Copyright © 2020 WanJun. All rights reserved. // #import "KMGoogleDriveManager.h" #import "KMCloudUploadOperationQueue.h" #import "KMCloudDownloadOperationQueue.h" #import "AppAuth.h" #import "GTLRUtilities.h" #import "GTMAppAuth.h" #if VERSION_PRO static NSString *const kClientID = @"943919378557-8d80bu59e7sgs426sum5ttbl3dvca792.apps.googleusercontent.com"; static NSString *const kClientSecret = @"yyuC2iDhjFpXDyHudBWyb4f3"; #else static NSString *const kClientID = @"701590830852-ndbtpjj13oahpba0l562j3bba8rei5vd.apps.googleusercontent.com"; static NSString *const kClientSecret = @"fgmhFsuan45HJ1oksDnyEVQ-"; #endif static NSString *const kSuccessURLString = @"http://openid.github.io/AppAuth-iOS/redirect/"; NSString *const kGTMAppAuthKeychainItemName = @"DriveSample: Google Drive. GTMAppAuth."; @interface KMGoogleDriveManager () //@property (nonatomic, assign) NSMutableArray *fileList; @property (nonatomic, strong) NSMutableArray *localFileList; @property (nonatomic, strong) NSMutableArray *sharedFileList; @property (nonatomic, strong) KMServicesCloudFile *localRootFolder;//获取本读根文件夹 @property (nonatomic, strong) GTLRServiceTicket *fileListTicket; @property (nonatomic, strong) GTLRServiceTicket *editFileListTicket; @property (nonatomic, strong) OIDRedirectHTTPHandler * redirectHTTPHandler; @property (nonatomic, strong) NSMutableDictionary *cloudFilesCache; @property (nonatomic, copy) CloudDeleteFileCallBack deleteFileBlock; @property (nonatomic, copy) CreateFolderCallBack createFolderBlock; @property (nonatomic, copy) GetFileListCallBack getFileListBlock; @property (nonatomic, copy) CloudLoginCallBack cloudLoginBlock; @property (nonatomic, copy) CloudLogoutCallBack cloudLogoutBlock; @end @implementation KMGoogleDriveManager @synthesize localFileList, sharedFileList; static KMGoogleDriveManager *_instance; + (instancetype)allocWithZone:(struct _NSZone *)zone { @synchronized(self) { if (nil == _instance) { _instance = [super allocWithZone:zone]; } return _instance; } } + (instancetype)shareInstance { @synchronized(self) { if (nil == _instance) { _instance = [[self alloc] init]; } return _instance; } } - (id)copyWithZone:(NSZone *)zone { return _instance; } - (id)mutableCopyWithZone:(NSZone *)zone { return _instance; } - (id)init { self = [super init]; if (self) { _cloudFilesCache = [[NSMutableDictionary alloc] init]; localFileList = [[NSMutableArray alloc] init]; sharedFileList = [[NSMutableArray alloc] init]; _localRootFolder = [[KMServicesCloudFile alloc] init]; id authorization = [GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthKeychainItemName]; self.driveService.authorizer = authorization; if ([self isSignedIn]) { [self fetchFileList:@""]; //获取根目录ID,并获取根目录下内容 } } return self; } #pragma mark - - (NSString *)signedInUsername { id auth = self.driveService.authorizer; BOOL isSignedIn = auth.canAuthorize; if (isSignedIn) { [[NSUserDefaults standardUserDefaults] setValue:auth.userEmail forKey:@"KMDriveLoginStatusKey"]; [[NSUserDefaults standardUserDefaults] synchronize]; return auth.userEmail; } else { return nil; } } - (BOOL)isSignedIn { NSString *name = [self signedInUsername]; return (name != nil); } - (KMServicesCloudFile *)cloudFolderCacheForDisPath:(NSString *)path { if (!path) { return self.localRootFolder; } else { return [self.cloudFilesCache objectForKey:path]; } } #pragma mark - googledrive 授权登陆 - (void)authorizedLoginCompletion:(CloudLoginCallBack)completion { self.cloudLoginBlock = completion; if (![self isSignedIn]) { [self runSigninThenHandler:^{ [self fetchFileList:@""]; //获取根目录ID,并获取根目录下内容 }]; } else { NSLog(@"已登陆"); } } - (void)runSigninThenHandler:(void (^)(void))handler { //执行授权 NSURL *successURL = [NSURL URLWithString:kSuccessURLString]; // Starts a loopback HTTP listener to receive the code, gets the redirect URI to be used. self.redirectHTTPHandler = [[OIDRedirectHTTPHandler alloc] initWithSuccessURL:successURL]; NSError *error; NSURL *localRedirectURI = [self.redirectHTTPHandler startHTTPListener:&error]; if (!localRedirectURI) { NSLog(@"Unexpected error starting redirect handler %@", error); return; } // Builds authentication request. OIDServiceConfiguration *configuration = [GTMAppAuthFetcherAuthorization configurationForGoogle]; // Applications that only need to access files created by this app should // use the kGTLRAuthScopeDriveFile scope. NSArray *scopes = @[ kGTLRAuthScopeDriveReadonly, OIDScopeEmail ];//https://www.googleapis.com/auth/drive.readonly kGTLRAuthScopeDrive OIDAuthorizationRequest *request = [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:kClientID clientSecret:kClientSecret scopes:scopes redirectURL:localRedirectURI responseType:OIDResponseTypeCode additionalParameters:nil]; // performs authentication request __weak __typeof(self) weakSelf = self; self.redirectHTTPHandler.currentAuthorizationFlow = [OIDAuthState authStateByPresentingAuthorizationRequest:request callback:^(OIDAuthState *_Nullable authState, NSError *_Nullable error) { __strong __typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) { return; } // Brings this app to the foreground. [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; if (authState) { GTMAppAuthFetcherAuthorization *gtmAuthorization = [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; strongSelf.driveService.authorizer = gtmAuthorization; [GTMAppAuthFetcherAuthorization saveAuthorization:gtmAuthorization toKeychainForName:kGTMAppAuthKeychainItemName]; KMGoogleDriveUserMetadata *userMetadata = [[KMGoogleDriveUserMetadata alloc] init]; userMetadata.accessToken = authState.lastTokenResponse.accessToken; userMetadata.accessTokenExpirationDate = authState.lastTokenResponse.accessTokenExpirationDate; userMetadata.tokenType = authState.lastTokenResponse.tokenType; userMetadata.idToken = authState.lastTokenResponse.idToken; userMetadata.refreshToken = authState.lastTokenResponse.refreshToken; userMetadata.scope = authState.lastTokenResponse.scope; userMetadata.userEmail = gtmAuthorization.userEmail; userMetadata.additionalParameters = authState.lastTokenResponse.additionalParameters; if (_cloudLoginBlock) { _cloudLoginBlock(userMetadata ,YES); } if (handler) { handler(); } } else { // self->_fileListFetchError = error; } }]; } #pragma mark - 取消授权 - (void)authorizedLogoutCompletion:(CloudLogoutCallBack)completion { self.cloudLogoutBlock = completion; GTLRDriveService *service = self.driveService; [GTMAppAuthFetcherAuthorization removeAuthorizationFromKeychainForName:kGTMAppAuthKeychainItemName]; service.authorizer = nil; if (_cloudLogoutBlock) { _cloudLogoutBlock(YES); } } #pragma mark - 上传 - (KMCloudOperation *)uploadCloudPath:(KMServicesCloudFile *)cloudPath localPath:(NSURL *)localPath currentConvetProgress:(CurrentProgressCallBack)currentProgress completion:(CompletionCallBack)completion { KMCloudUploadOperationQueue *queue = [KMCloudUploadOperationQueue sharedInstance]; queue.maxConcurrentOperationCount = 4; queue.name = [localPath path]; KMCloudOperation *op = [[KMCloudOperation alloc] initWithLoadCloudPath:cloudPath serverType:KMGoogleDrive localPath:localPath loadState:KMCloudLoadState_Upload currentConvetProgress:currentProgress completion:completion]; // if ([self isLoadOperationExisting:op]) { // return nil; // } else { // [queue addUploadOperation:op]; // [operationArr addObject:op]; // } if (![self isSignedIn]) { [self runSigninThenHandler:nil]; } else { if (![self isLoadOperationExisting:op]) { [queue addUploadOperation:op]; } } return op; } - (void)uploadCancel:(KMCloudOperation *)operation { if ([self isLoadOperationExisting:operation]) { [operation cancel]; KMCloudUploadOperationQueue *queue = [KMCloudUploadOperationQueue sharedInstance]; [queue cancel:operation.filePath]; [self removeUploadOperation:operation]; } else { return; } } #pragma mark - 下载 - (KMCloudOperation *)downloadCloudPath:(KMServicesCloudFile *)cloudPath localPath:(NSURL *)localPath currentConvetProgress:(CurrentProgressCallBack)currentProgress completion:(CompletionCallBack)completion { KMCloudDownloadOperationQueue *queue = [KMCloudDownloadOperationQueue sharedInstance]; queue.maxConcurrentOperationCount = 4; queue.name = cloudPath.displayName; KMCloudOperation *op = [[KMCloudOperation alloc] initWithLoadCloudPath:cloudPath serverType:KMGoogleDrive localPath:localPath loadState:KMCloudLoadState_Download currentConvetProgress:currentProgress completion:completion] ; if (![self isSignedIn]) { [self runSigninThenHandler:nil]; } else { if (![self isLoadOperationExisting:op]) { [queue addDownloadOperation:op]; } } return op; } - (void)downloadCancel:(KMCloudOperation *)operation { if ([self isLoadOperationExisting:operation]) { [operation cancel]; KMCloudDownloadOperationQueue *queue = [KMCloudDownloadOperationQueue sharedInstance]; [queue cancel:operation.filePath]; [self removeDownloadOperation:operation]; } else { return; } } #pragma mark - 删除 - (void)deleteFileWithPath:(KMServicesCloudFile *)fileData completion:(CloudDeleteFileCallBack)completion { self.deleteFileBlock = completion; if (![self isSignedIn]) { [self runSigninThenHandler:nil]; } else { GTLRDriveService *service = self.driveService; NSString *fileID = fileData.fileId; if (fileID) { GTLRDriveQuery_FilesDelete *query = [GTLRDriveQuery_FilesDelete queryWithFileId:fileID]; self.editFileListTicket = [service executeQuery:query completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRDrive_File *object, NSError *callbackError) { self.editFileListTicket = nil; if (callbackError == nil) { if (_deleteFileBlock) { _deleteFileBlock(fileData, YES); } [self getListWithFilePath:fileData setResponseBlock:^(NSArray *fileList,KMServerType serviceType,NSError *error) { }]; //创建成功后,刷新文件列表 } else { if (_deleteFileBlock) { _deleteFileBlock(fileData, NO); } } }]; } } } #pragma mark - 创建文件夹 - (void)createFolderWithPath:(KMServicesCloudFile *)fileData folderName:(NSString *)folderName completion:(CreateFolderCallBack)completion { self.createFolderBlock = completion; if (![self isSignedIn]) { [self runSigninThenHandler:nil]; } else { [self createFolder:fileData folderName:(NSString *)folderName]; } } - (void)createFolder:(KMServicesCloudFile *)fileData folderName:(NSString *)folderName { // NSString *parentsID = fileData.identifier; NSString *parentsID = [self getFolderPathWithFileData:fileData]; GTLRDriveService *service = self.driveService; GTLRDrive_File *folderObj = [GTLRDrive_File object]; folderObj.name = [NSString stringWithFormat:@"%@",folderName]; folderObj.mimeType = @"application/vnd.google-apps.folder"; folderObj.parents = @[parentsID]; GTLRDriveQuery_FilesCreate *query = [GTLRDriveQuery_FilesCreate queryWithObject:folderObj uploadParameters:nil]; self.editFileListTicket = [service executeQuery:query completionHandler:^(GTLRServiceTicket * _Nonnull callbackTicket, GTLRDrive_File *object, NSError * _Nullable callbackError) { self.editFileListTicket = nil; if (callbackError == nil) { KMServicesCloudFile *fileModel = [[KMServicesCloudFile alloc] init]; fileModel.mimeType = object.mimeType; fileModel.fileId = object.identifier; fileModel.kind = object.kind; fileModel.fileName = object.name; fileModel.filetype = KMCloudServiceFileType_Folder; if (_createFolderBlock) { _createFolderBlock(fileModel, YES); } [self getListWithFilePath:fileData setResponseBlock:^(NSArray *fileList,KMServerType serviceType,NSError *error) { }]; } else { NSLog(@"创建文件失败 callbackError=%@",callbackError); if (_createFolderBlock) { _createFolderBlock(fileData, NO); } } }]; } #pragma mark - 获取目录列表 - (void)getListWithFilePath:(KMServicesCloudFile *)fileData setResponseBlock:(GetFileListCallBack)responseBlock { //暂时这么修改,如果需要显示全部的共享文件,需要将根目录修改为数组(GoogleDrive需要) if ([fileData.fileId isEqualToString:_localRootFolder.fileId]) { fileData = nil; } self.getFileListBlock = responseBlock; NSString *parentsID = [self getFolderPathWithFileData:fileData]; if (fileData.displayName) { [self.cloudFilesCache setValue:fileData forKey:fileData.displayName]; } if (![self isSignedIn]) { [self runSigninThenHandler:^{ [self fetchFileList:parentsID]; }]; } else if ([parentsID isEqualToString:@""] || parentsID == nil) { [self fetchFileList:@""]; } else { [localFileList removeAllObjects]; GTLRDriveService *service = self.driveService; GTLRDriveQuery_FilesList *query = [GTLRDriveQuery_FilesList query]; query.q = [NSString stringWithFormat:@"'%@' in parents",parentsID]; query.pageSize = 1000; NSLog(@"query.q = %@",query.q); //ownedByMe:用户是否拥有该文件。未为共享驱动器中的项填充。 //size:文件内容大小 //webViewLink:在浏览器的相关Google编辑器或查看器中打开文件的链接。 //thumbnailLink:指向文件缩略图的短时间链接(如果可用)。通常持续几个小时。仅当请求应用程序可以访问文件内容时填充。 //starred: 用户是否为文件加了星。Uses NSNumber of boolValue. //modifiedByMeTime:用户上次修改文件的时间 //lastModifyingUser:最后一个修改文件的用户 //createdTime: 创建文件的时间 //descriptionProperty:文件的简短描述。 //fileExtension : 后缀名 query.fields = @"nextPageToken,kind,files(mimeType,kind,id,ownedByMe,size,name,parents,modifiedTime,lastModifyingUser,createdTime,webViewLink,owners)"; self.fileListTicket = [service executeQuery:query completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRDrive_FileList *fileList, NSError *callbackError) { for (GTLRDrive_File *string in fileList.files) { NSLog(@"parents == %@, name == %@,mimeType==%@,kind==%@,webViewLink=%@, file.identifier==%@",string.parents , string.name,string.mimeType,string.kind, string.webViewLink, string.identifier); } // Callback self.fileListTicket = nil; if (callbackError == nil) { [self.localFileList removeAllObjects]; for (GTLRDrive_File *file in fileList.files) { if ([file.parents[0] isEqualToString:parentsID]) { KMServicesCloudFile *tFileMeta = [[KMServicesCloudFile alloc] init]; tFileMeta.mimeType = file.mimeType; tFileMeta.fileId = file.identifier; tFileMeta.kind = file.kind; tFileMeta.fileName = file.name; tFileMeta.ownedByMe = file.ownedByMe; tFileMeta.fileSize = file.size.integerValue; tFileMeta.fileModiDate = file.modifiedTime.date; tFileMeta.lastUserName = file.lastModifyingUser.displayName; tFileMeta.authorName = file.owners[0].displayName; tFileMeta.createdTime = file.createdTime.date; tFileMeta.webViewLink = file.webViewLink; tFileMeta.parensID = file.parents[0]; if ([file.mimeType isEqualToString:@"application/vnd.google-apps.folder"]) { tFileMeta.filetype = KMCloudServiceFileType_Folder; } else { tFileMeta.filetype = KMCloudServiceFileType_File; } tFileMeta.displayName = [NSString stringWithFormat:@"%@/%@",fileData.displayName, file.name]; if (![file.name isEqualToString:@".DS_Store"] && ![file.mimeType isEqualToString:@"application/vnd.google-apps.shortcut"]) { [self.localFileList addObject:tFileMeta]; } } } if (_getFileListBlock) { _getFileListBlock(self.localFileList,KMGoogleDrive,nil); } } else { if (_getFileListBlock) { _getFileListBlock(nil,KMGoogleDrive,callbackError); } } }]; } } #pragma mark - - (GTLRDriveService *)driveService { static GTLRDriveService *service; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ service = [[GTLRDriveService alloc] init]; service.shouldFetchNextPages = YES; service.retryEnabled = YES; }); return service; } #pragma mark - private methods - (void)removeUploadOperation:(KMCloudOperation *)operation { //将传输任务从队列里删除 KMCloudUploadOperationQueue *queue = [KMCloudUploadOperationQueue sharedInstance]; if ([queue isUploadOperationExisting:operation.filePath]) { NSLog(@"removeUploadOperation operation.filePath == %@",operation.filePath); [queue cancel:operation.filePath]; } } - (void)removeDownloadOperation:(KMCloudOperation *)operation { //将传输任务从队列里删除 KMCloudDownloadOperationQueue *queue = [KMCloudDownloadOperationQueue sharedInstance]; if ([queue isDownloadOperationExisting:operation.filePath]) { NSLog(@"removeDownloadOperation operation.filePath == %@",operation.filePath); [queue cancel:operation.filePath]; } } - (BOOL)isLoadOperationExisting:(KMCloudOperation *)operation { KMCloudUploadOperationQueue *uploadQueue = [KMCloudUploadOperationQueue sharedInstance]; KMCloudDownloadOperationQueue *downloadQueue = [KMCloudDownloadOperationQueue sharedInstance]; NSMutableArray *operationArr = [NSMutableArray array]; for (NSArray *opArr in @[uploadQueue.operations, downloadQueue.operations]) { for (int j = 0; j < opArr.count; j++) { [operationArr addObject:opArr[j]]; } } for (KMCloudOperation *op in operationArr) { if ([op.filePath isEqualToString:operation.filePath]) { return YES; } } return NO; } - (BOOL)isBlankString:(NSString *)aStr { if (!aStr) { return YES; } if ([aStr isKindOfClass:[NSNull class]]) { return YES; } NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet]; NSString *trimmedStr = [aStr stringByTrimmingCharactersInSet:set]; if (!trimmedStr.length) { return YES; } return NO; } //获取文件对象所处的文件夹路径 - (NSString *)getFolderPathWithFileData:(KMServicesCloudFile *)fileData { if ([fileData.mimeType isEqualToString:@"application/vnd.google-apps.folder"]) { return fileData.fileId; } else { return fileData.parensID; } return nil; } - (void)fetchFileList:(NSString *)pathID { [localFileList removeAllObjects]; GTLRDriveService *service = self.driveService; GTLRDriveQuery_FilesList *query = [GTLRDriveQuery_FilesList query]; if (![pathID isEqualToString:@""]) { query.q = [NSString stringWithFormat:@"'%@' in parents",pathID]; } query.pageSize = 1000; query.fields = @"nextPageToken,kind,files(mimeType,kind,id,ownedByMe,size,name,parents,modifiedTime,lastModifyingUser,createdTime,webViewLink,owners)"; self.fileListTicket = [service executeQuery:query completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRDrive_FileList *fileList, NSError *callbackError) { // self->_fileListFetchError = callbackError; self.fileListTicket = nil; if (callbackError == nil) { [self.localFileList removeAllObjects]; if ([pathID isEqualToString:@""]) { NSMutableArray *locadParentsList = [[NSMutableArray alloc] init]; NSMutableArray *locadIdList = [[NSMutableArray alloc] init]; for (GTLRDrive_File *file in fileList.files) { // if ([file.mimeType isEqualToString:@"application/vnd.google-apps.spreadsheet"]) { if ([file.ownedByMe integerValue] == 0) { //googleDrive 共享文件 KMServicesCloudFile *fileMeta = [[KMServicesCloudFile alloc] init]; fileMeta.mimeType = file.mimeType; fileMeta.fileId = file.identifier; fileMeta.kind = file.kind; fileMeta.fileName = file.name; fileMeta.ownedByMe = file.ownedByMe; fileMeta.fileSize = file.size; fileMeta.fileModiDate = file.modifiedTime.date; fileMeta.displayName = file.lastModifyingUser.displayName; fileMeta.createdTime = file.createdTime.date; fileMeta.webViewLink = file.webViewLink; fileMeta.parensID = file.parents[0]; fileMeta.authorName = file.owners[0].displayName; if ([file.mimeType isEqualToString:@"application/vnd.google-apps.folder"]) { fileMeta.filetype = KMCloudServiceFileType_Folder; } else { fileMeta.filetype = KMCloudServiceFileType_File; } if (![file.name isEqualToString:@".DS_Store"] && ![file.mimeType isEqualToString:@"application/vnd.google-apps.shortcut"] && [self isBlankString:file.parents[0]]) { [self.localFileList addObject:fileMeta]; } } else { if (![self isBlankString:file.parents[0]]) { [locadParentsList addObject:file.parents[0]]; } [locadIdList addObject:file.identifier]; } } for (NSString *parent in locadParentsList) { if (![locadIdList containsObject:parent]) { self.localRootFolder.fileId = parent; self.localRootFolder.parensID = parent; } } } for (GTLRDrive_File *file in fileList.files) { // NSLog(@"file.parents[0] == %@, file.name == %@",file.parents[0], file.name); if ([file.parents[0] isEqualToString:self.localRootFolder.fileId]) { KMServicesCloudFile *fileMeta = [[KMServicesCloudFile alloc] init]; fileMeta.mimeType = file.mimeType; fileMeta.fileId = file.identifier; fileMeta.kind = file.kind; fileMeta.fileName = file.name; fileMeta.ownedByMe = file.ownedByMe; fileMeta.fileSize = file.size.integerValue; fileMeta.fileModiDate = file.modifiedTime.date; fileMeta.displayName = file.owners[0].displayName; fileMeta.createdTime = file.createdTime.date; fileMeta.webViewLink = file.webViewLink; fileMeta.parensID = file.parents[0]; fileMeta.authorName = file.owners[0].displayName; if ([file.mimeType isEqualToString:@"application/vnd.google-apps.folder"]) { fileMeta.filetype = KMCloudServiceFileType_Folder; } else { fileMeta.filetype = KMCloudServiceFileType_File; } if (![file.name isEqualToString:@".DS_Store"] && ![file.mimeType isEqualToString:@"application/vnd.google-apps.shortcut"]) { [self.localFileList addObject:fileMeta]; } } } if (_getFileListBlock) { _getFileListBlock(self.localFileList,KMGoogleDrive,nil); [self.localFileList removeAllObjects]; } } else { if (_getFileListBlock) { _getFileListBlock(nil,KMGoogleDrive,callbackError); } } }]; } @end