arq_restore/cocoastack/googledrive/GoogleDrive.m
2014-07-28 14:20:07 -04:00

563 lines
27 KiB
Objective-C

//
// GoogleDrive.m
// Arq
//
// Created by Stefan Reitshamer on 7/16/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "GoogleDrive.h"
#import "GoogleDriveRequest.h"
#import "NSString+SBJSON.h"
#import "GoogleDriveFolderLister.h"
#import "NSObject+SBJSON.h"
#import "NSString_extra.h"
#import "S3ObjectMetadata.h"
#import "ISO8601Date.h"
static NSString *kFolderMimeType = @"application/vnd.google-apps.folder";
@implementation GoogleDrive
+ (NSString *)errorDomain {
return @"GoogleDriveErrorDomain";
}
+ (NSURL *)endpoint {
return [NSURL URLWithString:@"https://www.googleapis.com/drive"];
}
- (id)init {
NSAssert(0==1, @"don't call this init method!");
return nil;
}
- (id)initWithEmailAddress:(NSString *)theEmailAddress refreshToken:(NSString *)theRefreshToken delegate:(id<GoogleDriveDelegate>)theDelegate {
if (self = [super init]) {
emailAddress = [theEmailAddress retain];
refreshToken = [theRefreshToken retain];
delegate = theDelegate;
NSAssert(delegate != nil, @"delegate may not be nil");
}
return self;
}
- (void)dealloc {
[emailAddress release];
[refreshToken release];
[super dealloc];
}
- (NSDictionary *)aboutWithTargetConnectionDelegate:(id<TargetConnectionDelegate>)theTCD error:(NSError **)error {
GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress method:@"GET" path:@"/drive/v2/about" queryString:nil refreshToken:refreshToken googleDriveDelegate:delegate dataTransferDelegate:nil error:error] autorelease];
if (req == nil) {
return nil;
}
NSData *response = [req dataWithTargetConnectionDelegate:theTCD error:error];
if (response == nil) {
return nil;
}
NSString *responseString = [[[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding] autorelease];
return (NSDictionary *)[responseString JSONValue:error];
}
- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [self fileExistsAtPath:thePath isDirectory:isDirectory dataSize:NULL lastModifiedDate:NULL targetConnectionDelegate:theDelegate error:error];
}
- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [self fileExistsAtPath:thePath isDirectory:NULL dataSize:theDataSize lastModifiedDate:NULL targetConnectionDelegate:theDelegate error:error];
}
- (NSNumber *)fileExistsAtPath:(NSString *)thePath isDirectory:(BOOL *)isDirectory dataSize:(unsigned long long *)theDataSize lastModifiedDate:(NSDate **)theLastModifiedDate targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
HSLogDetail(@"checking existence of %@", thePath);
if ([thePath isEqualToString:@"/"]) {
if (isDirectory != NULL) {
*isDirectory = YES;
}
HSLogDebug(@"%@ exists", thePath);
return YES;
}
NSError *myError = nil;
NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theDelegate error:&myError];
if (googleDriveItem == nil) {
if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) {
SETERRORFROMMYERROR;
return nil;
}
HSLogDebug(@"%@ does not exist", thePath);
return [NSNumber numberWithBool:NO];
}
if (isDirectory != NULL) {
*isDirectory = [[googleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType];
}
if (theDataSize != NULL) {
NSString *fileSize = [googleDriveItem objectForKey:@"fileSize"];
*theDataSize = (unsigned long long)[fileSize integerValue];
}
if (theLastModifiedDate != NULL) {
NSDate *date = [ISO8601Date dateFromString:[googleDriveItem objectForKey:@"modifiedDate"] error:error];
if (date == nil) {
return nil;
}
*theLastModifiedDate = date;
}
HSLogDebug(@"%@ exists", thePath);
return [NSNumber numberWithBool:YES];
}
- (NSArray *)contentsOfDirectoryAtPath:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
HSLogDetail(@"getting contents of directory %@", thePath);
if ([thePath hasSuffix:@"/"]) {
thePath = [thePath substringToIndex:[thePath length] - 1];
}
NSError *myError = nil;
NSString *folderId = [self folderIdForPath:thePath targetConnectionDelegate:theDelegate error:&myError];
if (folderId == nil) {
if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) {
SETERRORFROMMYERROR;
return nil;
}
return [NSArray array];
}
GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress refreshToken:refreshToken folderId:folderId googleDriveDelegate:delegate targetConnectionDelegate:theDelegate] autorelease];
NSArray *googleDriveItems = [lister googleDriveItems:error];
if (googleDriveItems == nil) {
return nil;
}
NSMutableArray *ret = [NSMutableArray array];
for (NSDictionary *item in googleDriveItems) {
[ret addObject:[item objectForKey:@"title"]];
}
HSLogDebug(@"returning %ld items for directory %@", [ret count], thePath);
return ret;
}
- (BOOL)createDirectoryAtPath:(NSString *)thePath withIntermediateDirectories:(BOOL)createIntermediates targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
HSLogDetail(@"creating directory %@", thePath);
NSString *parentPath = [thePath stringByDeletingLastPathComponent];
if (createIntermediates && ![thePath isEqualToString:@"/"]) {
BOOL isDirectory = NO;
NSNumber *exists = [self fileExistsAtPath:parentPath isDirectory:&isDirectory targetConnectionDelegate:nil error:error];
if (exists == nil) {
return NO;
}
if ([exists boolValue]) {
if (!isDirectory) {
SETNSERROR([GoogleDrive errorDomain], -1, @"%@ exists and is not a directory", parentPath);
return NO;
}
} else {
if (![self createDirectoryAtPath:parentPath withIntermediateDirectories:YES targetConnectionDelegate:theDelegate error:error]) {
return NO;
}
}
}
NSString *parentFolderId = [self folderIdForPath:parentPath targetConnectionDelegate:theDelegate error:error];
if (parentFolderId == nil) {
return NO;
}
NSString *lastPathComponent = [thePath lastPathComponent];
NSDictionary *parent = [NSDictionary dictionaryWithObject:parentFolderId forKey:@"id"];
NSArray *parents = [NSArray arrayWithObject:parent];
NSDictionary *requestJSON = [NSDictionary dictionaryWithObjectsAndKeys:
lastPathComponent, @"title",
parents, @"parents",
kFolderMimeType, @"mimeType",
nil];
NSData *requestBody = [[requestJSON JSONRepresentation:error] dataUsingEncoding:NSUTF8StringEncoding];
if (requestBody == nil) {
return NO;
}
GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress
method:@"POST"
path:@"/drive/v2/files"
queryString:nil
refreshToken:refreshToken
googleDriveDelegate:delegate
dataTransferDelegate:nil
error:error] autorelease];
if (req == nil) {
return NO;
}
[req setRequestHeader:@"application/json" forKey:@"Content-Type"];
[req setRequestBody:requestBody];
NSData *response = [req dataWithTargetConnectionDelegate:theDelegate error:error];
if (response == nil) {
return NO;
}
NSString *responseString = [[[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding] autorelease];
NSDictionary *responseJSON = [responseString JSONValue:error];
if (responseJSON == nil) {
return NO;
}
NSString *folderId = [responseJSON objectForKey:@"id"];
[delegate googleDriveDidFindFolderId:folderId forPath:thePath refreshToken:refreshToken];
return YES;
}
- (BOOL)removeItemAtPath:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
HSLogDetail(@"deleting %@", thePath);
if ([thePath isEqualToString:@"/"]) {
SETNSERROR([GoogleDrive errorDomain], -1, @"cannot delete root folder");
return NO;
}
NSError *myError = nil;
NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theDelegate error:&myError];
if (googleDriveItem == nil) {
if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) {
SETERRORFROMMYERROR;
return NO;
}
} else {
NSString *fileId = [googleDriveItem objectForKey:@"id"];
GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress
method:@"DELETE"
path:[NSString stringWithFormat:@"/drive/v2/files/%@", fileId]
queryString:nil
refreshToken:refreshToken
googleDriveDelegate:delegate
dataTransferDelegate:nil
error:error] autorelease];
if (req == nil) {
return NO;
}
if ([req dataWithTargetConnectionDelegate:theDelegate error:&myError] == nil) {
// If we do a DELETE and the network goes away, the delete may have happened, so when the network comes back and we retry
// we might get file-not-found. Don't return an error in that case.
if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) {
SETERRORFROMMYERROR;
return NO;
}
}
}
return YES;
}
- (NSData *)contentsOfFileAtPath:(NSString *)thePath dataTransferDelegate:(id<DataTransferDelegate>)theDTDelegate targetConnectionDelegate:(id<TargetConnectionDelegate>)theTCDelegate error:(NSError **)error {
HSLogDetail(@"getting contents of file %@", thePath);
NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theTCDelegate error:error];
if (googleDriveItem == nil) {
return nil;
}
NSURL *downloadURL = [NSURL URLWithString:[googleDriveItem objectForKey:@"downloadUrl"]];
GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithGetURL:downloadURL refreshToken:refreshToken googleDriveDelegate:delegate dataTransferDelegate:theDTDelegate error:error] autorelease];
NSData *response = [req dataWithTargetConnectionDelegate:theTCDelegate error:error];
return response;
}
- (BOOL)writeData:(NSData *)theData mimeType:(NSString *)theMimeType toFileAtPath:(NSString *)thePath dataTransferDelegate:(id <DataTransferDelegate>)theDelegate targetConnectionDelegate:(id <TargetConnectionDelegate>)theTCD error:(NSError **)error {
HSLogDebug(@"putting file %@", thePath);
// FIXME! Hack: If it's in /objects, we checked the object list so don't check for existence of the file:
NSArray *pathComponents = [thePath pathComponents];
if ([pathComponents count] > 2 && [[pathComponents objectAtIndex:[pathComponents count] - 1] length] == 40 && [[pathComponents objectAtIndex:[pathComponents count] - 2] isEqualToString:@"objects"]) {
HSLogDebug(@"skipping existence check for %@", thePath);
} else {
// Delete existing file if any.
if (![self removeItemAtPath:thePath targetConnectionDelegate:theTCD error:error]) {
return NO;
}
}
NSString *parentPath = [thePath stringByDeletingLastPathComponent];
NSError *myError = nil;
NSString *folderId = [self folderIdForPath:parentPath targetConnectionDelegate:theTCD error:&myError];
if (folderId == nil) {
if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) {
SETERRORFROMMYERROR;
return NO;
}
if (![self createDirectoryAtPath:parentPath withIntermediateDirectories:YES targetConnectionDelegate:theTCD error:error]) {
return NO;
}
folderId = [self folderIdForPath:parentPath targetConnectionDelegate:theTCD error:error];
if (folderId == nil) {
return NO;
}
}
GoogleDriveRequest *req = [[[GoogleDriveRequest alloc] initWithEmailAddress:emailAddress
method:@"POST"
path:@"/upload/drive/v2/files"
queryString:@"uploadType=multipart"
refreshToken:refreshToken
googleDriveDelegate:delegate
dataTransferDelegate:theDelegate
error:error] autorelease];
if (req == nil) {
return NO;
}
NSString *lastPathComponent = [thePath lastPathComponent];
NSDictionary *parent = [NSDictionary dictionaryWithObject:folderId forKey:@"id"];
NSArray *parents = [NSArray arrayWithObject:parent];
NSDictionary *requestParams = [NSDictionary dictionaryWithObjectsAndKeys:
lastPathComponent, @"title",
theMimeType, @"mimeType",
parents, @"parents", nil];
NSString *requestJSON = [requestParams JSONRepresentation:error];
if (requestJSON == nil) {
return NO;
}
NSString *uuid = [NSString stringWithRandomUUID];
[req setRequestHeader:[NSString stringWithFormat:@"multipart/related; boundary=\"%@\"", uuid] forKey:@"Content-Type"];
NSMutableData *requestBody = [NSMutableData data];
[requestBody appendData:[[NSString stringWithFormat:@"--%@\n", uuid] dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[@"Content-Type: application/json; charset=UTF-8\n" dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[requestJSON dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[[NSString stringWithFormat:@"--%@\n", uuid] dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\n", theMimeType] dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:theData];
[requestBody appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
[requestBody appendData:[[NSString stringWithFormat:@"--%@--\n", uuid] dataUsingEncoding:NSUTF8StringEncoding]];
[req setRequestBody:requestBody];
NSData *response = [req dataWithTargetConnectionDelegate:theTCD error:error];
return response != nil;
}
- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
HSLogDetail(@"getting size of %@", thePath);
NSError *myError = nil;
NSDictionary *googleDriveItem = [self firstGoogleDriveItemAtPath:thePath targetConnectionDelegate:theDelegate error:&myError];
if (googleDriveItem == nil) {
if (![myError isErrorWithDomain:[GoogleDrive errorDomain] code:ERROR_NOT_FOUND]) {
SETERRORFROMMYERROR;
return nil;
}
HSLogDebug(@"path %@ does not exist; returning size = 0", thePath);
return [NSNumber numberWithUnsignedInteger:0];
}
return [self sizeOfGoogleDriveItem:googleDriveItem targetConnectionDelegate:theDelegate error:error];
}
- (NSArray *)objectsAtPath:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
HSLogDetail(@"getting objects at %@", thePath);
thePath = [thePath stringByDeletingTrailingSlash];
BOOL isDir = NO;
unsigned long long dataSize = NULL;
NSDate *lastModifiedDate = nil;
NSError *myError = nil;
NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDir dataSize:&dataSize lastModifiedDate:&lastModifiedDate targetConnectionDelegate:theDelegate error:&myError];
if (exists == nil) {
SETERRORFROMMYERROR;
return nil;
}
NSArray *ret = nil;
if (![exists boolValue]) {
ret = [NSArray array];
} else if (isDir) {
ret = [self objectsInDirectory:thePath targetConnectionDelegate:theDelegate error:error];
} else {
S3ObjectMetadata *md = [[[S3ObjectMetadata alloc] initWithPath:thePath lastModified:lastModifiedDate size:dataSize storageClass:@"STANDARD"] autorelease];
ret = [NSArray arrayWithObject:md];
}
return ret;
}
- (NSArray *)pathsOfObjectsAtPath:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
HSLogDetail(@"getting paths of objects at %@", thePath);
BOOL isDirectory = NO;
NSNumber *exists = [self fileExistsAtPath:thePath isDirectory:&isDirectory targetConnectionDelegate:theDelegate error:error];
if (exists == nil) {
return nil;
}
NSArray *ret = nil;
if (![exists boolValue]) {
ret = [NSArray array];
} else if (isDirectory) {
ret = [self pathsOfObjectsInDirectory:thePath targetConnectionDelegate:theDelegate error:error];
} else {
ret = [NSArray arrayWithObject:thePath];
}
return ret;
}
#pragma mark internal
- (NSArray *)objectsInDirectory:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *folderId = [self folderIdForPath:thePath targetConnectionDelegate:theDelegate error:error];
if (folderId == nil) {
return nil;
}
GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress refreshToken:refreshToken folderId:folderId googleDriveDelegate:delegate targetConnectionDelegate:theDelegate] autorelease];
NSArray *googleDriveItems = [lister googleDriveItems:error];
if (googleDriveItems == nil) {
return nil;
}
NSMutableArray *ret = [NSMutableArray array];
for (NSDictionary *googleDriveItem in googleDriveItems) {
NSString *name = [googleDriveItem objectForKey:@"title"];
NSString *childPath = [thePath stringByAppendingPathComponent:name];
if ([[googleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) {
NSArray *childObjects = [self objectsInDirectory:childPath targetConnectionDelegate:theDelegate error:error];
if (childObjects == nil) {
return nil;
}
[ret addObjectsFromArray:childObjects];
} else {
NSDate *lastModifiedDate = [ISO8601Date dateFromString:[googleDriveItem objectForKey:@"modifiedDate"] error:error];
if (lastModifiedDate == nil) {
return nil;
}
NSNumber *dataSize = [googleDriveItem objectForKey:@"fileSize"];
S3ObjectMetadata *md = [[[S3ObjectMetadata alloc] initWithPath:childPath lastModified:lastModifiedDate size:dataSize storageClass:@"STANDARD"] autorelease];
[ret addObject:md];
}
}
return ret;
}
- (NSArray *)pathsOfObjectsInDirectory:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *folderId = [self folderIdForPath:thePath targetConnectionDelegate:theDelegate error:error];
if (folderId == nil) {
return nil;
}
GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress
refreshToken:refreshToken
folderId:folderId
googleDriveDelegate:delegate
targetConnectionDelegate:theDelegate] autorelease];
NSArray *googleDriveItems = [lister googleDriveItems:error];
if (googleDriveItems == nil) {
return nil;
}
NSMutableArray *ret = [NSMutableArray array];
for (NSDictionary *googleDriveItem in googleDriveItems) {
NSString *name = [googleDriveItem objectForKey:@"title"];
NSString *childPath = [thePath stringByAppendingPathComponent:name];
if ([[googleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) {
NSArray *childPaths = [self pathsOfObjectsInDirectory:childPath targetConnectionDelegate:theDelegate error:error];
if (childPaths == nil) {
return nil;
}
[ret addObjectsFromArray:childPaths];
} else {
[ret addObject:childPath];
}
}
return ret;
}
- (NSString *)folderIdForPath:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *folderId = [delegate googleDriveFolderIdForPath:thePath refreshToken:refreshToken];
if (folderId != nil) {
return folderId;
}
NSString *lastPathComponent = [thePath lastPathComponent];
NSString *parentPath = [thePath stringByDeletingLastPathComponent];
NSString *parentFolderId = [self folderIdForPath:parentPath targetConnectionDelegate:theDelegate error:error];
if (parentFolderId == nil) {
return nil;
}
GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress
refreshToken:refreshToken
folderId:parentFolderId
name:lastPathComponent
googleDriveDelegate:delegate
targetConnectionDelegate:theDelegate] autorelease];
NSArray *googleDriveItems = [lister googleDriveItems:error];
if (googleDriveItems == nil) {
return nil;
}
for (NSDictionary *item in googleDriveItems) {
if ([[item objectForKey:@"title"] isEqualToString:lastPathComponent]) {
if (![[item objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) {
SETNSERROR([GoogleDrive errorDomain], -1, @"%@ is not a folder", thePath);
}
[delegate googleDriveDidFindFolderId:[item objectForKey:@"id"] forPath:thePath refreshToken:refreshToken];
return [item objectForKey:@"id"];
}
}
SETNSERROR([GoogleDrive errorDomain], ERROR_NOT_FOUND, @"folderId not found for path %@", thePath);
return nil;
}
/*
* NOTE: This method ONLY RETURNS THE FIRST ITEM found with the given name.
* Google Drive API allows more than one item with the same name in the same folder, unfortunately.
*/
- (NSDictionary *)firstGoogleDriveItemAtPath:(NSString *)thePath targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *parentPath = [thePath stringByDeletingLastPathComponent];
NSString *folderId = [self folderIdForPath:parentPath targetConnectionDelegate:theDelegate error:error];
if (folderId == nil) {
return nil;
}
NSString *lastPathComponent = [thePath lastPathComponent];
GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress
refreshToken:refreshToken
folderId:folderId name:lastPathComponent
googleDriveDelegate:delegate
targetConnectionDelegate:theDelegate] autorelease];
NSArray *googleDriveItems = [lister googleDriveItems:error];
if (googleDriveItems == nil) {
return nil;
}
NSDictionary *ret = nil;
for (NSDictionary *item in googleDriveItems) {
if ([[item objectForKey:@"title"] isEqualToString:lastPathComponent]) {
if ([[item objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) {
[delegate googleDriveDidFindFolderId:[item objectForKey:@"id"] forPath:thePath refreshToken:refreshToken];
}
ret = item;
break;
}
}
if (ret == nil) {
SETNSERROR([GoogleDrive errorDomain], ERROR_NOT_FOUND, @"%@ not found in %@", lastPathComponent, parentPath);
}
return ret;
}
- (NSNumber *)sizeOfGoogleDriveItem:(NSDictionary *)theGoogleDriveItem targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
if (![[theGoogleDriveItem objectForKey:@"mimeType"] isEqualToString:kFolderMimeType]) {
NSString *theFileSize = [theGoogleDriveItem objectForKey:@"fileSize"];
return [NSNumber numberWithInteger:[theFileSize integerValue]];
}
GoogleDriveFolderLister *lister = [[[GoogleDriveFolderLister alloc] initWithEmailAddress:emailAddress
refreshToken:refreshToken
folderId:[theGoogleDriveItem objectForKey:@"id"]
googleDriveDelegate:delegate
targetConnectionDelegate:theDelegate] autorelease];
NSArray *googleDriveItems = [lister googleDriveItems:error];
if (googleDriveItems == nil) {
return nil;
}
unsigned long long total = 0;
for (NSDictionary *childGoogleDriveItem in googleDriveItems) {
NSNumber *childSize = [self sizeOfGoogleDriveItem:childGoogleDriveItem targetConnectionDelegate:theDelegate error:error];
if (childSize == nil) {
return nil;
}
total += [childSize unsignedLongLongValue];
}
return [NSNumber numberWithUnsignedLongLong:total];
}
@end