MediaManager refactoring: handle upload with mediaLoader (remove UploadManager).

This commit is contained in:
giomfo
2015-01-12 18:07:39 +01:00
parent 6baf80b501
commit f02f82b066
14 changed files with 247 additions and 292 deletions
+37 -11
View File
@@ -16,14 +16,22 @@
#import <UIKit/UIKit.h>
// provide the download/upload progress
// 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;
@@ -31,18 +39,18 @@ extern NSString *const kMediaLoaderProgressRemaingTimeKey;
extern NSString *const kMediaLoaderProgressDownloadRateKey;
// The callback blocks
typedef void (^blockMediaLoader_onMediaReady)(NSString *cacheFilePath);
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 *mediaURL;
NSString *mimeType;
blockMediaLoader_onMediaReady onMediaReady;
blockMediaLoader_onSuccess onSuccess;
blockMediaLoader_onError onError;
// Download
NSString *mediaURL;
long long expectedSize;
NSMutableData *downloadData;
NSURLConnection *downloadConnection;
@@ -51,15 +59,33 @@ typedef void (^blockMediaLoader_onError)(NSError *error);
CFAbsoluteTime downloadStartTime;
CFAbsoluteTime lastProgressEventTimeStamp;
NSTimer* progressCheckTimer;
// Upload
NSString *uploadId;
CGFloat initialRange;
CGFloat range;
}
@property (strong, readonly) NSMutableDictionary* downloadStatsDict;
- (void)downloadMedia:(NSString*)aMediaURL
mimeType:(NSString *)aMimeType
success:(blockMediaLoader_onMediaReady)success
failure:(blockMediaLoader_onError)failure;
@property (strong, readonly) NSMutableDictionary* statisticsDict;
- (void)cancel;
// Download
- (void)downloadMedia:(NSString *)aMediaURL
mimeType:(NSString *)aMimeType
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;
- (void)uploadData:(NSData *)data
mimeType:(NSString *)aMimeType
success:(blockMediaLoader_onSuccess)success
failure:(blockMediaLoader_onError)failure;
@end
+101 -39
View File
@@ -28,7 +28,7 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
@implementation MediaLoader
@synthesize downloadStatsDict;
@synthesize statisticsDict;
- (NSString*)validateContentURL:(NSString*)contentURL {
// Detect matrix content url
@@ -41,14 +41,42 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
return contentURL;
}
- (void)cancel {
// Cancel potential connection
if (downloadConnection) {
NSLog(@"Image download has been cancelled (%@)", mediaURL);
if (onError){
onError(nil);
}
// Reset blocks
onSuccess = nil;
onError = nil;
[downloadConnection cancel];
downloadConnection = nil;
downloadData = nil;
}
else {
// Reset blocks
onSuccess = nil;
onError = nil;
}
statisticsDict = nil;
}
- (void)dealloc {
[self cancel];
}
#pragma mark - Download
- (void)downloadMedia:(NSString*)aMediaURL
mimeType:(NSString *)aMimeType
success:(blockMediaLoader_onMediaReady)success
failure:(blockMediaLoader_onError)failure {
success:(blockMediaLoader_onSuccess)success
failure:(blockMediaLoader_onError)failure {
// Report provided params
mediaURL = aMediaURL;
mimeType = aMimeType;
onMediaReady = success;
onSuccess = success;
onError = failure;
downloadStartTime = statsStartTime = CFAbsoluteTimeGetCurrent();
@@ -60,33 +88,6 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
downloadConnection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
}
- (void)cancel {
// Cancel potential connection
if (downloadConnection) {
NSLog(@"Image download has been cancelled (%@)", mediaURL);
if (onError){
onError(nil);
}
// Reset blocks
onMediaReady = nil;
onError = nil;
[downloadConnection cancel];
downloadConnection = nil;
downloadData = nil;
}
else {
// Reset blocks
onMediaReady = nil;
onError = nil;
}
}
- (void)dealloc {
[self cancel];
}
#pragma mark -
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
expectedSize = response.expectedContentLength;
}
@@ -95,7 +96,7 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
NSLog(@"ERROR: media download failed: %@, %@", error, mediaURL);
// send the latest known upload info
[self progressCheckTimeout:nil];
downloadStatsDict = nil;
statisticsDict = nil;
if (onError) {
onError (error);
}
@@ -150,7 +151,7 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
NSString* downloadRateStr = [NSString stringWithFormat:@"%@/s", [NSByteCountFormatter stringFromByteCount:meanRate * 1024 countStyle:NSByteCountFormatterCountStyleFile]];
[dict setValue:downloadRateStr forKey:kMediaLoaderProgressDownloadRateKey];
downloadStatsDict = dict;
statisticsDict = dict;
// after 0.1s, resend the progress info
// the upload can be stuck
@@ -160,16 +161,16 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
// 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:downloadStatsDict];
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadProgressNotification object:mediaURL userInfo:statisticsDict];
}
}
}
- (IBAction)progressCheckTimeout:(id)sender {
// remove the bitrate -> can be invalid
[downloadStatsDict removeObjectForKey:kMediaLoaderProgressDownloadRateKey];
[statisticsDict removeObjectForKey:kMediaLoaderProgressDownloadRateKey];
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadProgressNotification object:mediaURL userInfo:downloadStatsDict];
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadProgressNotification object:mediaURL userInfo:statisticsDict];
[progressCheckTimer invalidate];
progressCheckTimer = nil;
}
@@ -177,14 +178,14 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// send the latest known upload info
[self progressCheckTimeout:nil];
downloadStatsDict = nil;
statisticsDict = nil;
if (downloadData.length) {
// Cache the downloaded data
NSString *cacheFilePath = [MediaManager cacheMediaData:downloadData forURL:mediaURL andType:mimeType];
// Call registered block
if (onMediaReady) {
onMediaReady(cacheFilePath);
if (onSuccess) {
onSuccess(cacheFilePath);
}
} else {
NSLog(@"ERROR: media download failed: %@", mediaURL);
@@ -197,4 +198,65 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown
downloadConnection = nil;
}
#pragma mark - Upload
- (id)initWithUploadId:(NSString *)anUploadId initialRange:(CGFloat)anInitialRange andRange:(CGFloat)aRange {
if (self = [super init]) {
uploadId = anUploadId;
initialRange = anInitialRange;
range = aRange;
}
return self;
}
- (void)uploadData:(NSData *)data mimeType:(NSString *)aMimeType success:(blockMediaLoader_onSuccess)success failure:(blockMediaLoader_onError)failure {
mimeType = aMimeType;
statsStartTime = CFAbsoluteTimeGetCurrent();
MatrixHandler *mxHandler = [MatrixHandler sharedHandler];
[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:[ConsoleTools 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
+14 -6
View File
@@ -19,20 +19,28 @@
extern NSString *const kMediaManagerPrefixForDummyURL;
// notify when a media download is finished
// object: URL
// notify when a media download is finished (object: URL)
extern NSString *const kMediaDownloadDidFinishNotification;
extern NSString *const kMediaDownloadDidFailNotification;
@interface MediaManager : NSObject
+ (id)sharedInstance;
// Launch data download from the provided URL. Return a mediaLoader reference in order to let the user cancel this action.
+ (MediaLoader*)downloadMedia:(NSString*)mediaURL mimeType:(NSString *)mimeType;
// 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;
// Check whether a download is already running for this media url. Return loader if any
+ (MediaLoader*)existingDownloaderForURL:(NSString*)url;
// 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;
// Check whether an upload is already running with this id. Return loader if any
+ (MediaLoader*)existingUploaderWithId:(NSString*)uploadId;
+ (void)removeUploaderWithId:(NSString*)uploadId;
// Load a picture from the local cache (Do not start any remote requests)
+ (UIImage*)loadCachePictureForURL:(NSString*)pictureURL;
// Store in cache the provided data for the media URL, return the path of the resulting file
+37 -14
View File
@@ -28,27 +28,21 @@ static MediaManager *sharedMediaManager = nil;
@implementation MediaManager
// Table of mediaLoaders in progress
// Table of downloads in progress
static NSMutableDictionary* downloadTableByURL = nil;
+ (id)sharedInstance {
@synchronized(self) {
if(sharedMediaManager == nil) {
sharedMediaManager = [[self alloc] init];
// Create download table here
downloadTableByURL = [[NSMutableDictionary alloc] init];
}
}
return sharedMediaManager;
}
// Table of uploads in progress
static NSMutableDictionary* uploadTableById = nil;
#pragma mark - Media Download
+ (MediaLoader*)downloadMedia:(NSString*)mediaURL mimeType:(NSString *)mimeType {
if ([mediaURL hasPrefix:kMediaManagerPrefixForDummyURL] == NO) {
+ (MediaLoader*)downloadMediaFromURL:(NSString*)mediaURL withType:(NSString *)mimeType {
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:mediaURL];
// Launch download
@@ -74,6 +68,35 @@ static NSMutableDictionary* downloadTableByURL = nil;
return nil;
}
#pragma mark - Media Uploader
+ (MediaLoader*)prepareUploaderWithId:(NSString *)uploadId initialRange:(CGFloat)initialRange andRange:(CGFloat)range {
if (uploadId) {
// Create a media loader to upload data
MediaLoader *mediaLoader = [[MediaLoader alloc] initWithUploadId:uploadId initialRange:initialRange andRange:range];
// Report this loader
if (!uploadTableById) {
uploadTableById = [[NSMutableDictionary alloc] init];
}
[uploadTableById setValue:mediaLoader forKey:uploadId];
return mediaLoader;
}
return nil;
}
+ (MediaLoader*)existingUploaderWithId:(NSString*)uploadId {
if (uploadTableById && uploadId) {
return [uploadTableById valueForKey:uploadId];
}
return nil;
}
+ (void)removeUploaderWithId:(NSString*)uploadId {
if (uploadTableById && uploadId) {
return [uploadTableById removeObjectForKey:uploadId];
}
}
#pragma mark - Cache Handling
+ (UIImage*)loadCachePictureForURL:(NSString*)pictureURL {
-37
View File
@@ -1,37 +0,0 @@
/*
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 UploadManager : NSObject
// trigger the kMediaUploadProgressNotification notification from the upload parameters
// URL : the uploading media URL
// bytesWritten / totalBytesWritten / totalBytesExpectedToWrite : theses parameters are provided by NSURLConnectionDelegate
// initialRange / range: the current upload info 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
+ (void) onUploadProgress:(NSString*)URL bytesWritten:(NSUInteger)bytesWritten totalBytesWritten:(long long)totalBytesWritten totalBytesExpectedToWrite:(long long)totalBytesExpectedToWrite initialRange:(CGFloat)initialRange range:(CGFloat)range;
// returns the stats info with kMediaLoaderProgress... key
+ (NSDictionary*)statsInfoForURL:(NSString*)URL;
// the upload
+ (void)removeURL:(NSString*)URL;
@end
-113
View File
@@ -1,113 +0,0 @@
/*
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 "UploadManager.h"
#import "MediaManager.h"
#import "ConsoleTools.h"
NSString *const kUploadManagerUploadStartTimeKey = @"kUploadManagerUploadStartTimeKey";
NSString *const kUploadManagerStatsStartTimeKey = @"kUploadManagerUploadStartTimeKey";
@implementation UploadManager
// Table of stats dictionry by media URL
static NSMutableDictionary* statsByURL = nil;
// trigger the kMediaUploadProgressNotification notification from the upload parameters
// URL : the uploading media URL
// bytesWritten / totalBytesWritten / totalBytesExpectedToWrite : theses parameters are provided by NSURLConnectionDelegate
// initialRange / range: the current upload info 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
+ (void) onUploadProgress:(NSString*)URL bytesWritten:(NSUInteger)bytesWritten totalBytesWritten:(long long)totalBytesWritten totalBytesExpectedToWrite:(long long)totalBytesExpectedToWrite initialRange:(CGFloat)initialRange range:(CGFloat)range {
// sanity check
if (!URL) {
// should never happen
return;
}
if (!statsByURL) {
statsByURL = [[NSMutableDictionary alloc] init];
}
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
NSMutableDictionary* dict = [statsByURL valueForKey:URL];
if (!dict) {
dict = [[NSMutableDictionary alloc] init];
// init the start times
[dict setValue:[NSNumber numberWithDouble:currentTime] forKey:kUploadManagerUploadStartTimeKey];
[dict setValue:[NSNumber numberWithDouble:currentTime] forKey:kUploadManagerStatsStartTimeKey];
[statsByURL setValue:dict forKey:URL];
}
CGFloat progressRate = initialRange + (((float)totalBytesWritten) / ((float)totalBytesExpectedToWrite) * range);
[dict setValue:[NSNumber numberWithFloat:progressRate] forKey:kMediaLoaderProgressRateKey];
CGFloat dataRate = 0;
CFAbsoluteTime statsStartTime = ((NSNumber*)[dict valueForKey:kUploadManagerStatsStartTimeKey]).doubleValue;
if (currentTime != statsStartTime)
{
dataRate = bytesWritten / 1024.0 / (currentTime - statsStartTime);
}
else
{
dataRate = bytesWritten / 1024.0 / 0.001;
}
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]];
[dict setValue:progressString forKey:kMediaLoaderProgressStringKey];
[dict setValue:[ConsoleTools formatSecondsInterval:dataRemainingTime] forKey:kMediaLoaderProgressRemaingTimeKey];
NSString* downloadRateStr = [NSString stringWithFormat:@"%@/s", [NSByteCountFormatter stringFromByteCount:dataRate * 1024 countStyle:NSByteCountFormatterCountStyleFile]];
[dict setValue:downloadRateStr forKey:kMediaLoaderProgressDownloadRateKey];
[[NSNotificationCenter defaultCenter] postNotificationName:kMediaUploadProgressNotification object:URL userInfo:dict];
}
// returns the stats info with kMediaLoaderProgress... key
+ (NSDictionary*)statsInfoForURL:(NSString*)URL {
// sanity check
if (URL) {
return [statsByURL valueForKey:URL];
}
return nil;
}
// the upload
+ (void)removeURL:(NSString*)URL {
if (URL) {
[statsByURL removeObjectForKey:URL];
}
}
@end