Added Deployment-Debug_PPC-Only build style to FriBidi. Fixes #9021
[adiumx.git] / Source / AIAddressBookUserIconSource.m
blob0e7faec9b1fb95b48dd14ffae03077d2485e1ac9
1 //
2 //  AIAddressBookUserIconSource.m
3 //  Adium
4 //
5 //  Created by Evan Schoenberg on 1/4/08.
6 //
8 #import "AIAddressBookUserIconSource.h"
9 #import "ESAddressBookIntegrationPlugin.h"
10 #import <Adium/AIPreferenceControllerProtocol.h>
11 #import <AddressBook/AddressBook.h>
12 #import <Adium/AIUserIcons.h>
13 #import <Adium/AIMetaContact.h>
15 #import <AIUtilities/AIImageAdditions.h>
17 #define KEY_AB_IMAGE_SYNC                                               @"AB Image Sync"
18 #define KEY_AB_PREFER_ADDRESS_BOOK_IMAGES               @"AB Prefer AB Images"
20 @implementation AIAddressBookUserIconSource
22 - (id)init
24         if ((self = [super init])) {
25                 //Tracking dictionary for asynchronous image loads
26                 trackingDict = [[NSMutableDictionary alloc] init];
27                 trackingDictPersonToTagNumber = [[NSMutableDictionary alloc] init];
28                 trackingDictTagNumberToPerson = [[NSMutableDictionary alloc] init];
29                 priority = AIUserIconLowPriority;
31                 [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_ADDRESSBOOK];
32         }
33         
34         return self;
37 - (void)dealloc
39         [trackingDictPersonToTagNumber release]; trackingDictPersonToTagNumber = nil;
40         [trackingDictTagNumberToPerson release]; trackingDictTagNumberToPerson = nil;
41         
42         [super dealloc];
45 /*!
46  * @brief AIUserIcons wants this source to update its user icon for an object
47  *
48  * Call +[AIUserIcons userIconSource:didDetermineUserIcon:asynchronously:forObject:] with the new icon, if appropriate
49  *
50  * @result An AIUserIconSourceQueryResult indicating the result
51  */
52 - (AIUserIconSourceQueryResult)updateUserIconForObject:(AIListObject *)inObject
54         if ([self queueDelayedFetchOfImageForPerson:[[ESAddressBookIntegrationPlugin class] personForListObject:inObject]
55                                                                                  object:inObject]) {
56                 return AIUserIconSourceLookingUpIconAsynchronously;
57         } else {
58                 return AIUserIconSourceDidNotFindIcon;
59         }
62 /*!
63  * @brief The priority at which this source should be used. See the #defines in AIUserIcons.h for posible values.
64  */
65 - (AIUserIconPriority)priority
67         return priority;
70 #pragma mark -
72 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
73                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
75         AIUserIconPriority oldPriority = priority;
77         preferAddressBookImages = [[prefDict objectForKey:KEY_AB_PREFER_ADDRESS_BOOK_IMAGES] boolValue];
78         useABImages = [[prefDict objectForKey:KEY_AB_USE_IMAGES] boolValue];
79         
80         priority = (preferAddressBookImages ? AIUserIconHighPriority : AIUserIconLowPriority);
81         if (priority != oldPriority) {
82                 [AIUserIcons userIconSource:self priorityDidChange:priority fromPriority:oldPriority];
83         }
86 #pragma mark Address Book
87 /*!
88  * @brief Called when the address book completes an asynchronous image lookup
89  *
90  * @param inData NSData representing an NSImage
91  * @param tag A tag indicating the lookup with which this call is associated. We use a tracking dictionary, trackingDict, to associate this int back to a usable object.
92  */
93 - (void)consumeImageData:(NSData *)inData forTag:(int)tag
95         if (useABImages) {
96                 NSNumber                *tagNumber;
97                 NSImage                 *image;
98                 AIListObject    *listObject;
99                 //              AIListContact   *parentContact;
100                 NSString                *uniqueID;
101                 id                              setOrObject;
102                 
103                 tagNumber = [NSNumber numberWithInt:tag];
104                 
105                 //Apply the image to the appropriate listObject
106                 image = (inData ? [[[NSImage alloc] initWithData:inData] autorelease] : nil);
107                 
108                 //Address book can feed us giant images, which we really don't want to keep around
109                 NSSize size = [image size];
110                 if (size.width > 96 || size.height > 96)
111                         image = [image imageByScalingToSize:NSMakeSize(96, 96)];
112                 
113                 //Get the object from our tracking dictionary
114                 setOrObject = [trackingDict objectForKey:tagNumber];
115                 
116                 if ([setOrObject isKindOfClass:[AIListObject class]]) {
117                         listObject = (AIListObject *)setOrObject;
118                         
119                         [AIUserIcons userIconSource:self
120                                    didDetermineUserIcon:image
121                                                  asynchronously:YES
122                                                           forObject:listObject];
123                         
124                 } else /*if ([setOrObject isKindOfClass:[NSSet class]])*/{
125                         NSEnumerator    *enumerator;
126                         
127                         //Apply the image to each listObject at the appropriate priority
128                         enumerator = [(NSSet *)setOrObject objectEnumerator];
129                         while ((listObject = [enumerator nextObject])) {
130                                 [AIUserIcons userIconSource:self
131                                            didDetermineUserIcon:image
132                                                          asynchronously:YES
133                                                                   forObject:listObject];
134                         }
135                 }
137                 //No further need for the dictionary entries
138                 [trackingDict removeObjectForKey:tagNumber];
139                 
140                 if ((uniqueID = [trackingDictTagNumberToPerson objectForKey:tagNumber])) {
141                         [trackingDictPersonToTagNumber removeObjectForKey:uniqueID];
142                         [trackingDictTagNumberToPerson removeObjectForKey:tagNumber];
143                 }
144         }
148  * @brief Queue an asynchronous image fetch for person associated with inObject
150  * Image lookups are done asynchronously.  This allows other processing to be done between image calls, improving the perceived
151  * speed.  [Evan: I have seen one instance of this being problematic. My localhost loop was broken due to odd network problems,
152  *                      and the asynchronous lookup therefore hung the problem.  Submitted as radar 3977541.]
154  * We load from the same ABPerson for multiple AIListObjects, one for each service/UID combination times
155  * the number of accounts on that service.  We therefore aggregate the lookups to lower the address book search
156  * and image/data creation overhead.
158  * @param person The ABPerson to fetch the image from
159  * @param inObject The AIListObject with which to ultimately associate the image
160  */
161 - (BOOL)queueDelayedFetchOfImageForPerson:(ABPerson *)person object:(AIListObject *)inObject
163         if (!person) return NO;
165         /* Some mild complexity here. If inObject is a metacontact, we should only proceed if
166          * none of its contained contacts have a higher-priority user icon than we will be.
167          * This prevents a metacontact-associated address book image from overriding a serverside
168          * contained-contact image if that isn't the sort of thing that the user might be into.
169          */
170         if ([inObject isKindOfClass:[AIMetaContact class]]) {
171                 NSEnumerator  *enumerator = [[(AIMetaContact *)inObject listContacts] objectEnumerator];
172                 AIListContact *listContact;
173                 while ((listContact = [enumerator nextObject])) {
174                         if (![AIUserIcons userIconSource:self changeWouldBeRelevantForObject:inObject])
175                                 return NO;
176                 }
177         }
179         int                             tag;
180         NSNumber                *tagNumber;
181         NSString                *uniqueId;
182         
183         uniqueId = [person uniqueId];
184         
185         //Check if we already have a tag for the loading of another object with the same
186         //internalObjectID
187         if ((tagNumber = [trackingDictPersonToTagNumber objectForKey:uniqueId])) {
188                 id                              previousValue;
189                 NSMutableSet    *objectSet;
190                 
191                 previousValue = [trackingDict objectForKey:tagNumber];
192                 
193                 if ([previousValue isKindOfClass:[AIListObject class]]) {
194                         //If the old value is just a listObject, create an array with the old object
195                         //and the new object
196                         objectSet = [NSMutableSet setWithObjects:previousValue,inObject,nil];
197                         
198                         //Store the array in the tracking dict
199                         [trackingDict setObject:objectSet forKey:tagNumber];
200                         
201                 } else /*if ([previousValue isKindOfClass:[NSMutableArray class]])*/{
202                         //Add the new object to the previously-created array
203                         [(NSMutableSet *)previousValue addObject:inObject];
204                 }
206         } else {
207                 //Begin the image load
208                 tag = [person beginLoadingImageDataForClient:self];
209                 tagNumber = [NSNumber numberWithInt:tag];
210                 
211                 //We need to be able to take a tagNumber and retrieve the object
212                 [trackingDict setObject:inObject forKey:tagNumber];
213                 
214                 //We also want to take a person's uniqueID and potentially find an existing tag number
215                 [trackingDictPersonToTagNumber setObject:tagNumber forKey:uniqueId];
216                 [trackingDictTagNumberToPerson setObject:uniqueId forKey:tagNumber];
217         }
219         return YES;
223 @end