mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-05 23:47:44 +02:00
Moved code under a subdirectory: Console
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 image’s 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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user