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
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;

View file

@ -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

View file

@ -24,7 +24,7 @@ extern NSString *const kMXCContactMatrixIdentifierUpdateNotification;
// the contactID is provided in parameter
extern NSString *const kMXCContactThumbnailUpdateNotification;
@interface MXCContact : NSObject
@interface MXCContact : NSObject<NSCoding>
// 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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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