// // BPPerson.m // Attempt2 // // Thanks to IrateScotsman demo code on using AddressBook API. #import "BPPerson.h" #import "BPMailAppData.h" @implementation BPPerson + (void)initialize { knownFavicons = [[NSMutableDictionary alloc] initWithCapacity:10]; } + (NSMutableDictionary *)savedFavicons { return knownFavicons; } + (void)saveFaviconsToDict:(NSMutableDictionary *)dict { [dict setObject:knownFavicons forKey:@"knownFavicons"]; } + (void)loadFaviconsFromDict:(NSDictionary *)dict { NSDictionary *oldFavicons = [dict objectForKey:@"knownFavicons"]; //NSLog(@"cached Favicons %@", oldFavicons); if (knownFavicons != nil) { NSEnumerator *e = [oldFavicons keyEnumerator]; NSString *key; while ((key = [e nextObject]) != nil) { NSImage *value = [oldFavicons objectForKey:key]; [knownFavicons setObject:value forKey:key]; // set image name [value setName:key]; } } } + (void)resetMyEmailAddresses { // set up myEmailAddresses NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:5]; if ([me address] != nil && ![[me address] isEqual:@""]) { [addresses addObject:[me address]]; } [addresses addObjectsFromArray:[mailAppData mailAccountsAddresses]]; myEmailAddresses = [[NSArray arrayWithArray:addresses] retain]; } + (ABPerson *)ABPersonFromEmail:(NSString *)address { if (myEmailAddresses == nil) [BPPerson resetMyEmailAddresses]; if ([myEmailAddresses containsObject:address]) return meInAddressBook; // it's not "me", see if it's someone in the address book ABSearchElement *wholeQuery = [ABPerson searchElementForProperty:kABEmailProperty label:nil // search all email types for a person key:nil value:address comparison:kABPrefixMatchCaseInsensitive]; NSArray *peopleFound = [[ABAddressBook sharedAddressBook] recordsMatchingSearchElement:wholeQuery]; if ([peopleFound count] == 0) { return nil; } else { // ignore for now if > 1 person with same email address return [peopleFound objectAtIndex:0]; } } // from IrateScotsman + (NSString *)displayNameForPerson:(ABPerson *)foundPerson { // Handle cases where either first name or last name (or both) are nil NSString *firstName = [foundPerson valueForProperty:kABFirstNameProperty]; NSString *lastName = [foundPerson valueForProperty:kABLastNameProperty]; NSString *nameString = @""; if (firstName != nil) { nameString = [nameString stringByAppendingString:firstName]; } if (lastName != nil) { nameString = [nameString stringByAppendingString:[NSString stringWithFormat:@" %@", lastName]]; } return nameString; } + (NSString *)primaryEmailForPerson:(ABPerson *)person { // Find primary email address ABMutableMultiValue *anEmailList = [person valueForProperty:kABEmailProperty]; int primaryIndex = [anEmailList indexForIdentifier:[anEmailList primaryIdentifier]]; NSString *anEmail = [anEmailList valueAtIndex:primaryIndex]; if (anEmail == nil) anEmail = @""; return anEmail; } - (NSImage *)loadFaviconAtURL:(NSString *)baseURL { NSURL *faviconURL = [NSURL URLWithString:[baseURL stringByAppendingString:@"/favicon.ico"]]; //NSLog(@"fetching favicon from web for %@", [faviconURL absoluteString]); NSImage *favicon = [[[NSImage alloc] initWithContentsOfURL:faviconURL] autorelease]; if (favicon != nil) { //NSLog(@"found %@", [faviconURL absoluteString]); // put into temp cache [favicon setName:baseURL]; [favicon setScalesWhenResized:YES]; [favicon setSize:NSMakeSize(36,36)]; // put into persistent cache [knownFavicons setObject:favicon forKey:baseURL]; return favicon; } return nil; } - (NSImage *)findImage { // set image NSImage *img = [NSImage imageNamed:emailAddress]; if (img != nil) { return img; } else { if (addressBookPerson == nil) { NSString *strAfterAtSym = [[emailAddress componentsSeparatedByString:@"@"] lastObject]; if (strAfterAtSym == nil) return nil; // if > 3 componenents e.g. 'student.uq.edu.au' // chop it down to 3 components e.g. 'uq.edu.au' NSArray *parts = [strAfterAtSym componentsSeparatedByString:@"."]; NSString *firstPart; NSString *lastTwoParts = nil; if ([parts count] >= 3) { // get 1st part firstPart = [NSString stringWithString:[parts objectAtIndex:[parts count]-3]]; // note last 2 parts lastTwoParts = [NSString stringWithFormat:@".%@", [parts objectAtIndex:[parts count]-2]]; lastTwoParts = [lastTwoParts stringByAppendingFormat:@".%@", [parts objectAtIndex:[parts count]-1]]; // set strAfterAtSym to be 3 parts altogether strAfterAtSym = [firstPart stringByAppendingString:lastTwoParts]; } NSString *baseURL = [NSString stringWithFormat:@"http://www.%@", strAfterAtSym]; // try retrieving from temp cache NSImage *favicon = [NSImage imageNamed:baseURL]; //if (favicon == nil && lastTwoParts != nil) // favicon = [NSImage imageNamed:[NSString stringWithFormat:@"http://www%@", lastTwoParts]]; // try retrieving from web if (favicon == nil) favicon = [self loadFaviconAtURL:baseURL]; //if (favicon == nil && lastTwoParts != nil) { // NSString *shortURL = [NSString stringWithFormat:@"http://www%@", lastTwoParts]; // if (![shortURL isEqual:@"http://www.net.au"]) // favicon = [self loadFaviconAtURL:shortURL]; //} // the last 2 things didn't work cos it freezes sometimes on loading them, // maybe if some servers are weird. it froze on loading www.net.au (which obviously doesn't exist) // So load in background return favicon; } else { img = [[[NSImage alloc] initWithData:[addressBookPerson imageData]] autorelease]; // so person's image can be retrieved to create icons [img setName:emailAddress]; return img; } } } - (void)awakeFromNib { mailAppData = mailAppDataRef; } - (id)initWithName:(NSString *)theName address:(NSString *)theAddress addressBookRef:(ABPerson *)addressBookRef { self = [super init]; addressBookPerson = [addressBookRef retain]; // set name name = [theName retain]; // set email emailAddress = [theAddress retain]; // don't set image here!! otherwise gets image even for CC ppl etc. // just initialise lazily in image() getter. //[self setImage:[self findImage]]; return self; } - (id)initWithName:(NSString *)theName address:(NSString *)theAddress { // see if email is in address book return [self initWithName:theName address:theAddress addressBookRef:[BPPerson ABPersonFromEmail:theAddress]]; } - (void)resetAddressBookPerson { ABPerson *newAddrBookPerson = [[BPPerson ABPersonFromEmail:emailAddress] retain]; [addressBookPerson release]; addressBookPerson = newAddrBookPerson; } // expect address in format: "sender" - (BPPerson *)initWithFormattedAddress:(NSString *)formattedAddress { NSString *theName; NSString *theAddress; NSArray *splitAddr = [formattedAddress componentsSeparatedByString:@"<"]; if ([splitAddr count] != 2) { //NSLog(@"BPPerson could not split theAddress %@ into 2 components, attempt result: %@", theAddress, splitAddr); theName = formattedAddress; theAddress = formattedAddress; } else { // set name & address (trim whitespace off name) theName = [[splitAddr objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; theAddress = [splitAddr objectAtIndex:1]; // remove end ">" from address theAddress = [theAddress substringToIndex:[theAddress length]-1]; } //NSLog(@"initWithFormattedAddress split result: '%@', '%@'", theName, theAddress); return [self initWithName:theName address:theAddress]; } + (BPPerson *)personWithName:(NSString *)aName address:(NSString *)anAddress { BPPerson *p = [[BPPerson alloc] initWithName:aName address:anAddress]; [p autorelease]; return p; } + (BPPerson *)personWithFormattedAddress:(NSString *)formattedAddress { BPPerson *p = [[BPPerson alloc] initWithFormattedAddress:formattedAddress]; [p autorelease]; return p; } + (NSArray *)personsWithFormattedAddresses:(NSArray *)addresses { NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:[addresses count]] autorelease]; int i; for (i=0; i<[addresses count]; i++) { [array addObject:[self personWithFormattedAddress:[addresses objectAtIndex:i]]]; } return array; } // e.g. addressesString: "me" , "you" you@yourplace.com // separator can be comma or semicolon + (NSArray *)personsWithFormattedAddressesString:(NSString *)addressesString { // if blank string, return empty array NSString *trimmed = [addressesString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if ([trimmed isEqual:@""]) return [NSArray array]; NSArray *formattedAddresses = [addressesString componentsSeparatedByString:@","]; if ([formattedAddresses count] == 1) { // there was no comma, try a semi-colon formattedAddresses = [addressesString componentsSeparatedByString:@";"]; } return [self personsWithFormattedAddresses:formattedAddresses]; } + (void)setupMe { // setup a BPPerson representing "me" meInAddressBook = [[ABAddressBook sharedAddressBook] me]; if (meInAddressBook == nil) { // initialise meInAddressBook as something so it can be used to compare // BPPerson objects meInAddressBook = [[ABPerson alloc] init]; me = [[BPPerson alloc] initWithName:@"" address:@"" addressBookRef:meInAddressBook]; } else { NSString *myName = [BPPerson displayNameForPerson:meInAddressBook]; NSString *myEmail = [BPPerson primaryEmailForPerson:meInAddressBook]; /* // if myEmail == @"", should do something if (myEmail == @"") { NSLog(@"Warning: empty email address found for 'me' entry in address book"); } */ me = [[BPPerson alloc] initWithName:myName address:myEmail addressBookRef:meInAddressBook]; } } + (BPPerson *)personThatsMe { if (me == nil) [BPPerson setupMe]; return me; } + (void)setMyEmail:(NSString *)emailAddr { if (me == nil) [BPPerson setupMe]; [emailAddr retain]; [me->emailAddress release]; me->emailAddress = emailAddr; } - (NSString *)summary { if ([name isEqual:emailAddress]) { // name and address are same if there was no separate "name" component return emailAddress; } else { return [NSString stringWithFormat:@"%@ <%@>", name, emailAddress]; } } + (NSString *)summaryForPersons:(NSArray *)persons { if ([persons count] == 0) return @""; NSString *string = [NSString stringWithString:[[persons objectAtIndex:0] summary]]; int i; for (i=1; i<[persons count]; i++) { string = [string stringByAppendingFormat:@", %@", [[persons objectAtIndex:i] summary]]; } return string; } - (NSString *)name { return name; } - (NSString *)address { return emailAddress; } - (BOOL)isInAddressBook { return (addressBookPerson != nil); } - (void)setImage:(NSImage *)anImage { [anImage retain]; [image release]; image = anImage; } - (NSImage *)image { if (image == nil) [self setImage:[self findImage]]; return image; } - (BOOL)isEqual:(id)anObject { if (![anObject isKindOfClass:[BPPerson class]]) return NO; if (anObject == self) return YES; BPPerson *otherPerson = (BPPerson *)anObject; if ([emailAddress isEqual:[otherPerson address]]) { return YES; } if (addressBookPerson != nil && [addressBookPerson isEqual:otherPerson->addressBookPerson]) { return YES; } return NO; } - (NSString *)description { return [NSString stringWithFormat:@"BPPerson '%@' %@", name, emailAddress]; } - (void)dealloc { NSLog(@"Destroying %@", self); [name release]; [emailAddress release]; [addressBookPerson release]; [image release]; [super dealloc]; } #pragma - #pragma archival - (void)encodeWithCoder:(NSCoder *)coder { // super doesn't implement NSCoding, so don't need to call super impl. [coder encodeObject:name forKey:@"name"]; [coder encodeObject:emailAddress forKey:@"emailAddress"]; //[coder encodeObject:image forKey:@"image"]; } - (id)initWithCoder:(NSCoder *)coder { [super init]; name = [[coder decodeObjectForKey:@"name"] retain]; emailAddress = [[coder decodeObjectForKey:@"emailAddress"] retain]; // can't save ABPerson since it doesn't implement NSCoding [self resetAddressBookPerson]; //[self setImage:[self findImage]]; //image = [[coder decodeObjectForKey:@"image"] retain]; return self; } @end