From 5371dcea351f4a3b2b06442241546452f9d4610e Mon Sep 17 00:00:00 2001 From: ylecollen Date: Wed, 28 Jan 2015 17:37:51 +0100 Subject: [PATCH] 1 - The contacts are now cached in the filesystem : it should improve the behaviour at could start 2 - the contact updates are now managed since the last sync so it should also improve the contact book --- matrixConsole/API/ContactManager.h | 6 + matrixConsole/API/ContactManager.m | 205 +++++++++++++++++++++++--- matrixConsole/Model/MXCContact.h | 8 +- matrixConsole/Model/MXCContact.m | 46 +++++- matrixConsole/Model/MXCContactField.h | 2 +- matrixConsole/Model/MXCContactField.m | 21 ++- matrixConsole/Model/MXCEmail.m | 19 +++ matrixConsole/Model/MXCPhoneNumber.h | 3 + matrixConsole/Model/MXCPhoneNumber.m | 48 ++++-- 9 files changed, 322 insertions(+), 36 deletions(-) diff --git a/matrixConsole/API/ContactManager.h b/matrixConsole/API/ContactManager.h index 399db478b..91f646762 100644 --- a/matrixConsole/API/ContactManager.h +++ b/matrixConsole/API/ContactManager.h @@ -22,6 +22,9 @@ // warn when there is a contacts list refresh extern NSString *const kContactManagerContactsListRefreshNotification; +// the phonenumber has been internationalized +extern NSString *const kContactsDidInternationalizeNotification; + @interface ContactManager : NSObject { dispatch_queue_t processingQueue; NSMutableDictionary* matrixIDBy3PID; @@ -34,6 +37,9 @@ extern NSString *const kContactManagerContactsListRefreshNotification; // delete contacts info - (void)reset; +// refresh the international phonenumber of the contacts +- (void)internationalizePhoneNumbers:(NSString*)countryCode; + // refresh self.contacts - (void)fullRefresh; diff --git a/matrixConsole/API/ContactManager.m b/matrixConsole/API/ContactManager.m index 4ef351e4b..869c7363b 100644 --- a/matrixConsole/API/ContactManager.m +++ b/matrixConsole/API/ContactManager.m @@ -27,13 +27,16 @@ // warn when there is a contacts list refresh NSString *const kContactManagerContactsListRefreshNotification = @"kContactManagerContactsListRefreshNotification"; +// the phonenumber has been internationalized +NSString *const kContactsDidInternationalizeNotification = @"kContactsDidInternationalizeNotification"; + // get the 3PIDS in one requests //#define CONTACTS_3PIDS_SYNC 1 // else checks the matrix IDs for each displayed contact @interface ContactManager() { NSDate *lastSyncDate; - NSMutableArray* deviceContactsList; + NSMutableDictionary* deviceContactByContactID; // NSMutableArray* pending3PIDs; @@ -93,10 +96,11 @@ static ContactManager* sharedContactManager = nil; // delete contacts info - (void)reset { + contacts = nil; lastSyncDate = nil; - deviceContactsList = nil; + deviceContactByContactID = nil; matrixContactByMatrixUserID = nil; if (hasStatusObserver) { [[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"]; @@ -104,11 +108,24 @@ static ContactManager* sharedContactManager = nil; } [self saveMatrixIDsDict]; + [self saveDeviceContacts]; + [self saveContactBookInfo]; // warn of the contacts list update [[NSNotificationCenter defaultCenter] postNotificationName:kContactManagerContactsListRefreshNotification object:nil userInfo:nil]; } +// refresh the international phonenumber of the contacts +- (void)internationalizePhoneNumbers:(NSString*)countryCode { + NSArray* contactsSnapshot = self.contacts; + + for(MXCContact* contact in contactsSnapshot) { + [contact internationalizePhonenumbers:countryCode]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:kContactsDidInternationalizeNotification object:nil userInfo:nil]; +} + - (void)fullRefresh { // check if the user allowed to sync local contacts @@ -162,7 +179,23 @@ static ContactManager* sharedContactManager = nil; } dispatch_async(processingQueue, ^{ - NSMutableArray* contactsList = [[NSMutableArray alloc] init]; + + // in case of cold start + // get the info from the file system + if (!lastSyncDate) { + // load cached contacts + [self loadDeviceContacts]; + [self loadContactBookInfo]; + + // no local contact -> assume that the last sync date is useless + if (deviceContactByContactID.count == 0) { + lastSyncDate = nil; + } + } + + BOOL contactBookUpdate = NO; + + NSMutableArray* deletedContactIDs = [[deviceContactByContactID allKeys] mutableCopy]; // can list tocal contacts if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) { @@ -176,8 +209,28 @@ static ContactManager* sharedContactManager = nil; CFIndex peopleCount = CFArrayGetCount(people); for (index = 0; index < peopleCount; index++) { + contactRecord = (ABRecordRef)CFArrayGetValueAtIndex(people, index); - [contactsList addObject:[[MXCContact alloc] initWithABRecord:contactRecord]]; + + NSString* contactID = [MXCContact contactID:contactRecord]; + + // the contact still exists + [deletedContactIDs removeObject:contactID]; + + if (lastSyncDate) { + // ignore unchanged contacts since the previous sync + CFDateRef lastModifDate = ABRecordCopyValue(contactRecord, kABPersonModificationDateProperty); + if (kCFCompareGreaterThan != CFDateCompare (lastModifDate, (__bridge CFDateRef)lastSyncDate, nil)) + { + CFRelease(lastModifDate); + continue; + } + CFRelease(lastModifDate); + } + + contactBookUpdate = YES; + // update the contact + [deviceContactByContactID setValue:[[MXCContact alloc] initWithABRecord:contactRecord] forKey:contactID]; } CFRelease(people); @@ -188,14 +241,28 @@ static ContactManager* sharedContactManager = nil; } } - deviceContactsList = contactsList; + // some contacts have been deleted + for (NSString* contactID in deletedContactIDs) { + contactBookUpdate = YES; + [deviceContactByContactID removeObjectForKey:contactID]; + } + + // something has been modified in the device contact book + if (contactBookUpdate) { + [self saveDeviceContacts]; + } + + lastSyncDate = [NSDate date]; + [self saveContactBookInfo]; + + NSMutableArray* deviceContacts = [[deviceContactByContactID allValues] mutableCopy]; if (mxHandler.mxSession) { [self manage3PIDS]; } else { // display what you could have read dispatch_async(dispatch_get_main_queue(), ^{ - contacts = deviceContactsList; + contacts = deviceContacts; hasStatusObserver = YES; // wait that the mxSession is ready @@ -213,17 +280,10 @@ static ContactManager* sharedContactManager = nil; dispatch_async(processingQueue, ^{ NSMutableArray* tmpContacts = nil; - // initial sync - if (!lastSyncDate) { - // display the current device contacts - tmpContacts = deviceContactsList; - } else { - // update with the known dict 3PID -> matrix ID - [self updateMatrixIDDeviceContactsList]; - - tmpContacts = [deviceContactsList mutableCopy]; - } - lastSyncDate = [NSDate date]; + // update with the known dict 3PID -> matrix ID + [self updateMatrixIDDeviceContacts]; + + tmpContacts = [[deviceContactByContactID allValues] mutableCopy]; dispatch_async(dispatch_get_main_queue(), ^{ // stored self.contacts in the right thread @@ -268,9 +328,12 @@ static ContactManager* sharedContactManager = nil; } } -- (void) updateMatrixIDDeviceContactsList { +- (void) updateMatrixIDDeviceContacts { + + NSArray* deviceContacts = [deviceContactByContactID allValues]; + // update the contacts info - for(MXCContact* contact in deviceContactsList) { + for(MXCContact* contact in deviceContacts) { [self updateContactMatrixIDs:contact]; } } @@ -523,6 +586,8 @@ static ContactManager* sharedContactManager = nil; #pragma mark - file caches static NSString *matrixIDsDictFile = @"matrixIDsDict"; +static NSString *localContactsFile = @"localContacts"; +static NSString *contactsBookInfoFile = @"contacts"; - (void)saveMatrixIDsDict { @@ -579,5 +644,107 @@ static NSString *matrixIDsDictFile = @"matrixIDsDict"; } } +- (void) saveDeviceContacts { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *dataFilePath = [documentsDirectory stringByAppendingPathComponent:localContactsFile]; + + if (deviceContactByContactID && (deviceContactByContactID.count > 0)) + { + NSMutableData *theData = [NSMutableData data]; + NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData]; + + [encoder encodeObject:deviceContactByContactID forKey:@"deviceContactByContactID"]; + + [encoder finishEncoding]; + + [theData writeToFile:dataFilePath atomically:YES]; + } + else + { + NSFileManager *fileManager = [[NSFileManager alloc] init]; + [fileManager removeItemAtPath:dataFilePath error:nil]; + } +} + +- (void) loadDeviceContacts { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *dataFilePath = [documentsDirectory stringByAppendingPathComponent:localContactsFile]; + + NSFileManager *fileManager = [[NSFileManager alloc] init]; + + if ([fileManager fileExistsAtPath:dataFilePath]) + { + // the file content could be corrupted + @try { + NSData* filecontent = [NSData dataWithContentsOfFile:dataFilePath options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:nil]; + + NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:filecontent]; + + id object = [decoder decodeObjectForKey:@"deviceContactByContactID"]; + + if ([object isKindOfClass:[NSDictionary class]]) { + deviceContactByContactID = [object mutableCopy]; + } + + [decoder finishDecoding]; + } @catch (NSException *exception) { + lastSyncDate = nil; + } + } + + if (!deviceContactByContactID) { + deviceContactByContactID = [[NSMutableDictionary alloc] init]; + } +} + +- (void) saveContactBookInfo { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *dataFilePath = [documentsDirectory stringByAppendingPathComponent:contactsBookInfoFile]; + + if (lastSyncDate) + { + NSMutableData *theData = [NSMutableData data]; + NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData]; + + [encoder encodeObject:lastSyncDate forKey:@"lastSyncDate"]; + + [encoder finishEncoding]; + + [theData writeToFile:dataFilePath atomically:YES]; + } + else + { + NSFileManager *fileManager = [[NSFileManager alloc] init]; + [fileManager removeItemAtPath:dataFilePath error:nil]; + } +} + +- (void) loadContactBookInfo { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *dataFilePath = [documentsDirectory stringByAppendingPathComponent:contactsBookInfoFile]; + + NSFileManager *fileManager = [[NSFileManager alloc] init]; + + if ([fileManager fileExistsAtPath:dataFilePath]) + { + // the file content could be corrupted + @try { + NSData* filecontent = [NSData dataWithContentsOfFile:dataFilePath options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:nil]; + + NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:filecontent]; + + lastSyncDate = [decoder decodeObjectForKey:@"lastSyncDate"]; + + [decoder finishDecoding]; + } @catch (NSException *exception) { + lastSyncDate = nil; + } + } +} + @end diff --git a/matrixConsole/Model/MXCContact.h b/matrixConsole/Model/MXCContact.h index c4251db5e..a0d7dd74e 100644 --- a/matrixConsole/Model/MXCContact.h +++ b/matrixConsole/Model/MXCContact.h @@ -24,7 +24,7 @@ extern NSString *const kMXCContactMatrixIdentifierUpdateNotification; // the contactID is provided in parameter extern NSString *const kMXCContactThumbnailUpdateNotification; -@interface MXCContact : NSObject +@interface MXCContact : NSObject // unique identifier @property (nonatomic, readonly) NSString * contactID; @@ -42,6 +42,9 @@ extern NSString *const kMXCContactThumbnailUpdateNotification; // array of matrix identifiers @property (nonatomic, readonly) NSArray* matrixIdentifiers; +// return the contact ID from native phonebook record ++ (NSString*)contactID:(ABRecordRef)record; + // create a contact from a local contact - (id)initWithABRecord:(ABRecordRef)record; @@ -57,4 +60,7 @@ extern NSString *const kMXCContactThumbnailUpdateNotification; // check if the patterns can match with this contact - (BOOL) matchedWithPatterns:(NSArray*)patterns; +// internationalize the contact phonenumbers +- (void)internationalizePhonenumbers:(NSString*)countryCode; + @end \ No newline at end of file diff --git a/matrixConsole/Model/MXCContact.m b/matrixConsole/Model/MXCContact.m index 07981cab3..99dbd849c 100644 --- a/matrixConsole/Model/MXCContact.m +++ b/matrixConsole/Model/MXCContact.m @@ -38,12 +38,16 @@ NSString *const kMXCContactThumbnailUpdateNotification = @"kMXCContactThumbnailU @implementation MXCContact ++ (NSString*)contactID:(ABRecordRef)record { + return [NSString stringWithFormat:@"%d", ABRecordGetRecordID(record)]; +} + - (id) initWithABRecord:(ABRecordRef)record { self = [super init]; if (self) { // compute a contact ID - _contactID = [NSString stringWithFormat:@"%d", ABRecordGetRecordID(record)]; + _contactID = [MXCContact contactID:record]; // use the contact book display name _displayName = (__bridge NSString*) ABRecordCopyCompositeName(record); @@ -238,6 +242,15 @@ NSString *const kMXCContactThumbnailUpdateNotification = @"kMXCContactThumbnailU return matched; } +// internationalize the contact phonenumbers +- (void)internationalizePhonenumbers:(NSString*)countryCode { + for(MXCPhoneNumber* phonenumber in _phoneNumbers) { + [phonenumber internationalize:countryCode]; + } +} + +#pragma mark - getter/setter + - (BOOL) isMatrixContact { return (nil != dummyField); } @@ -322,4 +335,35 @@ NSString *const kMXCContactThumbnailUpdateNotification = @"kMXCContactThumbnailU return [self thumbnailWithPreferedSize:CGSizeMake(256, 256)]; } +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder +{ + _contactID = [coder decodeObjectForKey:@"contactID"]; + _displayName = [coder decodeObjectForKey:@"displayName"]; + + _phoneNumbers = [coder decodeObjectForKey:@"phoneNumbers"]; + _emailAddresses = [coder decodeObjectForKey:@"emailAddresses"]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + + [coder encodeObject:_contactID forKey:@"contactID"]; + [coder encodeObject:_displayName forKey:@"displayName"]; + + if (_phoneNumbers) { + [coder encodeObject:_phoneNumbers forKey:@"phoneNumbers"]; + } else { + [coder setNilValueForKey:@"phoneNumbers"]; + } + + if (_emailAddresses) { + [coder encodeObject:_emailAddresses forKey:@"emailAddresses"]; + } else { + [coder setNilValueForKey:@"emailAddresses"]; + } +} + @end diff --git a/matrixConsole/Model/MXCContactField.h b/matrixConsole/Model/MXCContactField.h index 831d13e03..275815849 100644 --- a/matrixConsole/Model/MXCContactField.h +++ b/matrixConsole/Model/MXCContactField.h @@ -16,7 +16,7 @@ #import -@interface MXCContactField : NSObject +@interface MXCContactField : NSObject // contact ID where the email has been found @property (nonatomic, readonly) NSString *contactID; diff --git a/matrixConsole/Model/MXCContactField.m b/matrixConsole/Model/MXCContactField.m index b82f985ad..5085e52cc 100644 --- a/matrixConsole/Model/MXCContactField.m +++ b/matrixConsole/Model/MXCContactField.m @@ -33,7 +33,7 @@ @implementation MXCContactField -- (void) fieldInit { +- (void)initFields { // init members _contactID = nil; _matrixID = nil; @@ -44,7 +44,7 @@ self = [super init]; if (self) { - [self fieldInit]; + [self initFields]; _contactID = contactID; _matrixID = matrixID; } @@ -160,5 +160,22 @@ } } +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder +{ + if (self) { + [self initFields]; + _contactID = [coder decodeObjectForKey:@"contactID"]; + _matrixID = [coder decodeObjectForKey:@"matrixID"]; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:_contactID forKey:@"contactID"]; + [coder encodeObject:_matrixID forKey:@"matrixID"]; +} @end diff --git a/matrixConsole/Model/MXCEmail.m b/matrixConsole/Model/MXCEmail.m index 3ab90158f..a21adc7b0 100644 --- a/matrixConsole/Model/MXCEmail.m +++ b/matrixConsole/Model/MXCEmail.m @@ -69,5 +69,24 @@ return YES; } +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + _type = [coder decodeObjectForKey:@"type"]; + _emailAddress = [coder decodeObjectForKey:@"emailAddress"]; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + + [coder encodeObject:_type forKey:@"type"]; + [coder encodeObject:_emailAddress forKey:@"emailAddress"]; +} @end diff --git a/matrixConsole/Model/MXCPhoneNumber.h b/matrixConsole/Model/MXCPhoneNumber.h index b3bf0d6fc..c234fe9e7 100644 --- a/matrixConsole/Model/MXCPhoneNumber.h +++ b/matrixConsole/Model/MXCPhoneNumber.h @@ -22,9 +22,12 @@ // phonenumber info @property (nonatomic, readonly) NSString *type; @property (nonatomic, readonly) NSString *textNumber; +@property (nonatomic, readonly) NSString *internationalPhoneNumber; - (id)initWithTextNumber:(NSString*)textNumber type:(NSString*)aType contactID:(NSString*)aContactID matrixID:(NSString*)matrixID; +- (void)internationalize:(NSString*)countryCode; + - (BOOL)matchedWithPatterns:(NSArray*)patterns; @end \ No newline at end of file diff --git a/matrixConsole/Model/MXCPhoneNumber.m b/matrixConsole/Model/MXCPhoneNumber.m index d0bb00138..710101ecd 100644 --- a/matrixConsole/Model/MXCPhoneNumber.m +++ b/matrixConsole/Model/MXCPhoneNumber.m @@ -16,10 +16,8 @@ #import "MXCPhoneNumber.h" -@interface MXCPhoneNumber() { - // for search purpose - NSString* cleanedPhonenumber; -} +@interface MXCPhoneNumber () +@property (nonatomic, readonly) NSString *cleanedPhonenumber; @end @implementation MXCPhoneNumber @@ -28,9 +26,10 @@ self = [super initWithContactID:aContactID matrixID:matrixID]; if (self) { - _type = aType; - _textNumber = aTextNumber; - cleanedPhonenumber = nil; + _type = aType ? aType : @""; + _textNumber = aTextNumber ? aTextNumber : @"" ; + _cleanedPhonenumber = [MXCPhoneNumber cleanPhonenumber:_textNumber]; + _internationalPhoneNumber = _cleanedPhonenumber; } return self; @@ -68,13 +67,9 @@ return NO; } - if (!cleanedPhonenumber) { - cleanedPhonenumber = [MXCPhoneNumber cleanPhonenumber:_textNumber]; - } - if (patterns.count > 0) { for(NSString *pattern in patterns) { - if (([_textNumber rangeOfString:pattern].location == NSNotFound) && ([cleanedPhonenumber rangeOfString:pattern].location == NSNotFound)) { + if (([_textNumber rangeOfString:pattern].location == NSNotFound) && ([_cleanedPhonenumber rangeOfString:pattern].location == NSNotFound)) { return NO; } } @@ -83,4 +78,33 @@ return YES; } +- (void)internationalize:(NSString*)countryCode { + // need to plug to libphonenumber + _internationalPhoneNumber = _cleanedPhonenumber; +} + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)coder { + self = [super initWithCoder:coder]; + + if (self) { + _type = [coder decodeObjectForKey:@"type"]; + _textNumber = [coder decodeObjectForKey:@"textNumber"]; + _cleanedPhonenumber = [coder decodeObjectForKey:@"cleanedPhonenumber"]; + _internationalPhoneNumber = [coder decodeObjectForKey:@"internationalPhoneNumber"]; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + + [coder encodeObject:_type forKey:@"type"]; + [coder encodeObject:_textNumber forKey:@"textNumber"]; + [coder encodeObject:_cleanedPhonenumber forKey:@"cleanedPhonenumber"]; + [coder encodeObject:_internationalPhoneNumber forKey:@"internationalPhoneNumber"]; +} + @end \ No newline at end of file