Moved code under a subdirectory: Console

This commit is contained in:
manuroe
2015-04-03 09:04:22 +02:00
parent 1b24cdb7f1
commit 2b0350d14f
160 changed files with 0 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <UIKit/UIKit.h>
extern NSString *const kAPNSHandlerHasBeenUpdated;
@interface APNSHandler : NSObject {
BOOL transientActivity;
}
@property (nonatomic, copy) NSData *deviceToken;
@property (nonatomic) BOOL isAvailable; // true when app is registered for remote notif, and devive token is known
@property (nonatomic) BOOL isActive; // true when APNS is turned on (locally available and synced with server)
+ (APNSHandler *)sharedHandler;
- (void)reset;
@end
+143
View File
@@ -0,0 +1,143 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "APNSHandler.h"
#import "AppDelegate.h"
#import "MatrixSDKHandler.h"
NSString *const kAPNSHandlerHasBeenUpdated = @"kAPNSHandlerHasBeenUpdated";
static APNSHandler *sharedHandler = nil;
@implementation APNSHandler
+ (APNSHandler *)sharedHandler {
@synchronized(self) {
if(sharedHandler == nil) {
sharedHandler = [[super allocWithZone:NULL] init];
}
}
return sharedHandler;
}
#pragma mark - reset
- (void)reset {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsIsActive"];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
#pragma mark -
- (NSData *)deviceToken {
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"apnsDeviceToken"];
if (!token.length) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
token = nil;
}
return token;
}
- (void)setDeviceToken:(NSData *)deviceToken {
NSData *oldToken = self.deviceToken;
if (!deviceToken.length) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
} else {
[[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:@"apnsDeviceToken"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (!oldToken) {
// turn on the Apns flag, when the Apns registration succeeds for the first time
self.isActive = YES;
} else if (self.isActive && ![oldToken isEqualToData:deviceToken]) {
// Resync APNS to on if we think APNS is on, but the token has changed
self.isActive = YES;
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:kAPNSHandlerHasBeenUpdated object:nil];
}
- (BOOL)isAvailable {
BOOL isRegisteredForRemoteNotifications = NO;
if ([[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {
// iOS 8 and later
isRegisteredForRemoteNotifications = [[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
} else {
isRegisteredForRemoteNotifications = [[UIApplication sharedApplication] enabledRemoteNotificationTypes] != UIRemoteNotificationTypeNone;
}
return (isRegisteredForRemoteNotifications && self.deviceToken);
}
- (BOOL)isActive {
return ([self isAvailable] && [[NSUserDefaults standardUserDefaults] boolForKey:@"apnsIsActive"]);
}
- (void)setIsActive:(BOOL)isActive {
// Refuse to try & turn push on if we're not logged in, it's nonsensical.
if ([MatrixSDKHandler sharedHandler].status == MatrixSDKHandlerStatusLoggedOut) {
NSLog(@"[APNSHandler] Not setting push token because we're not logged in");
return;
}
transientActivity = isActive;
#ifdef DEBUG
NSString *appId = @"org.matrix.console.ios.dev";
#else
NSString *appId = @"org.matrix.console.ios.prod";
#endif
NSString *b64Token = [self.deviceToken base64EncodedStringWithOptions:0];
NSDictionary *pushData = @{
@"url": @"https://matrix.org/_matrix/push/v1/notify",
};
NSString *deviceLang = [NSLocale preferredLanguages][0];
NSString * profileTag = [[NSUserDefaults standardUserDefaults] valueForKey:@"pusherProfileTag"];
if (!profileTag) {
profileTag = @"";
NSString *alphabet = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (int i = 0; i < 16; ++i) {
unsigned char c = [alphabet characterAtIndex:arc4random() % alphabet.length];
profileTag = [profileTag stringByAppendingFormat:@"%c", c];
}
NSLog(@"[APNSHandler] Generated fresh profile tag: %@", profileTag);
[[NSUserDefaults standardUserDefaults] setValue:profileTag forKey:@"pusherProfileTag"];
} else {
NSLog(@"[APNSHandler] Using existing profile tag: %@", profileTag);
}
NSObject *kind = isActive ? @"http" : [NSNull null];
MXRestClient *restCli = [MatrixSDKHandler sharedHandler].mxRestClient;
[restCli setPusherWithPushkey:b64Token kind:kind appId:appId appDisplayName:@"Matrix Console iOS" deviceDisplayName:[[UIDevice currentDevice] name] profileTag:profileTag lang:deviceLang data:pushData success:^{
[[NSUserDefaults standardUserDefaults] setBool:transientActivity forKey:@"apnsIsActive"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:kAPNSHandlerHasBeenUpdated object:nil];
} failure:^(NSError *error) {
NSLog(@"[APNSHandler] Failed to send APNS token! (%@)", error);
[[NSNotificationCenter defaultCenter] postNotificationName:kAPNSHandlerHasBeenUpdated object:nil];
}];
}
@end
@@ -0,0 +1,52 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "SectionedContacts.h"
#import "MXCContact.h"
// 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;
}
+ (id)sharedManager;
@property (nonatomic, readonly) NSMutableArray *contacts;
// delete contacts info
- (void)reset;
// refresh the international phonenumber of the contacts
- (void)internationalizePhoneNumbers:(NSString*)countryCode;
// refresh self.contacts
- (void)fullRefresh;
// refresh matrix IDs
- (void)refreshContactMatrixIDs:(MXCContact*)contact;
// sort the contacts in sectioned arrays to be displayable in a UITableview
- (SectionedContacts *)getSectionedContacts:(NSArray*)contactsList;
@end
+769
View File
@@ -0,0 +1,769 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "ContactManager.h"
#import "MXCContact.h"
#import "MXCPhoneNumber.h"
#import "MXCEmail.h"
#import "MatrixSDKHandler.h"
#import "AppSettings.h"
// 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;
NSMutableDictionary* deviceContactByContactID;
//
NSMutableArray* pending3PIDs;
NSMutableArray* checked3PIDs;
NSMutableDictionary* matrixContactByMatrixUserID;
BOOL hasStatusObserver;
}
@end
@implementation ContactManager
@synthesize contacts;
#pragma mark Singleton Methods
static ContactManager* sharedContactManager = nil;
+ (id)sharedManager {
@synchronized(self) {
if(sharedContactManager == nil)
sharedContactManager = [[self alloc] init];
}
return sharedContactManager;
}
#pragma mark -
-(ContactManager *)init {
if (self = [super init]) {
NSString *label = [NSString stringWithFormat:@"ConsoleMatrix.%@.Contacts", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]];
processingQueue = dispatch_queue_create([label UTF8String], NULL);
// put an empty array instead of nil
contacts = [[NSMutableArray alloc] init];
// other inits
matrixContactByMatrixUserID = [[NSMutableDictionary alloc] init];
// save the last sync date
// to avoid resync the whole phonebook
lastSyncDate = nil;
// wait that the mxSession is ready
[[AppSettings sharedSettings] addObserver:self forKeyPath:@"syncLocalContacts" options:0 context:nil];
}
return self;
}
-(void)dealloc {
if (hasStatusObserver) {
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"];
[[AppSettings sharedSettings] removeObserver:self forKeyPath:@"syncLocalContacts"];
}
}
// delete contacts info
- (void)reset {
contacts = nil;
lastSyncDate = nil;
deviceContactByContactID = nil;
matrixContactByMatrixUserID = nil;
if (hasStatusObserver) {
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"];
hasStatusObserver = NO;
}
[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 {
dispatch_async(processingQueue, ^{
NSArray* contactsSnapshot = [deviceContactByContactID allValues];
for(MXCContact* contact in contactsSnapshot) {
[contact internationalizePhonenumbers:countryCode];
}
[self saveDeviceContacts];
[[NSNotificationCenter defaultCenter] postNotificationName:kContactsDidInternationalizeNotification object:nil userInfo:nil];
});
}
- (void)fullRefresh {
// check if the user allowed to sync local contacts
if (![[AppSettings sharedSettings] syncLocalContacts]) {
contacts = nil;
// if the user did not allow to sync local contacts
// ignore this sync
// at least, display the known contacts
[[NSNotificationCenter defaultCenter] postNotificationName:kContactManagerContactsListRefreshNotification object:nil userInfo:nil];
return;
}
// check if the application is allowed to list the contacts
ABAuthorizationStatus cbStatus = ABAddressBookGetAuthorizationStatus();
//
hasStatusObserver = NO;
// did not yet request the access
if (cbStatus == kABAuthorizationStatusNotDetermined) {
// request address book access
ABAddressBookRef ab = ABAddressBookCreateWithOptions(nil, nil);
if (ab) {
ABAddressBookRequestAccessWithCompletion(ab, ^(bool granted, CFErrorRef error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self fullRefresh];
});
});
CFRelease(ab);
}
return;
}
MatrixSDKHandler* mxHandler = [MatrixSDKHandler sharedHandler];
// remove any observer
if (hasStatusObserver) {
[mxHandler removeObserver:self forKeyPath:@"status"];
hasStatusObserver = NO;
}
pending3PIDs = [[NSMutableArray alloc] init];
checked3PIDs = [[NSMutableArray alloc] init];
// cold start
// launch the dict from the file system
// It is cached to improve UX.
if (!matrixIDBy3PID) {
[self loadMatrixIDsDict];
}
dispatch_async(processingQueue, ^{
// 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) {
NSString* countryCode = [[AppSettings sharedSettings] countryCode];
ABAddressBookRef ab = ABAddressBookCreateWithOptions(nil, nil);
ABRecordRef contactRecord;
CFIndex index;
CFMutableArrayRef people = (CFMutableArrayRef)ABAddressBookCopyArrayOfAllPeople(ab);
if (nil != people) {
CFIndex peopleCount = CFArrayGetCount(people);
for (index = 0; index < peopleCount; index++) {
contactRecord = (ABRecordRef)CFArrayGetValueAtIndex(people, index);
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;
MXCContact* contact = [[MXCContact alloc] initWithABRecord:contactRecord];
if (countryCode) {
[contact internationalizePhonenumbers:countryCode];
}
// update the contact
[deviceContactByContactID setValue:contact forKey:contactID];;
}
CFRelease(people);
}
if (ab) {
CFRelease(ab);
}
}
// 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 = deviceContacts;
hasStatusObserver = YES;
// wait that the mxSession is ready
[mxHandler addObserver:self forKeyPath:@"status" options:0 context:nil];
// at least, display the known contacts
[[NSNotificationCenter defaultCenter] postNotificationName:kContactManagerContactsListRefreshNotification object:nil userInfo:nil];
});
}
});
}
// the local contacts are listed
// update their 3PIDs and their update
- (void) manage3PIDS {
dispatch_async(processingQueue, ^{
NSMutableArray* tmpContacts = nil;
// 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
contacts = tmpContacts;
#if CONTACTS_3PIDS_SYNC
// refresh the 3PIDS -> matrix IDs
[self refreshMatrixIDs];
#else
// nop
// wait that refreshContactMatrixIDs is called
#endif
// at least, display the known contacts
[[NSNotificationCenter defaultCenter] postNotificationName:kContactManagerContactsListRefreshNotification object:nil userInfo:nil];
});
});
}
- (void) updateContactMatrixIDs:(MXCContact*) contact {
// the phonenumbers wil be managed later
/*for(ConsolePhoneNumber* pn in contact.phoneNumbers) {
if (pn.textNumber.length > 0) {
// not yet added
if ([pids indexOfObject:pn.textNumber] == NSNotFound) {
[pids addObject:pn.textNumber];
[medias addObject:@"msisdn"];
}
}
}*/
for(MXCEmail* email in contact.emailAddresses) {
if (email.emailAddress.length > 0) {
id matrixID = [matrixIDBy3PID valueForKey:email.emailAddress];
if ([matrixID isKindOfClass:[NSString class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
[email setMatrixID:matrixID];
});
}
}
}
}
- (void) updateMatrixIDDeviceContacts {
NSArray* deviceContacts = [deviceContactByContactID allValues];
// update the contacts info
for(MXCContact* contact in deviceContacts) {
[self updateContactMatrixIDs:contact];
}
}
#ifdef CONTACTS_3PIDS_SYNC
// refresh the 3PIDs -> Matrix ID list
// update the contact is required
- (void)refreshMatrixIDs {
// build the request parameters
NSMutableArray* pids = [[NSMutableArray alloc] init];
NSMutableArray* medias = [[NSMutableArray alloc] init];
for(MXCContact* contact in deviceContactsList) {
// the phonenumbers are not managed
/*for(ConsolePhoneNumber* pn in contact.phoneNumbers) {
if (pn.textNumber.length > 0) {
// not yet added
if ([pids indexOfObject:pn.textNumber] == NSNotFound) {
[pids addObject:pn.textNumber];
[medias addObject:@"msisdn"];
}
}
}*/
for(MXCEmail* email in contact.emailAddresses) {
if (email.emailAddress.length > 0) {
// not yet added
if ([pids indexOfObject:email.emailAddress] == NSNotFound) {
[pids addObject:email.emailAddress];
[medias addObject:@"email"];
}
}
}
}
// get some pids
if (pids.count > 0) {
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
if (mxHandler.mxRestClient) {
[mxHandler.mxRestClient lookup3pids:pids
forMedia:medias
success:^(NSArray *userIds) {
// sanity check
if (userIds.count == pids.count) {
matrixIDBy3PID = [[NSMutableDictionary alloc] initWithObjects:userIds forKeys:pids];
[self saveMatrixIDsDict];
[self updateMatrixIDDeviceContactsList];
// add the MX users
NSMutableArray* tmpContacts = [deviceContactsList mutableCopy];
[self mergeMXUsers:tmpContacts];
dispatch_async(dispatch_get_main_queue(), ^{
contacts = tmpContacts;
[[NSNotificationCenter defaultCenter] postNotificationName:kContactManagerContactsListRefreshNotification object:nil userInfo:nil];
});
}
}
failure:^(NSError *error) {
// try later
dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self refreshMatrixIDs];
});
}
];
}
}
}
#endif
// refresh matrix IDs
- (void)refreshContactMatrixIDs:(MXCContact*)contact {
#ifndef CONTACTS_3PIDS_SYNC
if (!contact.isMatrixContact) {
// check pending requests
NSMutableArray* pids = [[NSMutableArray alloc] init];
NSMutableArray* medias = [[NSMutableArray alloc] init];
for(MXCEmail* email in contact.emailAddresses) {
if (([pending3PIDs indexOfObject:email.emailAddress] == NSNotFound) && ([checked3PIDs indexOfObject:email.emailAddress] == NSNotFound)) {
[pids addObject:email.emailAddress];
[medias addObject:@"email"];
}
}
if (pids.count > 0) {
[pending3PIDs addObjectsFromArray:pids];
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
if (mxHandler) {
[mxHandler.mxRestClient lookup3pids:pids
forMedia:medias
success:^(NSArray *userIds) {
// sanity check
if (userIds.count == pids.count) {
// update statuses table
[checked3PIDs addObjectsFromArray:pids];
for(NSString* pid in pids) {
[pending3PIDs removeObject:pid];
}
BOOL isUpdated = NO;
NSMutableArray* matrixContactsToRemove = [[NSMutableArray alloc] init];
// apply updates
if (pids.count > 0) {
for(int index = 0; index < pids.count; index++) {
NSString* matrixID = [userIds objectAtIndex:index];
NSString* pid = [pids objectAtIndex:index];
// the dict is created on demand
if (!matrixIDBy3PID) {
[self loadMatrixIDsDict];
}
id currentMatrixID = [matrixIDBy3PID valueForKey:pid];
// do not keep useless info
if ([matrixID isKindOfClass:[NSString class]]) {
// do not update if not required
if (![currentMatrixID isKindOfClass:[NSString class]] || ![(NSString*)currentMatrixID isEqualToString:matrixID]) {
[matrixIDBy3PID setValue:matrixID forKey:pid];
isUpdated = YES;
}
} else {
if (currentMatrixID) {
[matrixIDBy3PID removeObjectForKey:pid];
isUpdated = YES;
}
}
// is there a matrix contact with the same
if ([matrixContactByMatrixUserID objectForKey:matrixID]) {
[matrixContactsToRemove addObject:[matrixContactByMatrixUserID objectForKey:matrixID]];
}
}
if (isUpdated) {
[self saveMatrixIDsDict];
}
}
// some matrix contacts will be replaced by this contact
if (matrixContactsToRemove.count > 0) {
[self updateContactMatrixIDs:contact];
for(MXCContact* contactToRemove in matrixContactsToRemove) {
[self.contacts removeObject:contactToRemove];
}
// warn there is a global refresh
[[NSNotificationCenter defaultCenter] postNotificationName:kContactManagerContactsListRefreshNotification object:nil userInfo:nil];
} else {
// update only this contact
[self updateContactMatrixIDs:contact];
}
}
}
failure:^(NSError *error) {
// try later
dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self refreshContactMatrixIDs:contact];
});
}];
}
else {
dispatch_after(dispatch_walltime(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self refreshContactMatrixIDs:contact];
});
}
}
}
#endif
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([@"status" isEqualToString:keyPath]) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([MatrixSDKHandler sharedHandler].status == MatrixSDKHandlerStatusServerSyncDone) {
if (hasStatusObserver) {
[[MatrixSDKHandler sharedHandler] removeObserver:self forKeyPath:@"status"];
hasStatusObserver = NO;
}
[self manage3PIDS];
}
});
} else if ([@"syncLocalContacts" isEqualToString:keyPath]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self fullRefresh];
});
}
}
- (SectionedContacts *)getSectionedContacts:(NSArray*)contactsList {
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
int indexOffset = 0;
NSInteger index, sectionTitlesCount = [[collation sectionTitles] count];
NSMutableArray *tmpSectionsArray = [[NSMutableArray alloc] initWithCapacity:(sectionTitlesCount)];
sectionTitlesCount += indexOffset;
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[tmpSectionsArray addObject:array];
}
int contactsCount = 0;
for (MXCContact *aContact in contactsList)
{
NSInteger section = [collation sectionForObject:aContact collationStringSelector:@selector(displayName)] + indexOffset;
[[tmpSectionsArray objectAtIndex:section] addObject:aContact];
++contactsCount;
}
NSMutableArray *tmpSectionedContactsTitle = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
NSMutableArray *shortSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
for (index = indexOffset; index < sectionTitlesCount; index++) {
NSMutableArray *usersArrayForSection = [tmpSectionsArray objectAtIndex:index];
if ([usersArrayForSection count] != 0) {
NSArray* sortedUsersArrayForSection = [collation sortedArrayFromArray:usersArrayForSection collationStringSelector:@selector(displayName)];
[shortSectionsArray addObject:sortedUsersArrayForSection];
[tmpSectionedContactsTitle addObject:[[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:(index - indexOffset)]];
}
}
return [[SectionedContacts alloc] initWithContacts:shortSectionsArray andTitles:tmpSectionedContactsTitle andCount:contactsCount];
}
#pragma mark - file caches
static NSString *matrixIDsDictFile = @"matrixIDsDict";
static NSString *localContactsFile = @"localContacts";
static NSString *contactsBookInfoFile = @"contacts";
- (void)saveMatrixIDsDict
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dataFilePath = [documentsDirectory stringByAppendingPathComponent:matrixIDsDictFile];
if (matrixIDBy3PID)
{
NSMutableData *theData = [NSMutableData data];
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData];
[encoder encodeObject:matrixIDBy3PID forKey:@"matrixIDsDict"];
[encoder finishEncoding];
[theData writeToFile:dataFilePath atomically:YES];
}
else
{
NSFileManager *fileManager = [[NSFileManager alloc] init];
[fileManager removeItemAtPath:dataFilePath error:nil];
}
}
- (void)loadMatrixIDsDict
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dataFilePath = [documentsDirectory stringByAppendingPathComponent:matrixIDsDictFile];
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:@"matrixIDsDict"];
if ([object isKindOfClass:[NSDictionary class]]) {
matrixIDBy3PID = [object mutableCopy];
}
[decoder finishDecoding];
} @catch (NSException *exception) {
}
}
if (!matrixIDBy3PID) {
matrixIDBy3PID = [[NSMutableDictionary alloc] init];
}
}
- (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
+40
View File
@@ -0,0 +1,40 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <UIKit/UIKit.h>
@interface MXCTools : NSObject
// Time interval
+ (NSString*)formatSecondsInterval:(CGFloat)secondsInterval;
#pragma mark - File
+ (long long)folderSize:(NSString *)folderPath;
// return the list of files by name
// isTimeSorted : the files are sorted by creation date from the oldest to the most recent one
// largeFilesFirst: move the largest file to the list head (large > 100KB). It can be combined isTimeSorted
+ (NSArray*)listFiles:(NSString *)folderPath timeSorted:(BOOL)isTimeSorted largeFilesFirst:(BOOL)largeFilesFirst;
// return the file extension from a contentType
+ (NSString*)fileExtensionFromContentType:(NSString*)contentType;
#pragma mark - Image
+ (UIImage*)forceImageOrientationUp:(UIImage*)imageSrc;
+ (UIImage *)resize:(UIImage *)image toFitInSize:(CGSize)size;
+ (UIImageOrientation)imageOrientationForRotationAngleInDegree:(NSInteger)angle;
@end
+246
View File
@@ -0,0 +1,246 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "MXCTools.h"
#import <MobileCoreServices/MobileCoreServices.h>
@implementation MXCTools
#pragma mark - Time interval
+ (NSString*)formatSecondsInterval:(CGFloat)secondsInterval {
NSMutableString* formattedString = [[NSMutableString alloc] init];
if (secondsInterval < 1) {
[formattedString appendString:@"< 1s"];
} else if (secondsInterval < 60)
{
[formattedString appendFormat:@"%ds", (int)secondsInterval];
}
else if (secondsInterval < 3600)
{
[formattedString appendFormat:@"%dm %2ds", (int)(secondsInterval/60), ((int)secondsInterval) % 60];
}
else if (secondsInterval >= 3600)
{
[formattedString appendFormat:@"%dh %dm %ds", (int)(secondsInterval / 3600),
((int)(secondsInterval) % 3600) / 60,
(int)(secondsInterval) % 60];
}
[formattedString appendString:@" left"];
return formattedString;
}
#pragma mark - File
// return an array of files attributes
+ (NSArray*) listAttributesFiles:(NSString *)folderPath {
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:nil];
NSEnumerator *contentsEnumurator = [contents objectEnumerator];
NSString *file;
NSMutableArray* res = [[NSMutableArray alloc] init];
while (file = [contentsEnumurator nextObject])
{
NSString* itemPath = [folderPath stringByAppendingPathComponent:file];
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:nil];
// is directory
if ([[fileAttributes objectForKey:NSFileType] isEqual:NSFileTypeDirectory])
{
[res addObjectsFromArray:[MXCTools listAttributesFiles:itemPath]];
}
else
{
NSMutableDictionary* att = [fileAttributes mutableCopy];
// add the file path
[att setObject:itemPath forKey:@"NSFilePath"];
[res addObject:att];
}
}
return res;
}
// recursive method to compute the folder content size
+ (long long)folderSize:(NSString *)folderPath {
long long folderSize = 0;
NSArray *fileAtts = [MXCTools listAttributesFiles:folderPath];
for(NSDictionary *fileAtt in fileAtts) {
folderSize += [[fileAtt objectForKey:NSFileSize] intValue];
}
return folderSize;
}
// return the list of files by name
// isTimeSorted : the files are sorted by creation date from the oldest to the most recent one
// largeFilesFirst: move the largest file to the list head (large > 100KB). It can be combined isTimeSorted
+ (NSArray*)listFiles:(NSString *)folderPath timeSorted:(BOOL)isTimeSorted largeFilesFirst:(BOOL)largeFilesFirst {
NSArray* attFilesList = [MXCTools listAttributesFiles:folderPath];
if (attFilesList.count > 0) {
// sorted by timestamp (oldest first)
if (isTimeSorted) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"NSFileCreationDate" ascending:YES selector:@selector(compare:)];
attFilesList = [attFilesList sortedArrayUsingDescriptors:@[ sortDescriptor]];
}
// list the large files first
if (largeFilesFirst) {
NSMutableArray* largeFilesAttList = [[NSMutableArray alloc] init];
NSMutableArray* smallFilesAttList = [[NSMutableArray alloc] init];
for (NSDictionary* att in attFilesList) {
if ([[att objectForKey:NSFileSize] intValue] > 100 * 1024) {
[largeFilesAttList addObject:att];
} else {
[smallFilesAttList addObject:att];
}
}
NSMutableArray* mergedList = [[NSMutableArray alloc] init];
[mergedList addObjectsFromArray:largeFilesAttList];
[mergedList addObjectsFromArray:smallFilesAttList];
attFilesList = mergedList;
}
// list filenames
NSMutableArray* res = [[NSMutableArray alloc] init];
for (NSDictionary* att in attFilesList) {
[res addObject:[att valueForKey:@"NSFilePath"]];
}
return res;
} else {
return nil;
}
}
// return the file extension from a contentType
+ (NSString*) fileExtensionFromContentType:(NSString*)contentType {
if (!contentType) {
return @"";
}
CFStringRef mimeType = (__bridge CFStringRef)contentType;
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
NSString* extension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
CFRelease(uti);
if (extension) {
return [NSString stringWithFormat:@".%@", extension];
}
// else undefined type
if ([contentType isEqualToString:@"application/jpeg"]) {
return @".jpg";
} else if ([contentType isEqualToString:@"audio/x-alaw-basic"]) {
return @".alaw";
} else if ([contentType isEqualToString:@"audio/x-caf"]) {
return @".caf";
} else if ([contentType isEqualToString:@"audio/aac"]) {
return @".aac";
}
return @"";
}
#pragma mark - Image
+ (UIImage*)forceImageOrientationUp:(UIImage*)imageSrc {
if ((imageSrc.imageOrientation == UIImageOrientationUp) || (!imageSrc)) {
// Nothing to do
return imageSrc;
}
// Draw the entire image in a graphics context, respecting the images orientation setting
UIGraphicsBeginImageContext(imageSrc.size);
[imageSrc drawAtPoint:CGPointMake(0, 0)];
UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return retImage;
}
+ (UIImage *)resize:(UIImage *)image toFitInSize:(CGSize)size {
UIImage *resizedImage = image;
// Check whether resize is required
if (size.width && size.height) {
CGFloat width = image.size.width;
CGFloat height = image.size.height;
if (width > size.width) {
height = (height * size.width) / width;
height = floorf(height / 2) * 2;
width = size.width;
}
if (height > size.height) {
width = (width * size.height) / height;
width = floorf(width / 2) * 2;
height = size.height;
}
if (width != image.size.width || height != image.size.height) {
// Create the thumbnail
CGSize imageSize = CGSizeMake(width, height);
UIGraphicsBeginImageContext(imageSize);
// // set to the top quality
// CGContextRef context = UIGraphicsGetCurrentContext();
// CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGRect thumbnailRect = CGRectMake(0, 0, 0, 0);
thumbnailRect.origin = CGPointMake(0.0,0.0);
thumbnailRect.size.width = imageSize.width;
thumbnailRect.size.height = imageSize.height;
[image drawInRect:thumbnailRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}
return resizedImage;
}
+ (UIImageOrientation)imageOrientationForRotationAngleInDegree:(NSInteger)angle {
NSInteger modAngle = angle % 360;
UIImageOrientation orientation = UIImageOrientationUp;
if (45 <= modAngle && modAngle < 135) {
return UIImageOrientationRight;
} else if (135 <= modAngle && modAngle < 225) {
return UIImageOrientationDown;
} else if (225 <= modAngle && modAngle < 315) {
return UIImageOrientationLeft;
}
return orientation;
}
@end
@@ -0,0 +1,113 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <MatrixSDK/MatrixSDK.h>
extern NSString *const kMatrixSDKHandlerUnsupportedEventDescriptionPrefix;
typedef enum : NSUInteger {
MatrixSDKHandlerStatusLoggedOut = 0,
MatrixSDKHandlerStatusLogged,
MatrixSDKHandlerStatusStoreDataReady,
MatrixSDKHandlerStatusInitialServerSyncInProgress,
MatrixSDKHandlerStatusServerSyncDone,
MatrixSDKHandlerStatusPaused
} MatrixSDKHandlerStatus;
@interface MatrixSDKHandler : NSObject
@property (strong, nonatomic) dispatch_queue_t processingQueue;
@property (strong, nonatomic) MXRestClient *mxRestClient;
@property (strong, nonatomic) MXSession *mxSession;
@property (strong, nonatomic) NSString *homeServerURL;
@property (strong, nonatomic) NSString *homeServer;
@property (strong, nonatomic) NSString *userLogin;
@property (strong, nonatomic) NSString *userId;
@property (strong, nonatomic, readonly) NSString *localPartFromUserId;
@property (strong, nonatomic) NSString *accessToken;
@property (strong, nonatomic) NSString *identityServerURL;
// The type of events to display
@property (strong, nonatomic) NSArray *eventsFilterForMessages;
// Matrix user's settings
@property (nonatomic) MXPresence userPresence;
@property (nonatomic,readonly) MatrixSDKHandlerStatus status;
@property (nonatomic,readonly) BOOL isActivityInProgress;
// return the MX cache size in bytes
@property (nonatomic,readonly) NSUInteger MXCacheSize;
// return the sum of the caches (MX cache + media cache ...) in bytes
@property (nonatomic,readonly) NSUInteger cachesSize;
// defines the min allow cache size in bytes
@property (nonatomic,readonly) NSUInteger minCachesSize;
// defines the current max caches size in bytes
@property (nonatomic,readwrite) NSUInteger currentMaxCachesSize;
// defines the max allowed caches size in bytes
@property (nonatomic,readonly) NSUInteger maxAllowedCachesSize;
+ (MatrixSDKHandler *)sharedHandler;
- (void)pauseInBackgroundTask;
- (void)resume;
- (void)logout;
// Flush and restore Matrix data
- (void)reload:(BOOL)clearCache;
- (void)enableInAppNotifications:(BOOL)isEnabled;
// return a userIds list of 1:1 room members
- (NSArray*)oneToOneRoomMemberIDs;
// Searches if a private OneToOne room has been started with this user
// Returns the room ID (nil if not found)
- (NSString*)privateOneToOneRoomIdWithUserId:(NSString*)userId;
// Reopens an existing private OneToOne room with this userId or creates a new one (if it doesn't exist)
- (void)startPrivateOneToOneRoomWithUserId:(NSString*)userId;
// Enables inApp notifications for a dedicated room if they were disabled
- (void)restoreInAppNotificationsForRoomId:(NSString*)roomID;
// Stores the current text message partially typed in text input before leaving a room (use nil to reset the current value)
- (void)storePartialTextMessage:(NSString*)textMessage forRoomId:(NSString*)roomId;
// Returns the current partial message stored for this room (nil if none)
- (NSString*)partialTextMessageForRoomId:(NSString*)roomId;
// user power level in a dedicated room
- (CGFloat)getPowerLevel:(MXRoomMember *)roomMember inRoom:(MXRoom *)room;
- (BOOL)isSupportedAttachment:(MXEvent*)event;
- (BOOL)isEmote:(MXEvent*)event;
// Note: the room state expected by the 3 following methods is the room state right before handling the event
- (NSString*)senderDisplayNameForEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState;
- (NSString*)senderAvatarUrlForEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState;
- (NSString*)displayTextForEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState inSubtitleMode:(BOOL)isSubtitle;
// return the presence ring color
// nil means there is no ring to display
- (UIColor*)getPresenceRingColor:(MXPresence)presence;
// return YES if the text contains a bing word
- (BOOL)containsBingWord:(NSString*)text;
// Return the suitable url to display the content thumbnail into the provided view size
// Note: the provided view size is supposed in points, this method will convert this size in pixels by considering screen scale
- (NSString*)thumbnailURLForContent:(NSString*)contentURI inViewSize:(CGSize)viewSize withMethod:(MXThumbnailingMethod)thumbnailingMethod;
@end
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <UIKit/UIKit.h>
#import "MatrixSDKHandler.h"
// Provide the download progress
// object: URL
// userInfo: kMediaLoaderProgressRateKey : progress value nested in a NSNumber (range 0->1)
// : kMediaLoaderProgressStringKey : progress string XXX KB / XXX MB" (optional)
// : kMediaLoaderProgressRemaingTimeKey : remaining time string "XX s left" (optional)
// : kMediaLoaderProgressDownloadRateKey : string like XX MB/s (optional)
extern NSString *const kMediaDownloadProgressNotification;
// Provide the upload progress
// object: uploadId
// userInfo: kMediaLoaderProgressRateKey : progress value nested in a NSNumber (range 0->1)
// : kMediaLoaderProgressStringKey : progress string XXX KB / XXX MB" (optional)
// : kMediaLoaderProgressRemaingTimeKey : remaining time string "XX s left" (optional)
// : kMediaLoaderProgressDownloadRateKey : string like XX MB/s (optional)
extern NSString *const kMediaUploadProgressNotification;
// userInfo keys
extern NSString *const kMediaLoaderProgressRateKey;
extern NSString *const kMediaLoaderProgressStringKey;
extern NSString *const kMediaLoaderProgressRemaingTimeKey;
extern NSString *const kMediaLoaderProgressDownloadRateKey;
// The callback blocks
typedef void (^blockMediaLoader_onSuccess)(NSString *url); // url is a cache file path for successful download, or a remote url for upload.
typedef void (^blockMediaLoader_onError)(NSError *error);
@interface MediaLoader : NSObject <NSURLConnectionDataDelegate> {
NSString *mimeType;
blockMediaLoader_onSuccess onSuccess;
blockMediaLoader_onError onError;
NSString *folder;
// Download
NSString *mediaURL;
long long expectedSize;
NSMutableData *downloadData;
NSURLConnection *downloadConnection;
// statistic info (bitrate, remaining time...)
CFAbsoluteTime statsStartTime;
CFAbsoluteTime downloadStartTime;
CFAbsoluteTime lastProgressEventTimeStamp;
NSTimer* progressCheckTimer;
// Upload
NSString *uploadId;
CGFloat initialRange;
CGFloat range;
MXHTTPOperation* operation;
}
@property (strong, readonly) NSMutableDictionary* statisticsDict;
- (void)cancel;
// Download
- (void)downloadMedia:(NSString *)aMediaURL
mimeType:(NSString *)aMimeType
folder:(NSString*)folder
success:(blockMediaLoader_onSuccess)success
failure:(blockMediaLoader_onError)failure;
// Upload
// initialRange / range: an upload could be a subpart of uploads. initialRange defines the global upload progress already did done before this current upload.
// range is the range value of this upload in the global scope.
// e.g. : Upload a media can be split in two parts :
// 1 - upload the thumbnail -> initialRange = 0, range = 0.1 : assume that the thumbnail upload is 10% of the upload process
// 2 - upload the media -> initialRange = 0.1, range = 0.9 : the media upload is 90% of the global upload
- (id)initWithUploadId:(NSString *)anUploadId initialRange:(CGFloat)anInitialRange andRange:(CGFloat)aRange folder:(NSString*)aFolder;
- (void)uploadData:(NSData *)data
mimeType:(NSString *)aMimeType
success:(blockMediaLoader_onSuccess)success
failure:(blockMediaLoader_onError)failure;
@end
+252
View File
@@ -0,0 +1,252 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "MediaManager.h"
#import "MXCTools.h"
NSString *const kMediaDownloadProgressNotification = @"kMediaDownloadProgressNotification";
NSString *const kMediaUploadProgressNotification = @"kMediaUploadProgressNotification";
NSString *const kMediaLoaderProgressRateKey = @"kMediaLoaderProgressRateKey";
NSString *const kMediaLoaderProgressStringKey = @"kMediaLoaderProgressStringKey";
NSString *const kMediaLoaderProgressRemaingTimeKey = @"kMediaLoaderProgressRemaingTimeKey";
NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDownloadRateKey";
@implementation MediaLoader
@synthesize statisticsDict;
- (void)cancel {
// Cancel potential connection
if (downloadConnection) {
NSLog(@"[MediaLoader] Media download has been cancelled (%@)", mediaURL);
if (onError){
onError(nil);
}
// Reset blocks
onSuccess = nil;
onError = nil;
[downloadConnection cancel];
downloadConnection = nil;
downloadData = nil;
}
else {
if (operation.operation.executing) {
NSLog(@"[MediaLoader] Media upload has been cancelled (%@)", mediaURL);
[operation cancel];
operation = nil;
}
// Reset blocks
onSuccess = nil;
onError = nil;
}
folder = nil;
statisticsDict = nil;
}
- (void)dealloc {
[self cancel];
}
#pragma mark - Download
- (void)downloadMedia:(NSString*)aMediaURL
mimeType:(NSString *)aMimeType
folder:(NSString*)aFolder
success:(blockMediaLoader_onSuccess)success
failure:(blockMediaLoader_onError)failure {
// Report provided params
mediaURL = aMediaURL;
mimeType = aMimeType;
folder = aFolder;
onSuccess = success;
onError = failure;
downloadStartTime = statsStartTime = CFAbsoluteTimeGetCurrent();
lastProgressEventTimeStamp = -1;
// Check provided url (it may be a matrix content uri, we use SDK to build absoluteURL)
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
NSString *absoluteMediaURL = [mxHandler.mxRestClient urlOfContent:aMediaURL];
if (nil == absoluteMediaURL) {
// It was not a matrix content uri, we keep the provided url
absoluteMediaURL = aMediaURL;
}
// Start downloading
NSURL *url = [NSURL URLWithString:absoluteMediaURL];
downloadData = [[NSMutableData alloc] init];
downloadConnection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
expectedSize = response.expectedContentLength;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"[MediaLoader] Failed to download media (%@): %@", mediaURL, error);
// send the latest known upload info
[self progressCheckTimeout:nil];
statisticsDict = nil;
if (onError) {
onError (error);
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append data
[downloadData appendData:data];
if (expectedSize > 0) {
//
float rate = ((float)downloadData.length) / ((float)expectedSize);
// should never happen
if (rate > 1) {
rate = 1.0;
}
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
CGFloat meanRate = downloadData.length / (currentTime - downloadStartTime)/ 1024.0;
CGFloat dataRemainingTime = 0;
if (0 != meanRate)
{
dataRemainingTime = ((expectedSize - downloadData.length) / 1024.0) / meanRate;
}
statsStartTime = currentTime;
// build the user info dictionary
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setValue:[NSNumber numberWithFloat:rate] forKey:kMediaLoaderProgressRateKey];
NSString* progressString = [NSString stringWithFormat:@"%@ / %@", [NSByteCountFormatter stringFromByteCount:downloadData.length countStyle:NSByteCountFormatterCountStyleFile], [NSByteCountFormatter stringFromByteCount:expectedSize countStyle:NSByteCountFormatterCountStyleFile]];
[dict setValue:progressString forKey:kMediaLoaderProgressStringKey];
[dict setValue:[MXCTools formatSecondsInterval:dataRemainingTime] forKey:kMediaLoaderProgressRemaingTimeKey];
NSString* downloadRateStr = [NSString stringWithFormat:@"%@/s", [NSByteCountFormatter stringFromByteCount:meanRate * 1024 countStyle:NSByteCountFormatterCountStyleFile]];
[dict setValue:downloadRateStr forKey:kMediaLoaderProgressDownloadRateKey];
statisticsDict = dict;
// after 0.1s, resend the progress info
// the upload can be stuck
[progressCheckTimer invalidate];
progressCheckTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(progressCheckTimeout:) userInfo:self repeats:NO];
// trigger the event only each 0.1s to avoid send to many events
if ((lastProgressEventTimeStamp == -1) || ((currentTime - lastProgressEventTimeStamp) > 0.1)) {
lastProgressEventTimeStamp = currentTime;
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadProgressNotification object:mediaURL userInfo:statisticsDict];
}
}
}
- (IBAction)progressCheckTimeout:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadProgressNotification object:mediaURL userInfo:statisticsDict];
[progressCheckTimer invalidate];
progressCheckTimer = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// send the latest known upload info
[self progressCheckTimeout:nil];
statisticsDict = nil;
if (downloadData.length) {
// Cache the downloaded data
NSString *cacheFilePath = [MediaManager cacheMediaData:downloadData forURL:mediaURL andType:mimeType inFolder:folder];
// Call registered block
if (onSuccess) {
onSuccess(cacheFilePath);
}
} else {
NSLog(@"[MediaLoader] Failed to download media: %@", mediaURL);
if (onError){
onError(nil);
}
}
downloadData = nil;
downloadConnection = nil;
}
#pragma mark - Upload
- (id)initWithUploadId:(NSString *)anUploadId initialRange:(CGFloat)anInitialRange andRange:(CGFloat)aRange folder:(NSString*)aFolder {
if (self = [super init]) {
uploadId = anUploadId;
initialRange = anInitialRange;
range = aRange;
folder = aFolder;
}
return self;
}
- (void)uploadData:(NSData *)data mimeType:(NSString *)aMimeType success:(blockMediaLoader_onSuccess)success failure:(blockMediaLoader_onError)failure {
mimeType = aMimeType;
statsStartTime = CFAbsoluteTimeGetCurrent();
MatrixSDKHandler *mxHandler = [MatrixSDKHandler sharedHandler];
operation = [mxHandler.mxRestClient uploadContent:data mimeType:mimeType timeout:30 success:success failure:failure uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
[self updateUploadProgressWithBytesWritten:bytesWritten totalBytesWritten:totalBytesWritten andTotalBytesExpectedToWrite:totalBytesExpectedToWrite];
}];
}
- (void)updateUploadProgressWithBytesWritten:(NSUInteger)bytesWritten totalBytesWritten:(long long)totalBytesWritten andTotalBytesExpectedToWrite:(long long)totalBytesExpectedToWrite {
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
if (!statisticsDict) {
statisticsDict = [[NSMutableDictionary alloc] init];
}
CGFloat progressRate = initialRange + (((float)totalBytesWritten) / ((float)totalBytesExpectedToWrite) * range);
[statisticsDict setValue:[NSNumber numberWithFloat:progressRate] forKey:kMediaLoaderProgressRateKey];
CGFloat dataRate = 0;
if (currentTime != statsStartTime)
{
dataRate = bytesWritten / 1024.0 / (currentTime - statsStartTime);
}
else
{
dataRate = bytesWritten / 1024.0 / 0.001;
}
statsStartTime = currentTime;
CGFloat dataRemainingTime = 0;
if (0 != dataRate)
{
dataRemainingTime = (totalBytesExpectedToWrite - totalBytesWritten)/ 1024.0 / dataRate;
}
NSString* progressString = [NSString stringWithFormat:@"%@ / %@", [NSByteCountFormatter stringFromByteCount:totalBytesWritten countStyle:NSByteCountFormatterCountStyleFile], [NSByteCountFormatter stringFromByteCount:totalBytesExpectedToWrite countStyle:NSByteCountFormatterCountStyleFile]];
[statisticsDict setValue:progressString forKey:kMediaLoaderProgressStringKey];
[statisticsDict setValue:[MXCTools formatSecondsInterval:dataRemainingTime] forKey:kMediaLoaderProgressRemaingTimeKey];
NSString* downloadRateStr = [NSString stringWithFormat:@"%@/s", [NSByteCountFormatter stringFromByteCount:dataRate * 1024 countStyle:NSByteCountFormatterCountStyleFile]];
[statisticsDict setValue:downloadRateStr forKey:kMediaLoaderProgressDownloadRateKey];
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaUploadProgressNotification object:uploadId userInfo:statisticsDict];
}
@end
+70
View File
@@ -0,0 +1,70 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <AVFoundation/AVFoundation.h>
#import "MediaLoader.h"
extern NSString *const kMediaManagerPrefixForDummyURL;
extern NSString *const kMediaManagerThumbnailFolder;
// notify when a media download is finished (object: URL)
extern NSString *const kMediaDownloadDidFinishNotification;
extern NSString *const kMediaDownloadDidFailNotification;
@interface MediaManager : NSObject
// Download data from the provided URL. Return a mediaLoader reference in order to let the user cancel this action.
+ (MediaLoader*)downloadMediaFromURL:(NSString *)mediaURL withType:(NSString *)mimeType inFolder:(NSString*)folder;
// Check whether a download is already running for this media url. Return loader if any
+ (MediaLoader*)existingDownloaderForURL:(NSString*)url inFolder:(NSString*)folder;
// cancel any pending download, within the folder
+ (void)cancelDownloadsInFolder:(NSString*)folder;
// cancel any pending download
+ (void)cancelDownloads;
// Prepares and returns a media loader to upload data to matrix content repository.
// initialRange / range: an upload could be a subpart of uploads. initialRange defines the global upload progress already did done before this current upload.
// range is the range value of this upload in the global scope.
// e.g. : Upload a media can be split in two parts :
// 1 - upload the thumbnail -> initialRange = 0, range = 0.1 : assume that the thumbnail upload is 10% of the upload process
// 2 - upload the media -> initialRange = 0.1, range = 0.9 : the media upload is 90% of the global upload
+ (MediaLoader*)prepareUploaderWithId:(NSString *)uploadId initialRange:(CGFloat)initialRange andRange:(CGFloat)range inFolder:(NSString*)folder;
// Check whether an upload is already running with this id. Return loader if any
+ (MediaLoader*)existingUploaderWithId:(NSString*)uploadId inFolder:(NSString*)folder;
+ (void)removeUploaderWithId:(NSString*)uploadId inFolder:(NSString*)folder;
// cancel pending MediaLoader in folder
+ (void)cancelUploadsInFolder:(NSString*)folder;
// cancel any pending upload
+ (void)cancelUploads;
// Load a picture from the local cache (Do not start any remote requests)
+ (UIImage*)loadCachePictureForURL:(NSString*)pictureURL inFolder:(NSString*)folder;
// Store in cache the provided data for the media URL, return the path of the resulting file
+ (NSString*)cacheMediaData:(NSData *)mediaData forURL:(NSString *)mediaURL andType:(NSString *)mimeType inFolder:(NSString*)folder;
// Return the cache path deduced from media URL and type
+ (NSString*)cachePathForMediaURL:(NSString*)mediaURL andType:(NSString *)mimeType inFolder:(NSString*)folder;
// check if the media cache size must be reduced to fit the user expected cache size
+ (void)reduceCacheSizeToInsert:(NSUInteger)bytes;
// cache size management (values are in bytes)
+ (NSUInteger)cacheSize;
+ (NSUInteger)minCacheSize;
+ (NSUInteger)currentMaxCacheSize;
+ (void)setCurrentMaxCacheSize:(NSUInteger)maxCacheSize;
+ (NSUInteger)maxAllowedCacheSize;
+ (void)clearCache;
@end
+408
View File
@@ -0,0 +1,408 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "MediaManager.h"
#import "AppSettings.h"
#import "MXCTools.h"
#import "AppDelegate.h"
NSString *const kMediaManagerPrefixForDummyURL = @"dummyUrl-";
NSString *const kMediaDownloadDidFinishNotification = @"kMediaDownloadDidFinishNotification";
NSString *const kMediaDownloadDidFailNotification = @"kMediaDownloadDidFailNotification";
NSString *const kMediaManagerThumbnailFolder = @"kMediaManagerThumbnailFolder";
static NSString* mediaCachePath = nil;
static NSString *mediaDir = @"mediacache";
static MediaManager *sharedMediaManager = nil;
// store the current cache size
// avoid listing files because it is useless
static NSUInteger storageCacheSize = 0;
@implementation MediaManager
// Table of downloads in progress
static NSMutableDictionary* downloadTableByURL = nil;
// Table of uploads in progress
static NSMutableDictionary* uploadTableById = nil;
#pragma mark - Media Download
+ (NSString*)downloadKey:mediaURL andFolder:(NSString*)folder {
NSMutableString* key = [[NSMutableString alloc] init];
[key appendString:mediaURL];
if (folder.length > 0) {
[key appendFormat:@"_download_%@", folder];
}
return key;
}
+ (MediaLoader*)downloadMediaFromURL:(NSString*)mediaURL withType:(NSString *)mimeType inFolder:(NSString*)folder {
if (mediaURL && [mediaURL hasPrefix:kMediaManagerPrefixForDummyURL] == NO) {
// Create a media loader to download data
MediaLoader *mediaLoader = [[MediaLoader alloc] init];
// Report this loader
if (!downloadTableByURL) {
downloadTableByURL = [[NSMutableDictionary alloc] init];
}
[downloadTableByURL setValue:mediaLoader forKey:[MediaManager downloadKey:mediaURL andFolder:folder]];
// Launch download
[mediaLoader downloadMedia:mediaURL mimeType:mimeType folder:folder success:^(NSString *cacheFilePath) {
[downloadTableByURL removeObjectForKey:[MediaManager downloadKey:mediaURL andFolder:folder]];
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFinishNotification object:mediaURL userInfo:nil];
} failure:^(NSError *error) {
[downloadTableByURL removeObjectForKey:[MediaManager downloadKey:mediaURL andFolder:folder]];
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:mediaURL userInfo:nil];
}];
return mediaLoader;
}
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:mediaURL userInfo:nil];
return nil;
}
// try to find out a media loader from a media URL
+ (id)existingDownloaderForURL:(NSString*)url inFolder:(NSString*)folder {
if (downloadTableByURL && url) {
return [downloadTableByURL valueForKey:[MediaManager downloadKey:url andFolder:folder]];
}
return nil;
}
+ (void)cancelDownloadsInFolder:(NSString*)folder {
NSMutableArray* pendingLoaders =[[NSMutableArray alloc] init];
NSArray* allKeys = [downloadTableByURL allKeys];
// any folder name ?
if (folder.length > 0) {
NSString* keySuffix = [NSString stringWithFormat:@"_download_%@", folder];
for(NSString* key in allKeys) {
if ([key hasSuffix:keySuffix]) {
[pendingLoaders addObject:[downloadTableByURL valueForKey:key]];
[downloadTableByURL removeObjectForKey:key];
}
}
} else {
for(NSString* key in allKeys) {
if ([key rangeOfString:@"_download_"].location == NSNotFound) {
[pendingLoaders addObject:[downloadTableByURL valueForKey:key]];
[downloadTableByURL removeObjectForKey:key];
}
}
}
if (pendingLoaders > 0) {
for (MediaLoader* loader in pendingLoaders) {
[loader cancel];
}
}
}
// cancel any pending download
+ (void)cancelDownloads {
NSArray* allKeys = [downloadTableByURL allKeys];
for(NSString* key in allKeys) {
[[downloadTableByURL valueForKey:key] cancel];
[downloadTableByURL removeObjectForKey:key];
}
}
#pragma mark - Media Uploader
+ (NSString*)uploadKey:uploadId andFolder:(NSString*)folder {
NSMutableString* key = [[NSMutableString alloc] init];
[key appendString:uploadId];
if (folder.length > 0) {
[key appendFormat:@"_upload_%@", folder];
}
return key;
}
+ (MediaLoader*)prepareUploaderWithId:(NSString *)uploadId initialRange:(CGFloat)initialRange andRange:(CGFloat)range inFolder:(NSString*)aFolder {
if (uploadId) {
// Create a media loader to upload data
MediaLoader *mediaLoader = [[MediaLoader alloc] initWithUploadId:uploadId initialRange:initialRange andRange:range folder:aFolder];
// Report this loader
if (!uploadTableById) {
uploadTableById = [[NSMutableDictionary alloc] init];
}
[uploadTableById setValue:mediaLoader forKey:[MediaManager uploadKey:uploadId andFolder:aFolder]];
return mediaLoader;
}
return nil;
}
+ (MediaLoader*)existingUploaderWithId:(NSString*)uploadId inFolder:(NSString*)folder {
if (uploadTableById && uploadId) {
return [uploadTableById valueForKey:[MediaManager uploadKey:uploadId andFolder:folder]];
}
return nil;
}
+ (void)removeUploaderWithId:(NSString*)uploadId inFolder:(NSString*)folder {
if (uploadTableById && uploadId) {
return [uploadTableById removeObjectForKey:[MediaManager uploadKey:uploadId andFolder:folder]];
}
}
+ (void)cancelUploadsInFolder:(NSString*)folder {
NSMutableArray* pendingLoaders =[[NSMutableArray alloc] init];
NSArray* allKeys = [uploadTableById allKeys];
//
if (folder.length > 0) {
NSString* keySuffix = [NSString stringWithFormat:@"_upload_%@", folder];
for(NSString* key in allKeys) {
if ([key hasSuffix:keySuffix]) {
[pendingLoaders addObject:[uploadTableById valueForKey:key]];
[uploadTableById removeObjectForKey:key];
}
}
} else {
for(NSString* key in allKeys) {
if ([key rangeOfString:@"_upload_"].location == NSNotFound) {
[pendingLoaders addObject:[uploadTableById valueForKey:key]];
[uploadTableById removeObjectForKey:key];
}
}
}
if (pendingLoaders > 0) {
for (MediaLoader* loader in pendingLoaders) {
[loader cancel];
}
}
}
// cancel any pending download
+ (void)cancelUploads {
NSArray* allKeys = [uploadTableById allKeys];
for(NSString* key in allKeys) {
[[uploadTableById valueForKey:key] cancel];
[uploadTableById removeObjectForKey:key];
}
}
#pragma mark - Cache Handling
+ (UIImage*)loadCachePictureForURL:(NSString*)pictureURL inFolder:(NSString*)folder {
UIImage* res = nil;
NSString* filename = [MediaManager cachePathForMediaURL:pictureURL andType:@"image/jpeg" inFolder:folder];
if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
NSData* imageContent = [NSData dataWithContentsOfFile:filename options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:nil];
if (imageContent) {
res = [[UIImage alloc] initWithData:imageContent];
}
}
return res;
}
+ (void)reduceCacheSizeToInsert:(NSUInteger)bytes {
if (([MediaManager cacheSize] + bytes) > [MediaManager maxAllowedCacheSize]) {
NSString* thumbnailPath = [MediaManager cacheFolderPath:kMediaManagerThumbnailFolder];
NSString* activeRoomPath = nil;
if ([AppDelegate theDelegate].masterTabBarController.visibleRoomId) {
activeRoomPath = [MediaManager cacheFolderPath:[AppDelegate theDelegate].masterTabBarController.visibleRoomId];
}
// add a 50 MB margin to reduce this method call
NSUInteger maxSize = 0;
// check if the cache cannot content the file
if ([MediaManager maxAllowedCacheSize] < (bytes - 50 * 1024 * 1024)) {
// delete item as much as possible
maxSize = 0;
} else {
maxSize = [MediaManager maxAllowedCacheSize] - bytes - 50 * 1024 * 1024;
}
NSArray* filesList = [MXCTools listFiles:mediaCachePath timeSorted:YES largeFilesFirst:YES];
// list the files sorted by timestamp
for(NSString* filepath in filesList) {
// do not release the contact thumbnails : they must be released by when the contacts are deleted
// do not release the active room medias : it could trigger weird UI effect on a tablet / iphone 6+
if (![filepath hasPrefix:thumbnailPath] && (!activeRoomPath || ![filepath hasPrefix:activeRoomPath])) {
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filepath error:nil];
// sanity check
if (fileAttributes) {
// delete the files
if ([[NSFileManager defaultManager] removeItemAtPath:filepath error:nil]) {
storageCacheSize -= fileAttributes.fileSize;
if (storageCacheSize < maxSize) {
return;
}
}
}
}
}
}
}
+ (NSString*)cacheMediaData:(NSData*)mediaData forURL:(NSString *)mediaURL andType:(NSString *)mimeType inFolder:(NSString*)folder {
[MediaManager reduceCacheSizeToInsert:mediaData.length];
NSString* filename = [MediaManager cachePathForMediaURL:mediaURL andType:mimeType inFolder:folder];
if ([mediaData writeToFile:filename atomically:YES]) {
storageCacheSize += mediaData.length;
return filename;
} else {
return nil;
}
}
+ (NSString*)cacheFolderPath:(NSString*)folder {
NSString* path = [MediaManager getCachePath];
// update the path if the folder is provided
if (folder.length > 0) {
path = [[MediaManager getCachePath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%lu", (unsigned long)folder.hash]];
}
// create the folder it does not exist
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:nil];
}
return path;
}
+ (NSString*)cachePathForMediaURL:(NSString*)mediaURL andType:(NSString *)mimeType inFolder:(NSString*)folder {
NSString* fileExt = [MXCTools fileExtensionFromContentType:mimeType];
NSString* fileBase = @"";
// use the mime type to extract a base filename
if ([mimeType rangeOfString:@"/"].location != NSNotFound){
NSArray *components = [mimeType componentsSeparatedByString:@"/"];
fileBase = [components objectAtIndex:0];
}
return [[MediaManager cacheFolderPath:folder] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%lu%@", [fileBase substringToIndex:3], (unsigned long)mediaURL.hash, fileExt]];
}
+ (NSUInteger)cacheSize {
if (!mediaCachePath) {
// compute the path
mediaCachePath = [MediaManager getCachePath];
}
// assume that 0 means uninitialized
if (storageCacheSize == 0) {
storageCacheSize = (NSUInteger)[MXCTools folderSize:mediaCachePath];
}
return storageCacheSize;
}
+ (NSUInteger)minCacheSize {
NSUInteger minSize = [MediaManager cacheSize];
NSArray* filenamesList = [MXCTools listFiles:mediaCachePath timeSorted:NO largeFilesFirst:YES];
NSFileManager* defaultManager = [NSFileManager defaultManager];
for(NSString* filename in filenamesList) {
NSDictionary* attsDict = [defaultManager attributesOfItemAtPath:filename error:nil];
if (attsDict) {
if (attsDict.fileSize > 100 * 1024) {
minSize -= attsDict.fileSize;
}
}
}
return minSize;
}
+ (NSUInteger)currentMaxCacheSize {
return [AppSettings sharedSettings].currentMaxMediaCacheSize;
}
+ (void)setCurrentMaxCacheSize:(NSUInteger)maxCacheSize {
[AppSettings sharedSettings].currentMaxMediaCacheSize = maxCacheSize;
}
+ (NSUInteger)maxAllowedCacheSize {
return [AppSettings sharedSettings].maxAllowedMediaCacheSize;
}
+ (void)clearCache {
NSError *error = nil;
if (!mediaCachePath) {
// compute the path
mediaCachePath = [MediaManager getCachePath];
}
[MediaManager cancelDownloads];
[MediaManager cancelUploads];
if (mediaCachePath) {
if (![[NSFileManager defaultManager] removeItemAtPath:mediaCachePath error:&error]) {
NSLog(@"[MediaManager] Failed to delete media cache dir: %@", error);
} else {
NSLog(@"[MediaManager] Media cache has been deleted");
}
} else {
NSLog(@"[MediaManager] Media cache does not exist");
}
mediaCachePath = nil;
// force to recompute the cache size at next cacheSize call
storageCacheSize = 0;
}
+ (NSString*)getCachePath {
NSString *cachePath = nil;
if (!mediaCachePath) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheRoot = [paths objectAtIndex:0];
mediaCachePath = [cacheRoot stringByAppendingPathComponent:mediaDir];
if (![[NSFileManager defaultManager] fileExistsAtPath:mediaCachePath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:mediaCachePath withIntermediateDirectories:NO attributes:nil error:nil];
}
}
cachePath = mediaCachePath;
return cachePath;
}
@end