Developer Area

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

Revision 442, 15.0 kB (checked in by henri, 5 years ago)

Centralized parsing .tel query string, and fixed "www" prefix removal.

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        [self refreshPreferences];
59        book = ABAddressBookCreate();
60        ABAddressBookRegisterExternalChangeCallback(book, abChanged, self);
61        return self;
62}
63
64- (void)refreshPreferences {
65        // get user preferences
66        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
67        self.prefsUnitKm = [defaults integerForKey:@"unitsPreference"];
68        self.prefsSorting = [defaults integerForKey:@"sortingPreference"];
69}
70
71- (void)loadFriendsDataFromAB:(ABAddressBookRef)aBook {
72        ABAddressBookRef theBook = ABAddressBookCreate();       // Override the incoming ab because it's not thread safe
73        if (self != nil) {
74                if (!theBook) {
75                        NSLog(@"Error opening address book");
76                        if (self.delegate)
77                                [self.delegate friendsDataDidLoad];
78                        return;
79                }
80                ABRecordRef *selectedGroup = NULL;
81
82                // The below code allows to filter people by a selected chosen group
83                // We disable it for now, and maybe enable it when we allow the user to
84                // choose which group he wants to filter by
85                // TODO: allow filter by group, managed in preferences
86//              CFArrayRef allGroups = ABAddressBookCopyArrayOfAllGroups(theBook);
87//              CFIndex nGroups = ABAddressBookGetGroupCount(theBook);
88//              ABRecordRef *aGroup;
89//              for (int i=0;i < nGroups;i++) {
90//                      aGroup = (ABRecordRef *)CFArrayGetValueAtIndex(allGroups,i);
91//                      NSString* gName = (NSString *)ABRecordCopyValue(aGroup, kABGroupNameProperty);
92//                      if ([gName isEqualToString:@"TelFriends"]) {
93//                              [gName release];
94//                              selectedGroup = aGroup;
95//                              break;
96//                      }
97//                      [gName release];
98//              }
99               
100                // Find everyone with a .tel and add them to peopleCurrentList
101                CFArrayRef allPeople;
102                CFIndex nPeople = 0;
103                if (selectedGroup != NULL) {
104                        allPeople = ABGroupCopyArrayOfAllMembers(selectedGroup);
105                        if (allPeople)
106                                nPeople = CFArrayGetCount(allPeople);
107                } else {
108                        allPeople = ABAddressBookCopyArrayOfAllPeople(theBook);
109                        if (allPeople)
110                                nPeople = ABAddressBookGetPersonCount(theBook);
111                }
112                ABRecordRef *aPerson;
113               
114                for (int i=0;i < nPeople;i++) {
115                        aPerson = (ABRecordRef *)CFArrayGetValueAtIndex(allPeople,i);
116                       
117                        NSNumber *aPersonId = [NSNumber numberWithInt:(int)ABRecordGetRecordID(aPerson)];
118                        NSString *theTel = [self getTelForABRecordId:aPersonId withABRecordRef:aPerson];
119                        if (theTel) {
120                                // the people list has for each personid: name, type of status, location, location timestamp, distance from user and telname
121                                NSString *compName = [(NSString *)ABRecordCopyCompositeName(aPerson) autorelease];
122                                if (!compName) {
123                                        compName = theTel;
124                                }
125                                NSArray *personInfoRow = [NSArray arrayWithObjects:compName,
126                                                                                  [NSNumber numberWithInt:FriendsDataSectionStatusUnknown],
127                                                                                  noLoc,
128                                                                                  [NSDate date],
129                                                                                  [NSNumber numberWithInt:0],
130                                                                                  theTel,
131                                                                                  nil];
132                               
133                                [peopleCurrentList setObject:personInfoRow forKey:aPersonId];
134                        };
135                        CFRelease(aPerson);
136                }
137               
138                if (allPeople)
139                        CFRelease(allPeople);
140//              if (allGroups)
141//                      CFRelease(allGroups);
142               
143                if (prefsSorting == 0) {        // alpha sorting on composite name
144                        self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(compositeNameSortAlgorithm:)];
145                } else if (prefsSorting == 1) { // distance sorting
146                        self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(distanceSortAlgorithm:)];
147                }
148        }
149        if (self.delegate)
150                [self.delegate friendsDataDidLoad];
151
152        return;
153}
154
155- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
156
157        return [self.sectionNames count];
158}
159
160- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
161        return nil;             // No title
162        //return ([NSString stringWithString:[sectionNames objectAtIndex:section]]);
163}
164
165- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
166
167        return [peopleCurrentList count];
168}
169
170- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
171
172       
173        static NSString *MyIdentifier = @"FriendsTableViewCellIdentifier";
174       
175        FriendsTableViewCell *cell = (FriendsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
176        if (cell == nil) {
177                NSArray *friendsTableViewCellNib = [[NSBundle mainBundle] loadNibNamed:@"FriendsTableViewCell" owner:self options:nil];
178                cell = (FriendsTableViewCell *)[friendsTableViewCellNib objectAtIndex:0];
179        }
180        // Configure the cell
181       
182        NSNumber *thePersonId = [sortedPeopleArray objectAtIndex:indexPath.row];
183//      ABRecordRef thePerson = ABAddressBookGetPersonWithRecordID(book, (ABRecordID)[thePersonId intValue]);
184        NSArray *thePersonInfo = [peopleCurrentList objectForKey:thePersonId];
185       
186        NSString *labelCellDistanceTitle;
187
188        if (self.prefsUnitKm == 1) {
189                cell.labelCellUnit.text = LocStr(@"km");
190        } else {
191                cell.labelCellUnit.text = LocStr(@"mi");
192        }               
193       
194        if ([[thePersonInfo objectAtIndex:1] intValue] == 2) { // unknown if there's a LOC (not yet queried)
195                labelCellDistanceTitle = @"";
196                cell.labelCellUnit.text = @"";
197        } else if ([[thePersonInfo objectAtIndex:1] intValue] == 1) { // there is no LOC
198                labelCellDistanceTitle = [NSString stringWithString:@"-"];
199        } else { // there's a location
200//              CLLocation *personLoc = [thePersonInfo objectAtIndex:2];
201//              NSLog(@"Person location: %@", [personLoc description]);
202               
203                NSInteger distanceInUnits;
204                if (self.prefsUnitKm == 1) {
205                        distanceInUnits = [[thePersonInfo objectAtIndex:4] intValue] / 1000;
206                } else {
207                        distanceInUnits = [[thePersonInfo objectAtIndex:4] intValue] / 1609;
208                }
209                labelCellDistanceTitle = [NSString stringWithFormat:@"%d", distanceInUnits];
210        }
211       
212        cell.labelCellName.text = [thePersonInfo objectAtIndex:0];
213//      cell.labelCellName.text = [NSString stringWithFormat:@"%@ %@", (NSString *)ABRecordCopyValue(thePerson, kABPersonFirstNameProperty),
214//                                                         (NSString *)ABRecordCopyValue(thePerson, kABPersonLastNameProperty)];
215        cell.labelCellTel.text = [thePersonInfo objectAtIndex:5];
216        cell.labelCellDistance.text = labelCellDistanceTitle;
217
218        return cell;
219}
220
221
222// Method that flips the current data set with the newly gotten dataset
223// Synchronize to tell everyone it's being updated
224- (void)updateDataSet {
225
226        [peopleCurrentList setDictionary:peopleNewList];
227        [peopleNewList removeAllObjects];
228        if (prefsSorting == 0) {        // alpha sorting on composite name
229                self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(compositeNameSortAlgorithm:)];
230        } else if (prefsSorting == 1) { // distance sorting
231                self.sortedPeopleArray = [peopleCurrentList keysSortedByValueUsingSelector:@selector(distanceSortAlgorithm:)];
232        }
233        return;
234}
235
236- (NSString *)makeKmlFromData {
237
238        NSString *kmlLineFormat =       @"<Placemark>"
239                                                                @"<name>%@</name>"
240                                                                @"<Point><coordinates>%f,%f,0</coordinates></Point>"
241                                                                @"</Placemark>";
242        NSMutableString *kml = [[[NSMutableString alloc] initWithString:
243                                                        @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
244                                                         @"<kml xmlns=\"http://earth.google.com/kml/2.2\">"
245                                                         @"<Document>"
246                                                         @"<name>Testing Stuff.kml</name>"
247                                                        ] autorelease];
248
249        // the people list has for each personid: name, type of status, location, location timestamp, distance from user and telname
250        // the sorted people array just has personids
251       
252        NSNumber *theId;
253        NSArray *thePersonInfo;
254        for (theId in sortedPeopleArray) {
255                thePersonInfo = [peopleCurrentList objectForKey:theId];
256                if ([(NSNumber *)[thePersonInfo objectAtIndex:1] intValue] != FriendsDataSectionWithLoc)
257                        continue;
258                [kml appendFormat:kmlLineFormat,
259                                                                                [thePersonInfo objectAtIndex:0],
260                                                                                [(CLLocation *)[thePersonInfo objectAtIndex:2] coordinate].longitude,
261                                                                                [(CLLocation *)[thePersonInfo objectAtIndex:2] coordinate].latitude
262                 ];
263        }
264        [kml appendString:@"</Document></kml>"];
265        return kml;
266}
267
268#pragma mark ---- AddressBook change callback method ----
269
270void abChanged (ABAddressBookRef addressBook,
271                                CFDictionaryRef info,
272                                void *context) {
273        if (ABAddressBookHasUnsavedChanges(addressBook)) {
274                // Not done saving changes, let's wait
275                return;
276        }
277        // Something changed in the address book
278        // Let's get the latest changes and reload
279        FriendsData *fd = context;
280        [fd loadFriendsDataFromAB:addressBook];
281//      [fd performSelectorOnMainThread:@selector(loadFriendsDataFromAB:) withObject:(id)addressBook waitUntilDone:NO];
282}
283
284
285#pragma mark ---- utility methods ----
286
287// Get AddressBook
288- (ABAddressBookRef) getAddressBookRef {
289        return book;
290}
291
292- (NSString *)getNameForABRecordId:(NSNumber *)theId {
293        // WARNING: Only exists if person has been retrieved
294        NSArray *personInfoRow = [peopleCurrentList objectForKey:theId];
295        if (personInfoRow) {
296                return [personInfoRow objectAtIndex:0];
297        }
298        return nil;
299}
300       
301// Get ABRecordId for an Index Path
302- (NSNumber *) getABRecordIdForIndexPath:(NSIndexPath *)indexPath {
303
304        NSNumber *thePersonId = [sortedPeopleArray objectAtIndex:indexPath.row];
305        return thePersonId;
306}
307
308// Get ABRecordId for a specific .tel
309- (NSNumber *) getABRecordIdForTel:(NSString *)telname {
310        if (telname == NULL)
311                return [NSNumber numberWithInt:kABRecordInvalidID];
312        // the people list has for each personid: name, type of status, location, location timestamp, distance from user and telname
313        // the sorted people array just has personids
314       
315        NSNumber *theId;
316        NSArray *thePersonInfo;
317        for (theId in sortedPeopleArray) {
318                thePersonInfo = [peopleCurrentList objectForKey:theId];
319                if ([(NSString *)[thePersonInfo objectAtIndex:5] isEqualToString:telname]) {
320                        return theId;
321                }
322        }
323        // Couldn't find a match
324        return [NSNumber numberWithInt:kABRecordInvalidID];
325}
326
327// Get ABRecordRef for an ID
328- (ABRecordRef) getABRecordRefForABRecordId:(NSNumber *)theId withAddressBook:(ABRecordRef)theBook {
329
330        ABRecordRef recordRef;
331        recordRef = ABAddressBookGetPersonWithRecordID(theBook, (ABRecordID)[theId intValue]);
332        return recordRef;
333}
334
335// Get .tel value for a record: the first url that ends with .tel
336- (NSString *) getTelForABRecordId:(NSNumber *)theId withABRecordRef:(ABRecordRef)recordRef {
337
338        // First see if we've already cached the info
339        NSArray *personInfoRow = [peopleCurrentList objectForKey:theId];
340        if (personInfoRow != nil)
341                return [personInfoRow objectAtIndex:5];
342
343        BOOL shouldRelease = NO;
344        if (!recordRef) {
345                shouldRelease = YES;
346                recordRef = ABAddressBookGetPersonWithRecordID(book, (ABRecordID)[theId intValue]);
347        }
348        CFStringRef telUrl;
349        BOOL foundUrl = NO;
350       
351        ABMultiValueRef allUrls = ABRecordCopyValue(recordRef, kABPersonURLProperty);
352       
353        for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) {
354                telUrl = ABMultiValueCopyValueAtIndex(allUrls, j);
355                if ([(NSString *)telUrl hasSuffix:@".tel"]) {
356                        foundUrl = YES;
357                        break;
358                }
359                if ([(NSString *)telUrl hasSuffix:@".tel/"]) {
360                        foundUrl = YES;
361                        break;
362                }
363                CFRelease(telUrl);
364        }
365//      for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) {
366//              ABMultiValueRemoveValueAndLabelAtIndex(allUrls, j);
367//      }
368        CFRelease(allUrls);
369
370       
371        if (shouldRelease) {
372                CFRelease(recordRef);
373        }
374        if (foundUrl) {
375                NSString *returnedTelUrl = [NSString stringWithString:(NSString *)telUrl];
376                CFRelease(telUrl);
377                return returnedTelUrl;
378        }
379       
380        return nil;
381}
382
383// Get .tel ABMultiValue index for a record
384// given any url that ends with .tel
385- (NSInteger) getTelIndexForABRecordId:(NSNumber *)theId withABRecordRef:(ABRecordRef)recordRef {
386       
387       
388        BOOL shouldRelease = NO;
389        if (!recordRef) {
390                shouldRelease = YES;
391                recordRef = ABAddressBookGetPersonWithRecordID(book, (ABRecordID)[theId intValue]);
392        }
393        CFStringRef telUrlValue;
394        ABMultiValueRef allUrls = ABRecordCopyValue(recordRef, kABPersonURLProperty);
395        NSInteger telIndex = -1; //  Return value for nonexistent
396       
397        for (CFIndex j = 0; j < ABMultiValueGetCount(allUrls); j++) {
398                telUrlValue = ABMultiValueCopyValueAtIndex(allUrls, j);
399                if ([(NSString *)telUrlValue hasSuffix:@".tel"] || [(NSString *)telUrlValue hasSuffix:@".tel/"]) {
400                        CFRelease(telUrlValue);
401                        telIndex = (NSInteger)j;
402                        break;
403                }
404                CFRelease(telUrlValue);
405        }
406        CFRelease(allUrls);
407        if (shouldRelease) {
408                CFRelease(recordRef);
409        }
410        return telIndex;
411}
412
413#pragma mark ---- singleton object methods ----
414
415// See "Creating a Singleton Instance" in the Cocoa Fundamentals Guide for more info
416
417+ (FriendsData *)sharedInstance {
418    @synchronized(self) {
419        if (sharedFriendsData == nil) {
420            [[self alloc] init]; // assignment not done here
421        }
422    }
423    return sharedFriendsData;
424}
425
426+ (id)allocWithZone:(NSZone *)zone {
427    @synchronized(self) {
428        if (sharedFriendsData == nil) {
429            sharedFriendsData = [super allocWithZone:zone];
430            return sharedFriendsData;  // assignment and return on first allocation
431        }
432    }
433    return nil; // on subsequent allocation attempts return nil
434}
435
436- (id)copyWithZone:(NSZone *)zone
437{
438    return self;
439}
440
441- (id)retain {
442    return self;
443}
444
445- (unsigned)retainCount {
446    return UINT_MAX;  // denotes an object that cannot be released
447}
448
449- (void)release {
450    // do nothing
451}
452
453- (id)autorelease {
454    return self;
455}
456
457- (void)dealloc {
458        CFRelease(book);
459        [super dealloc];
460}
461
462@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