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
This commit is contained in:
ylecollen 2015-01-28 17:37:51 +01:00
parent 574c340d3e
commit 5371dcea35
9 changed files with 322 additions and 36 deletions

View file

@ -22,6 +22,9 @@
// warn when there is a contacts list refresh // warn when there is a contacts list refresh
extern NSString *const kContactManagerContactsListRefreshNotification; extern NSString *const kContactManagerContactsListRefreshNotification;
// the phonenumber has been internationalized
extern NSString *const kContactsDidInternationalizeNotification;
@interface ContactManager : NSObject { @interface ContactManager : NSObject {
dispatch_queue_t processingQueue; dispatch_queue_t processingQueue;
NSMutableDictionary* matrixIDBy3PID; NSMutableDictionary* matrixIDBy3PID;
@ -34,6 +37,9 @@ extern NSString *const kContactManagerContactsListRefreshNotification;
// delete contacts info // delete contacts info
- (void)reset; - (void)reset;
// refresh the international phonenumber of the contacts
- (void)internationalizePhoneNumbers:(NSString*)countryCode;
// refresh self.contacts // refresh self.contacts
- (void)fullRefresh; - (void)fullRefresh;

View file

@ -27,13 +27,16 @@
// warn when there is a contacts list refresh // warn when there is a contacts list refresh
NSString *const kContactManagerContactsListRefreshNotification = @"kContactManagerContactsListRefreshNotification"; NSString *const kContactManagerContactsListRefreshNotification = @"kContactManagerContactsListRefreshNotification";
// the phonenumber has been internationalized
NSString *const kContactsDidInternationalizeNotification = @"kContactsDidInternationalizeNotification";
// get the 3PIDS in one requests // get the 3PIDS in one requests
//#define CONTACTS_3PIDS_SYNC 1 //#define CONTACTS_3PIDS_SYNC 1
// else checks the matrix IDs for each displayed contact // else checks the matrix IDs for each displayed contact
@interface ContactManager() { @interface ContactManager() {
NSDate *lastSyncDate; NSDate *lastSyncDate;
NSMutableArray* deviceContactsList; NSMutableDictionary* deviceContactByContactID;
// //
NSMutableArray* pending3PIDs; NSMutableArray* pending3PIDs;
@ -93,10 +96,11 @@ static ContactManager* sharedContactManager = nil;
// delete contacts info // delete contacts info
- (void)reset { - (void)reset {
contacts = nil; contacts = nil;
lastSyncDate = nil; lastSyncDate = nil;
deviceContactsList = nil; deviceContactByContactID = nil;
matrixContactByMatrixUserID = nil; matrixContactByMatrixUserID = nil;
if (hasStatusObserver) { if (hasStatusObserver) {
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"]; [[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"];
@ -104,11 +108,24 @@ static ContactManager* sharedContactManager = nil;
} }
[self saveMatrixIDsDict]; [self saveMatrixIDsDict];
[self saveDeviceContacts];
[self saveContactBookInfo];
// warn of the contacts list update // warn of the contacts list update
[[NSNotificationCenter defaultCenter] postNotificationName:kContactManagerContactsListRefreshNotification object:nil userInfo:nil]; [[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 { - (void)fullRefresh {
// check if the user allowed to sync local contacts // check if the user allowed to sync local contacts
@ -162,7 +179,23 @@ static ContactManager* sharedContactManager = nil;
} }
dispatch_async(processingQueue, ^{ 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 // can list tocal contacts
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) { if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
@ -176,8 +209,28 @@ static ContactManager* sharedContactManager = nil;
CFIndex peopleCount = CFArrayGetCount(people); CFIndex peopleCount = CFArrayGetCount(people);
for (index = 0; index < peopleCount; index++) { for (index = 0; index < peopleCount; index++) {
contactRecord = (ABRecordRef)CFArrayGetValueAtIndex(people, 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); 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) { if (mxHandler.mxSession) {
[self manage3PIDS]; [self manage3PIDS];
} else { } else {
// display what you could have read // display what you could have read
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
contacts = deviceContactsList; contacts = deviceContacts;
hasStatusObserver = YES; hasStatusObserver = YES;
// wait that the mxSession is ready // wait that the mxSession is ready
@ -213,17 +280,10 @@ static ContactManager* sharedContactManager = nil;
dispatch_async(processingQueue, ^{ dispatch_async(processingQueue, ^{
NSMutableArray* tmpContacts = nil; NSMutableArray* tmpContacts = nil;
// initial sync // update with the known dict 3PID -> matrix ID
if (!lastSyncDate) { [self updateMatrixIDDeviceContacts];
// display the current device contacts
tmpContacts = deviceContactsList; tmpContacts = [[deviceContactByContactID allValues] mutableCopy];
} else {
// update with the known dict 3PID -> matrix ID
[self updateMatrixIDDeviceContactsList];
tmpContacts = [deviceContactsList mutableCopy];
}
lastSyncDate = [NSDate date];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
// stored self.contacts in the right thread // 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 // update the contacts info
for(MXCContact* contact in deviceContactsList) { for(MXCContact* contact in deviceContacts) {
[self updateContactMatrixIDs:contact]; [self updateContactMatrixIDs:contact];
} }
} }
@ -523,6 +586,8 @@ static ContactManager* sharedContactManager = nil;
#pragma mark - file caches #pragma mark - file caches
static NSString *matrixIDsDictFile = @"matrixIDsDict"; static NSString *matrixIDsDictFile = @"matrixIDsDict";
static NSString *localContactsFile = @"localContacts";
static NSString *contactsBookInfoFile = @"contacts";
- (void)saveMatrixIDsDict - (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 @end

View file

@ -24,7 +24,7 @@ extern NSString *const kMXCContactMatrixIdentifierUpdateNotification;
// the contactID is provided in parameter // the contactID is provided in parameter
extern NSString *const kMXCContactThumbnailUpdateNotification; extern NSString *const kMXCContactThumbnailUpdateNotification;
@interface MXCContact : NSObject @interface MXCContact : NSObject<NSCoding>
// unique identifier // unique identifier
@property (nonatomic, readonly) NSString * contactID; @property (nonatomic, readonly) NSString * contactID;
@ -42,6 +42,9 @@ extern NSString *const kMXCContactThumbnailUpdateNotification;
// array of matrix identifiers // array of matrix identifiers
@property (nonatomic, readonly) NSArray* matrixIdentifiers; @property (nonatomic, readonly) NSArray* matrixIdentifiers;
// return the contact ID from native phonebook record
+ (NSString*)contactID:(ABRecordRef)record;
// create a contact from a local contact // create a contact from a local contact
- (id)initWithABRecord:(ABRecordRef)record; - (id)initWithABRecord:(ABRecordRef)record;
@ -57,4 +60,7 @@ extern NSString *const kMXCContactThumbnailUpdateNotification;
// check if the patterns can match with this contact // check if the patterns can match with this contact
- (BOOL) matchedWithPatterns:(NSArray*)patterns; - (BOOL) matchedWithPatterns:(NSArray*)patterns;
// internationalize the contact phonenumbers
- (void)internationalizePhonenumbers:(NSString*)countryCode;
@end @end

View file

@ -38,12 +38,16 @@ NSString *const kMXCContactThumbnailUpdateNotification = @"kMXCContactThumbnailU
@implementation MXCContact @implementation MXCContact
+ (NSString*)contactID:(ABRecordRef)record {
return [NSString stringWithFormat:@"%d", ABRecordGetRecordID(record)];
}
- (id) initWithABRecord:(ABRecordRef)record { - (id) initWithABRecord:(ABRecordRef)record {
self = [super init]; self = [super init];
if (self) { if (self) {
// compute a contact ID // compute a contact ID
_contactID = [NSString stringWithFormat:@"%d", ABRecordGetRecordID(record)]; _contactID = [MXCContact contactID:record];
// use the contact book display name // use the contact book display name
_displayName = (__bridge NSString*) ABRecordCopyCompositeName(record); _displayName = (__bridge NSString*) ABRecordCopyCompositeName(record);
@ -238,6 +242,15 @@ NSString *const kMXCContactThumbnailUpdateNotification = @"kMXCContactThumbnailU
return matched; return matched;
} }
// internationalize the contact phonenumbers
- (void)internationalizePhonenumbers:(NSString*)countryCode {
for(MXCPhoneNumber* phonenumber in _phoneNumbers) {
[phonenumber internationalize:countryCode];
}
}
#pragma mark - getter/setter
- (BOOL) isMatrixContact { - (BOOL) isMatrixContact {
return (nil != dummyField); return (nil != dummyField);
} }
@ -322,4 +335,35 @@ NSString *const kMXCContactThumbnailUpdateNotification = @"kMXCContactThumbnailU
return [self thumbnailWithPreferedSize:CGSizeMake(256, 256)]; 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 @end

View file

@ -16,7 +16,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface MXCContactField : NSObject @interface MXCContactField : NSObject<NSCoding>
// contact ID where the email has been found // contact ID where the email has been found
@property (nonatomic, readonly) NSString *contactID; @property (nonatomic, readonly) NSString *contactID;

View file

@ -33,7 +33,7 @@
@implementation MXCContactField @implementation MXCContactField
- (void) fieldInit { - (void)initFields {
// init members // init members
_contactID = nil; _contactID = nil;
_matrixID = nil; _matrixID = nil;
@ -44,7 +44,7 @@
self = [super init]; self = [super init];
if (self) { if (self) {
[self fieldInit]; [self initFields];
_contactID = contactID; _contactID = contactID;
_matrixID = matrixID; _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 @end

View file

@ -69,5 +69,24 @@
return YES; 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 @end

View file

@ -22,9 +22,12 @@
// phonenumber info // phonenumber info
@property (nonatomic, readonly) NSString *type; @property (nonatomic, readonly) NSString *type;
@property (nonatomic, readonly) NSString *textNumber; @property (nonatomic, readonly) NSString *textNumber;
@property (nonatomic, readonly) NSString *internationalPhoneNumber;
- (id)initWithTextNumber:(NSString*)textNumber type:(NSString*)aType contactID:(NSString*)aContactID matrixID:(NSString*)matrixID; - (id)initWithTextNumber:(NSString*)textNumber type:(NSString*)aType contactID:(NSString*)aContactID matrixID:(NSString*)matrixID;
- (void)internationalize:(NSString*)countryCode;
- (BOOL)matchedWithPatterns:(NSArray*)patterns; - (BOOL)matchedWithPatterns:(NSArray*)patterns;
@end @end

View file

@ -16,10 +16,8 @@
#import "MXCPhoneNumber.h" #import "MXCPhoneNumber.h"
@interface MXCPhoneNumber() { @interface MXCPhoneNumber ()
// for search purpose @property (nonatomic, readonly) NSString *cleanedPhonenumber;
NSString* cleanedPhonenumber;
}
@end @end
@implementation MXCPhoneNumber @implementation MXCPhoneNumber
@ -28,9 +26,10 @@
self = [super initWithContactID:aContactID matrixID:matrixID]; self = [super initWithContactID:aContactID matrixID:matrixID];
if (self) { if (self) {
_type = aType; _type = aType ? aType : @"";
_textNumber = aTextNumber; _textNumber = aTextNumber ? aTextNumber : @"" ;
cleanedPhonenumber = nil; _cleanedPhonenumber = [MXCPhoneNumber cleanPhonenumber:_textNumber];
_internationalPhoneNumber = _cleanedPhonenumber;
} }
return self; return self;
@ -68,13 +67,9 @@
return NO; return NO;
} }
if (!cleanedPhonenumber) {
cleanedPhonenumber = [MXCPhoneNumber cleanPhonenumber:_textNumber];
}
if (patterns.count > 0) { if (patterns.count > 0) {
for(NSString *pattern in patterns) { 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; return NO;
} }
} }
@ -83,4 +78,33 @@
return YES; 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 @end