// // MGMGoogleContacts.m // VoiceBase // // Created by Mr. Gecko on 8/17/10. // Copyright (c) 2011 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/ // // Permission to use, copy, modify, and/or distribute this software for any purpose // with or without fee is hereby granted, provided that the above copyright notice // and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, // OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, // DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS // ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // #import "MGMGoogleContacts.h" #import "MGMContactsProtocol.h" #import "MGMInstance.h" #import "MGMAddons.h" #import "MGMXML.h" #import NSString * const MGMGCAuthenticationURL = @"https://www.google.com/accounts/ClientLogin"; NSString * const MGMGCAuthenticationBody = @"Email=%@&Passwd=%@&source=MrGeckosMedia-VoiceBase-0.1&service=cp&accountType=HOSTED_OR_GOOGLE"; NSString * const MGMGCUseragent = @"VoiceBase/0.1"; NSString * const MGMGCContactsURL = @"https://www.google.com/m8/feeds/contacts/default/full?max-results=10000"; NSString * const MGMGCGroupsURL = @"https://www.google.com/m8/feeds/groups/default/full?max-results=10000"; NSString * const MGMGCAuthorization = @"Authorization"; const BOOL MGMGoogleContactsInvisible = YES; @implementation MGMGoogleContacts - (id)initWithDelegate:(id)theDelegate { if ((self = [super init])) { gettingContacts = NO; delegate = theDelegate; user = [[MGMUser userWithID:[[delegate user] settingForKey:MGMCGoogleContactsUser]] retain]; if (user==nil) user = [[delegate user] retain]; NSString *username = [user settingForKey:MGMUserName]; if (![username containsString:@"@"]) username = [username stringByAppendingString:@"@gmail.com"]; NSURLCredential *credentials = [NSURLCredential credentialWithUser:username password:[user password] persistence:NSURLCredentialPersistenceForSession]; connectionManager = [[MGMURLConnectionManager managerWithCookieStorage:[user cookieStorage]] retain]; [connectionManager setCredentials:credentials]; [connectionManager setUserAgent:MGMGCUseragent]; isAuthenticating = YES; afterAuthentication = [NSMutableArray new]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:MGMGCAuthenticationURL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0]; [request setHTTPMethod:MGMPostMethod]; [request setValue:MGMURLForm forHTTPHeaderField:MGMContentType]; [request setHTTPBody:[[NSString stringWithFormat:MGMGCAuthenticationBody, [username addPercentEscapes], [[user password] addPercentEscapes]] dataUsingEncoding:NSUTF8StringEncoding]]; MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self]; [handler setFailWithError:@selector(authentication:didFailWithError:)]; [handler setFinish:@selector(authenticationDidFinish:)]; [handler setInvisible:MGMGoogleContactsInvisible]; [connectionManager addHandler:handler]; } return self; } - (void)dealloc { [user release]; [connectionManager cancelAll]; [connectionManager release]; [authenticationString release]; [afterAuthentication release]; [releaseTimer fire]; [contactEntries release]; [contactPhoto release]; [super dealloc]; } - (void)authentication:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { NSLog(@"MGMGoogleContacts Error: %@", theError); } + (NSDictionary *)dictionaryWithString:(NSString *)theString { NSArray *values = [theString componentsSeparatedByString:@"\n"]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; for (int i=0; i<[values count]; i++) { if (![[values objectAtIndex:i] isEqual:@""]) { NSMutableArray *info = [NSMutableArray arrayWithArray:[[values objectAtIndex:i] componentsSeparatedByString:@"="]]; NSString *key = [info objectAtIndex:0]; [info removeObjectAtIndex:0]; NSString *value = [info componentsJoinedByString:@"="]; [dictionary setObject:value forKey:key]; } } return dictionary; } - (void)authenticationDidFinish:(MGMURLBasicHandler *)theHandler { NSDictionary *info = [MGMGoogleContacts dictionaryWithString:[theHandler string]]; [authenticationString release]; authenticationString = [[NSString stringWithFormat:@"GoogleLogin auth=%@", [info objectForKey:@"Auth"]] retain]; isAuthenticating = NO; while ([afterAuthentication count]!=0) { [[afterAuthentication objectAtIndex:0] invoke]; [afterAuthentication removeObjectAtIndex:0]; } } - (MGMUser *)user { return user; } - (void)stop { shouldStop = YES; [connectionManager cancelAll]; if (gettingContacts || gettingGroups) [connectionManager cancelAll]; shouldStop = NO; } - (void)getContacts:(id)sender { if (isAuthenticating) { SEL selector = @selector(getContacts:); NSMethodSignature *signature = [self methodSignatureForSelector:selector]; if (signature!=nil) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocation setSelector:selector]; [invocation setTarget:self]; [invocation setArgument:&sender atIndex:2]; [afterAuthentication addObject:invocation]; } return; } if (gettingContacts) { NSDictionary *info = [NSDictionary dictionaryWithObject:contactsSender forKey:MGMCRecallSender]; NSError *error = [NSError errorWithDomain:MGMCRecallError code:1 userInfo:info]; if ([sender respondsToSelector:@selector(contactsError:)]) [sender contactsError:error]; return; } gettingContacts = YES; contactsSender = sender; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:MGMGCContactsURL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]; [request setValue:authenticationString forHTTPHeaderField:MGMGCAuthorization]; MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self]; [handler setFailWithError:@selector(contacts:didFailWithError:)]; [handler setFinish:@selector(contactsDidFinish:)]; [handler setInvisible:MGMGoogleContactsInvisible]; [connectionManager addHandler:handler]; } - (void)contacts:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { gettingContacts = NO; NSLog(@"MGMGoogleContacts Error: %@", theError); if ([contactsSender respondsToSelector:@selector(contactsError:)]) [contactsSender contactsError:theError]; } - (void)contactsDidFinish:(MGMURLBasicHandler *)theHandler { [releaseTimer invalidate]; [releaseTimer release]; releaseTimer = nil; [contacts release]; contacts = [NSMutableArray new]; MGMXMLElement *XML = [(MGMXMLDocument *)[[[MGMXMLDocument alloc] initWithData:[theHandler data] options:MGMXMLDocumentTidyXML error:nil] autorelease] rootElement]; contactEntries = [[XML elementsForName:@"entry"] retain]; contactsIndex=0; [self continueContacts]; } - (void)photo:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { NSLog(@"MGMGoogleContacts Photo Error: %@", theError); [self parseContact]; contactsIndex++; [self continueContacts]; } - (void)photoDidFinish:(MGMURLBasicHandler *)theHandler { contactPhoto = [[theHandler data] retain]; [self parseContact]; contactsIndex++; [self continueContacts]; } - (void)parseContact { if (shouldStop) return; MGMXMLElement *entry = [contactEntries objectAtIndex:contactsIndex]; NSArray *titles = [entry elementsForName:@"title"]; NSString *name = @""; if ([titles count]!=0) name = [[titles objectAtIndex:0] stringValue]; NSArray *organizations = [entry elementsForName:@"gd:organization"]; NSString *company = @""; if ([organizations count]!=0) { NSArray *organizationName = [[organizations objectAtIndex:0] elementsForName:@"gd:orgName"]; if ([organizationName count]!=0) company = [[organizationName objectAtIndex:0] stringValue]; } NSArray *phones = [entry elementsForName:@"gd:phoneNumber"]; NSData *image = nil; if ([phones count]>0) { if (contactPhoto!=nil) { image = [contactPhoto resizeTo:MGMABPhotoSize]; [contactPhoto release]; contactPhoto = nil; } } for (int p=0; p<[phones count]; p++) { if (shouldStop) break; MGMXMLElement *phone = [phones objectAtIndex:p]; NSMutableDictionary *contact = [NSMutableDictionary dictionary]; [contact setObject:name forKey:MGMCName]; [contact setObject:company forKey:MGMCCompany]; if (delegate!=nil) [contact setObject:[[phone stringValue] phoneFormatWithAreaCode:[delegate areaCode]] forKey:MGMCNumber]; else [contact setObject:[[phone stringValue] phoneFormat] forKey:MGMCNumber]; NSString *label = @""; MGMXMLNode *labelXML = [phone attributeForName:@"label"]; if (labelXML==nil) { MGMXMLNode *rel = [phone attributeForName:@"rel"]; if (rel!=nil) { NSString *string = [rel stringValue]; NSRange range = [string rangeOfString:@"#"]; if (range.location!=NSNotFound) { label = [string substringFromIndex:range.location+range.length]; } } } else { label = [labelXML stringValue]; } [contact setObject:[label capitalizedString] forKey:MGMCLabel]; [contacts addObject:[[contact copy] autorelease]]; if (image!=nil) [contact setObject:image forKey:MGMCPhoto]; if ([contactsSender respondsToSelector:@selector(gotContact:)]) [contactsSender gotContact:contact]; } } - (void)continueContacts { for (; contactsIndex<[contactEntries count]; contactsIndex++) { if (shouldStop) break; MGMXMLElement *entry = [contactEntries objectAtIndex:contactsIndex]; NSArray *phones = [entry elementsForName:@"gd:phoneNumber"]; if ([phones count]!=0) { NSArray *links = [entry elementsForName:@"link"]; BOOL loadingPhoto = NO; for (int i=0; i<[links count]; i++) { MGMXMLNode *rel = [(MGMXMLElement *)[links objectAtIndex:i] attributeForName:@"rel"]; if (rel!=nil) { if ([[rel stringValue] containsString:@"#photo"]) { NSString *url = [[[links objectAtIndex:i] attributeForName:@"href"] stringValue]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0]; [request setValue:authenticationString forHTTPHeaderField:MGMGCAuthorization]; MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self]; [handler setFailWithError:@selector(photo:didFailWithError:)]; [handler setFinish:@selector(photoDidFinish:)]; [connectionManager addHandler:handler]; loadingPhoto = YES; break; } } } if (!loadingPhoto) [self parseContact]; else break; } } if (contactsIndex>=[contactEntries count]) { releaseTimer = [[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(releaseContacts) userInfo:nil repeats:NO] retain]; if ([contactsSender respondsToSelector:@selector(doneGettingContacts)]) [contactsSender doneGettingContacts]; [contactEntries release]; contactEntries = nil; contactsSender = nil; gettingContacts = NO; } } - (void)releaseContacts { [releaseTimer invalidate]; [releaseTimer release]; releaseTimer = nil; [contacts release]; contacts = nil; } - (void)getGroups:(id)sender { if (isAuthenticating) { SEL selector = @selector(getGroups:); NSMethodSignature *signature = [self methodSignatureForSelector:selector]; if (signature!=nil) { NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocation setSelector:selector]; [invocation setTarget:self]; [invocation setArgument:&sender atIndex:2]; [afterAuthentication addObject:invocation]; } return; } if (gettingGroups) { NSDictionary *info = [NSDictionary dictionaryWithObject:groupsSender forKey:MGMCRecallSender]; NSError *error = [NSError errorWithDomain:MGMCRecallError code:1 userInfo:info]; if ([sender respondsToSelector:@selector(groupsError:)]) [sender groupsError:error]; return; } if ([sender respondsToSelector:@selector(groupsError:)]) [sender groupsError:nil]; return; gettingGroups = YES; groupsSender = sender; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:MGMGCGroupsURL]]; [request setValue:authenticationString forHTTPHeaderField:MGMGCAuthorization]; MGMURLBasicHandler *handler = [MGMURLBasicHandler handlerWithRequest:request delegate:self]; [handler setFailWithError:@selector(groups:didFailWithError:)]; [handler setFinish:@selector(groupsDidFinish:)]; [handler setInvisible:MGMGoogleContactsInvisible]; [connectionManager addHandler:handler]; } - (void)groups:(MGMURLBasicHandler *)theHandler didFailWithError:(NSError *)theError { gettingGroups = NO; NSLog(@"MGMGoogleContacts Error: %@", theError); } - (void)groupsDidFinish:(MGMURLBasicHandler *)theHandler { MGMXMLElement *XML = [(MGMXMLDocument *)[[[MGMXMLDocument alloc] initWithData:[theHandler data] options:MGMXMLDocumentTidyXML error:nil] autorelease] rootElement]; NSLog(@"%@", XML); gettingGroups = NO; } @end