Developer Area

root/apps/iphone/superbook/trunk/Classes/FriendsData.m @ 396

Revision 386, 15.4 kB (checked in by henri, 5 years ago)

Refactoring: removed an NSTimer and replaced with performSelector with delay

Line 
1//
2//  FriendsData.m
3//  LocateThem
4//
5//  Created by Henri Asseily on 8/15/08.
6/*
7 Copyright (c) 2008-2009, Telnic Ltd. All rights reserved.
8 
9 Redistribution and use in source and binary forms, with or without modification,
10 are permitted provided that the following conditions are met:
11 
12 Redistributions of source code must retain the above copyright notice, this list of conditions
13 and the following disclaimer. Redistributions in binary form must reproduce the above copyright
14 notice, this list of conditions and the following disclaimer in the documentation and/or other
15 materials provided with the distribution.
16 Neither the name of the Telnic Ltd. nor the names of its contributors may be used to endorse or
17 promote products derived from this software without specific prior written permission.
18 THIS DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
19 OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
20 AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
24 IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27//
28
29#import "FriendsData.h"
30
31static FriendsData *sharedFriendsData = nil;
32
33@interface FriendsData (PrivateMethods)
34
35void abChanged (ABAddressBookRef addressBook,
36                                CFDictionaryRef info,
37                                void *context);
38
39@end
40
41@implementation FriendsData
42
43@synthesize delegate;
44@synthesize sectionNames;
45@synthesize prefsUnitKm;
46@synthesize prefsSorting;
47@synthesize lastFriendsUpdate;
48@synthesize peopleCurrentList;
49@synthesize peopleNewList;
50@synthesize sortedPeopleArray;
51
52- (id) init {
53        self = [super init];
54        noLoc = [[[CLLocation alloc] initWithLatitude:0 longitude:0] retain];
55        self.sectionNames = [NSArray arrayWithObjects:@"Friends with .tel", nil];
56        self.peopleCurrentList = [NSMutableDictionary dictionary];
57        self.peopleNewList = [NSMutableDictionary dictionary];
58        // get user preferences
59        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
60        self.prefsUnitKm = [defaults integerForKey:@"unitsPreference"];
61        self.prefsSorting = [defaults integerForKey:@"sortingPreference"];
62
63        book = ABAddressBookCreate();
64        ABAddressBookRegisterExternalChangeCallback(book, abChanged, self);
65        return self;
66}
67
68- (void)loadFriendsDataFromAB:(ABAddressBookRef)aBook {
69        ABAddressBookRef theBook = ABAddressBookCreate();       // Override the incoming ab because it's not thread safe
70        if (self != nil) {
71                if (!theBook) {
72                        NSLog(@"Error opening address book");
73                        if (self.delegate)
74                                [self.delegate friendsDataDidLoad];
75                        return;
76                }
77                ABRecordRef *selectedGroup = NULL;
78
79                // The below code allows to filter people by a selected chosen group
80                // We disable it for now, and maybe enable it when we allow the user to
81                // choose which group he wants to filter by
82                // TODO: allow filter by group, managed in preferences
83//              CFArrayRef allGroups = ABAddressBookCopyArrayOfAllGroups(theBook);
84//              CFIndex nGroups = ABAddressBookGetGroupCount(theBook);
85//              ABRecordRef *aGroup;
86//              for (int i=0;i < nGroups;i++) {
87//                      aGroup = (ABRecordRef *)CFArrayGetValueAtIndex(allGroups,i);
88//                      NSString* gName = (NSString *)ABRecordCopyValue(aGroup, kABGroupNameProperty);
89//                      if ([gName isEqualToString:@"TelFriends"]) {
90//                              [gName release];
91//                              selectedGroup = aGroup;
92//                              break;
93//                      }
94//                      [gName release];
95//              }
96               
97                // Find everyone with a .tel and add them to peopleCurrentList
98                CFArrayRef allPeople;
99                CFIndex nPeople = 0;
100                if (selectedGroup != NULL) {
101                        allPeople = ABGroupCopyArrayOfAllMembers(selectedGroup);
102                        if (allPeople)
103                                nPeople = CFArrayGetCount(allPeople);
104                } else {
105                        allPeople = ABAddressBookCopyArrayOfAllPeople(theBook);
106                        if (allPeople)
107                                nPeople = ABAddressBookGetPersonCount(theBook);
108                }
109                ABRecordRef *aPerson;
110               
111                for (int i=0;i < nPeople;i++) {
112                        aPerson = (ABRecordRef *)CFArrayGetValueAtIndex(allPeople,i);
113                       
114                        NSNumber *aPersonId = [NSNumber numberWithInt:(int)ABRecordGetRecordID(aPerson)];
115                        NSString *theTel = [self getTelForABRecordId:aPersonId withABRecordRef:aPerson];
116                        if (theTel) {
117                                // the people list has for each personid: name, type of status, location, location timestamp, distance from user and telname
118                                NSString *compName = [(NSString *)ABRecordCopyCompositeName(aPerson) autorelease];
119                                if (!compName) {
120                                        compName = theTel;
121                                }
122                                NSArray *personInfoRow = [NSArray arrayWithObjects:compName,
123                                                                                  [NSNumber numberWithInt:FriendsDataSectionStatusUnknown],
124                                                                                  noLoc,
125                                                                                  [NSDate date],
126                                                                                  [NSNumber numberWithInt:0],
127                                                                                  theTel,
128                                                                                  nil];
129                               
130                                [peopleCurrentList setObject:personInfoRow forKey:aPersonId];
131                        };
132                        CFRelease(aPerson);
133                }
134               
135                if (allPeople)
136                        CFRelease(allPeople);
137//              if (allGroups)
138//                      CFRelease(allGroups);
139               
140                if (prefsSorting == 0) {        // alpha sorting on composite name
141                        self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(compositeNameSortAlgorithm:)];
142                } else if (prefsSorting == 1) { // distance sorting
143                        self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(distanceSortAlgorithm:)];
144                }
145        }
146        if (self.delegate)
147                [self.delegate friendsDataDidLoad];
148
149        return;
150}
151
152- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
153
154        return [self.sectionNames count];
155}
156
157- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
158        return nil;             // No title
159        //return ([NSString stringWithString:[sectionNames objectAtIndex:section]]);
160}
161
162- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
163
164        return [peopleCurrentList count];
165}
166
167- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
168
169       
170        static NSString *MyIdentifier = @"FriendsTableViewCellIdentifier";
171       
172        FriendsTableViewCell *cell = (FriendsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
173        if (cell == nil) {
174                NSArray *friendsTableViewCellNib = [[NSBundle mainBundle] loadNibNamed:@"FriendsTableViewCell" owner:self options:nil];
175                cell = (FriendsTableViewCell *)[friendsTableViewCellNib objectAtIndex:0];
176
177                if (self.prefsUnitKm == 1) {
178                        cell.labelCellUnit.text = @"km";
179                } else {
180                        cell.labelCellUnit.text = @"mi";
181                }               
182        }
183        // Configure the cell
184       
185        NSNumber *thePersonId = [sortedPeopleArray objectAtIndex:indexPath.row];
186//      ABRecordRef thePerson = ABAddressBookGetPersonWithRecordID(book, (ABRecordID)[thePersonId intValue]);
187        NSArray *thePersonInfo = [peopleCurrentList objectForKey:thePersonId];
188       
189        NSString *labelCellDistanceTitle;
190       
191        if ([[thePersonInfo objectAtIndex:1] intValue] == 2) { // unknown if there's a LOC (not yet queried)
192                labelCellDistanceTitle = [NSString stringWithString:@"?"];
193        } else if ([[thePersonInfo objectAtIndex:1] intValue] == 1) { // there is no LOC
194                labelCellDistanceTitle = [NSString stringWithString:@"-"];
195        } else { // there's a location
196//              CLLocation *personLoc = [thePersonInfo objectAtIndex:2];
197//              NSLog(@"Person location: %@", [personLoc description]);
198               
199                NSInteger distanceInUnits;
200                if (self.prefsUnitKm == 1) {
201                        distanceInUnits = [[thePersonInfo objectAtIndex:4] intValue] / 1000;
202                } else {
203                        distanceInUnits = [[thePersonInfo objectAtIndex:4] intValue] / 1609;
204                }
205                labelCellDistanceTitle = [NSString stringWithFormat:@"%d", distanceInUnits];
206        }
207       
208        cell.labelCellName.text = [thePersonInfo objectAtIndex:0];
209//      cell.labelCellName.text = [NSString stringWithFormat:@"%@ %@", (NSString *)ABRecordCopyValue(thePerson, kABPersonFirstNameProperty),
210//                                                         (NSString *)ABRecordCopyValue(thePerson, kABPersonLastNameProperty)];
211        cell.labelCellTel.text = [thePersonInfo objectAtIndex:5];
212        cell.labelCellDistance.text = labelCellDistanceTitle;
213
214        return cell;
215}
216
217
218// Method that flips the current data set with the newly gotten dataset
219// Synchronize to tell everyone it's being updated
220- (void)updateDataSet {
221
222        [peopleCurrentList setDictionary:peopleNewList];
223        [peopleNewList removeAllObjects];
224        if (prefsSorting == 0) {        // alpha sorting on composite name
225                self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(compositeNameSortAlgorithm:)];
226        } else if (prefsSorting == 1) { // distance sorting
227                self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(distanceSortAlgorithm:)];
228        }
229        return;
230}
231
232- (NSString *)makeKmlFromData {
233
234        NSString *kmlLineFormat =       @"<Placemark>"
235                                                                @"<name>%@</name>"
236                                                                @"<Point><coordinates>%f,%f,0</coordinates></Point>"
237                                                                @"</Placemark>";
238        NSMutableString *kml = [[[NSMutableString alloc] initWithString:
239                                                        @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
240                                                         @"<kml xmlns=\"http://earth.google.com/kml/2.2\">"
241                                                         @"<Document>"
242                                                         @"<name>Testing Stuff.kml</name>"
243                                                        ] autorelease];
244
245        // the people list has for each personid: name, type of status, location, location timestamp, distance from user and telname
246        // the sorted people array just has personids
247       
248        NSNumber *theId;
249        NSArray *thePersonInfo;
250        for (theId in sortedPeopleArray) {
251                thePersonInfo = [peopleCurrentList objectForKey:theId];
252                if ([(NSNumber *)[thePersonInfo objectAtIndex:1] intValue] != FriendsDataSectionWithLoc)
253                        continue;
254                [kml appendFormat:kmlLineFormat,
255                                                                                [thePersonInfo objectAtIndex:0],
256                                                                                [(CLLocation *)[thePersonInfo objectAtIndex:2] coordinate].longitude,
257                                                                                [(CLLocation *)[thePersonInfo objectAtIndex:2] coordinate].latitude
258                 ];
259        }
260        [kml appendString:@"</Document></kml>"];
261        return kml;
262}
263
264#pragma mark ---- AddressBook change callback method ----
265
266void abChanged (ABAddressBookRef addressBook,
267                                CFDictionaryRef info,
268                                void *context) {
269        if (ABAddressBookHasUnsavedChanges(addressBook)) {
270                // Not done saving changes, let's wait
271                return;
272        }
273        // Something changed in the address book
274        // Let's get the latest changes and reload
275        FriendsData *fd = context;
276        [fd loadFriendsDataFromAB:addressBook];
277//      [fd performSelectorOnMainThread:@selector(loadFriendsDataFromAB:) withObject:(id)addressBook waitUntilDone:NO];
278}
279
280
281#pragma mark ---- utility methods ----
282
283// Get AddressBook
284- (ABAddressBookRef) getAddressBookRef {
285        return book;
286}
287
288- (NSString *)getNameForABRecordId:(NSNumber *)theId {
289        // WARNING: Only exists if person has been retrieved
290        NSArray *personInfoRow = [peopleCurrentList objectForKey:theId];
291        if (personInfoRow) {
292                return [personInfoRow objectAtIndex:0];
293        }
294        return nil;
295}
296       
297// Get ABRecordId for an Index Path
298- (NSNumber *) getABRecordIdForIndexPath:(NSIndexPath *)indexPath {
299
300        NSNumber *thePersonId = [sortedPeopleArray objectAtIndex:indexPath.row];
301        return thePersonId;
302}
303
304// Get ABRecordId for a specific .tel
305- (NSNumber *) getABRecordIdForTel:(NSString *)telname {
306        if (telname == NULL)
307                return [NSNumber numberWithInt:kABRecordInvalidID];
308        // the people list has for each personid: name, type of status, location, location timestamp, distance from user and telname
309        // the sorted people array just has personids
310       
311        NSNumber *theId;
312        NSArray *thePersonInfo;
313        for (theId in sortedPeopleArray) {
314                thePersonInfo = [peopleCurrentList objectForKey:theId];
315                if ([(NSString *)[thePersonInfo objectAtIndex:5] isEqualToString:telname]) {
316                        return theId;
317                }
318        }
319        // Couldn't find a match
320        return [NSNumber numberWithInt:kABRecordInvalidID];
321}
322
323// Get ABRecordRef for an ID
324- (ABRecordRef) getABRecordRefForABRecordId:(NSNumber *)theId withAddressBook:(ABRecordRef)theBook {
325
326        ABRecordRef recordRef;
327        recordRef = ABAddressBookGetPersonWithRecordID(theBook, (ABRecordID)[theId intValue]);
328        return recordRef;
329}
330
331// Get .tel value for a record: the first url that ends with .tel
332- (NSString *) getTelForABRecordId:(NSNumber *)theId withABRecordRef:(ABRecordRef)recordRef {
333
334        // First see if we've already cached the info
335        NSArray *personInfoRow = [peopleCurrentList objectForKey:theId];
336        if (personInfoRow != nil)
337                return [personInfoRow objectAtIndex:5];
338
339        BOOL shouldRelease = NO;
340        if (!recordRef) {
341                shouldRelease = YES;
342                recordRef = ABAddressBookGetPersonWithRecordID(book, (ABRecordID)[theId intValue]);
343        }
344        CFStringRef telUrl;
345        BOOL foundUrl = NO;
346       
347        ABMultiValueRef allUrls = ABRecordCopyValue(recordRef, kABPersonURLProperty);
348       
349        for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) {
350                telUrl = ABMultiValueCopyValueAtIndex(allUrls, j);
351                if ([(NSString *)telUrl hasSuffix:@".tel"]) {
352                        foundUrl = YES;
353                        break;
354                }
355                if ([(NSString *)telUrl hasSuffix:@".tel/"]) {
356                        foundUrl = YES;
357                        break;
358                }
359                CFRelease(telUrl);
360        }
361//      for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) {
362//              ABMultiValueRemoveValueAndLabelAtIndex(allUrls, j);
363//      }
364        CFRelease(allUrls);
365
366       
367        if (shouldRelease) {
368                CFRelease(recordRef);
369                //CFRelease(theBook); // TODO: why is this crashing?
370        }
371        if (foundUrl) {
372                NSMutableString *cleanTelUrl = [NSMutableString stringWithString:(NSString *)telUrl];
373                CFRelease(telUrl);
374                if ([cleanTelUrl hasSuffix:@".tel/"]) {
375                        // Remove the trailing slash
376                        [cleanTelUrl deleteCharactersInRange:NSMakeRange([cleanTelUrl length]-1, 1)];
377                }
378                if ([cleanTelUrl hasPrefix:@"http://"]) {
379                        NSString *retStr = [cleanTelUrl substringFromIndex:[@"http://" length]];
380                        return retStr;
381                } else if ([(NSString *)telUrl hasPrefix:@"https://"]) {
382                        NSString *retStr = [cleanTelUrl substringFromIndex:[@"https://" length]];
383                        return retStr;
384                } else {
385                        return cleanTelUrl;
386                }
387        }
388       
389        return nil;
390}
391
392// Get .tel ABMultiValue index for a record
393// given any url that ends with .tel
394- (NSInteger) getTelIndexForABRecordId:(NSNumber *)theId withABRecordRef:(ABRecordRef)recordRef {
395       
396       
397        BOOL shouldRelease = NO;
398        if (!recordRef) {
399                shouldRelease = YES;
400                recordRef = ABAddressBookGetPersonWithRecordID(book, (ABRecordID)[theId intValue]);
401        }
402        CFStringRef telUrlValue;
403        ABMultiValueRef allUrls = ABRecordCopyValue(recordRef, kABPersonURLProperty);
404        NSInteger telIndex = -1; //  Return value for nonexistent
405       
406        for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) {
407                telUrlValue = ABMultiValueCopyValueAtIndex(allUrls, j);
408                if ([(NSString *)telUrlValue hasSuffix:@".tel"] || [(NSString *)telUrlValue hasSuffix:@".tel/"]) {
409                        CFRelease(telUrlValue);
410                        telIndex = (NSInteger)j;
411                        break;
412                }
413                CFRelease(telUrlValue);
414        }
415        CFRelease(allUrls);
416        if (shouldRelease) {
417                CFRelease(recordRef);
418        }
419        return telIndex;
420}
421
422#pragma mark ---- singleton object methods ----
423
424// See "Creating a Singleton Instance" in the Cocoa Fundamentals Guide for more info
425
426+ (FriendsData *)sharedInstance {
427    @synchronized(self) {
428        if (sharedFriendsData == nil) {
429            [[self alloc] init]; // assignment not done here
430        }
431    }
432    return sharedFriendsData;
433}
434
435+ (id)allocWithZone:(NSZone *)zone {
436    @synchronized(self) {
437        if (sharedFriendsData == nil) {
438            sharedFriendsData = [super allocWithZone:zone];
439            return sharedFriendsData;  // assignment and return on first allocation
440        }
441    }
442    return nil; // on subsequent allocation attempts return nil
443}
444
445- (id)copyWithZone:(NSZone *)zone
446{
447    return self;
448}
449
450- (id)retain {
451    return self;
452}
453
454- (unsigned)retainCount {
455    return UINT_MAX;  // denotes an object that cannot be released
456}
457
458- (void)release {
459    // do nothing
460}
461
462- (id)autorelease {
463    return self;
464}
465
466- (void)dealloc {
467        CFRelease(book);
468        [super dealloc];
469}
470
471@end
Note: See TracBrowser for help on using the browser.
Telnic
Search This Site
Partners
Neustar
ICANN
Main site | WHOIS | Sell .tel | FAQ | Archived Site | About Telnic | Contact Us