diff --git a/matrixConsole.xcodeproj/project.pbxproj b/matrixConsole.xcodeproj/project.pbxproj index d43d7ba02..8d8b3dde0 100644 --- a/matrixConsole.xcodeproj/project.pbxproj +++ b/matrixConsole.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 71D2E4EC1A49814B000DE015 /* MemberActionsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 71D2E4EB1A49814B000DE015 /* MemberActionsCell.m */; }; 71DB9DC11A495B6400504A09 /* MemberViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71DB9DC01A495B6400504A09 /* MemberViewController.m */; }; 71E94A771A5C4020009F52E5 /* PieChartView.m in Sources */ = {isa = PBXBuildFile; fileRef = 71E94A761A5C4020009F52E5 /* PieChartView.m */; }; - 71E94A7A1A5FB6D3009F52E5 /* UploadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 71E94A791A5FB6D3009F52E5 /* UploadManager.m */; }; D648B86A591308736E2D4078 /* libPods-matrixConsole.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8141B1E2401FFCC3C5B99234 /* libPods-matrixConsole.a */; }; F00B5DB91A1B9BCE00EA1C8D /* CustomImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = F00B5DB81A1B9BCE00EA1C8D /* CustomImageView.m */; }; F013EEEC1A40D437002BB093 /* matrixConsole-Defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = F013EEEB1A40D437002BB093 /* matrixConsole-Defaults.plist */; }; @@ -74,8 +73,6 @@ 71DB9DC01A495B6400504A09 /* MemberViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MemberViewController.m; sourceTree = ""; }; 71E94A751A5C4020009F52E5 /* PieChartView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PieChartView.h; sourceTree = ""; }; 71E94A761A5C4020009F52E5 /* PieChartView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PieChartView.m; sourceTree = ""; }; - 71E94A781A5FB6D3009F52E5 /* UploadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UploadManager.h; sourceTree = ""; }; - 71E94A791A5FB6D3009F52E5 /* UploadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UploadManager.m; sourceTree = ""; }; 8141B1E2401FFCC3C5B99234 /* libPods-matrixConsole.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-matrixConsole.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B7EC7E45C718BF2BBCE0CF48 /* Pods-matrixConsole.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-matrixConsole.debug.xcconfig"; path = "Pods/Target Support Files/Pods-matrixConsole/Pods-matrixConsole.debug.xcconfig"; sourceTree = ""; }; F00B5DB71A1B9BCE00EA1C8D /* CustomImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomImageView.h; sourceTree = ""; }; @@ -215,8 +212,6 @@ F021FBEE1A5EF57300EA3AE6 /* MediaLoader.m */, F021FBF01A5F1F8E00EA3AE6 /* MediaManager.h */, F021FBF11A5F1F8E00EA3AE6 /* MediaManager.m */, - 71E94A781A5FB6D3009F52E5 /* UploadManager.h */, - 71E94A791A5FB6D3009F52E5 /* UploadManager.m */, ); path = API; sourceTree = ""; @@ -516,7 +511,6 @@ F03EF5F919F171EB00A0EE52 /* RecentsViewController.m in Sources */, 71E94A771A5C4020009F52E5 /* PieChartView.m in Sources */, F0465AFA1A251F85003639F9 /* RoomMessage.m in Sources */, - 71E94A7A1A5FB6D3009F52E5 /* UploadManager.m in Sources */, F021FBF21A5F1F8E00EA3AE6 /* MediaManager.m in Sources */, F04EE51F1A3A01D500C64930 /* APNSHandler.m in Sources */, F03C47111A02952800E445AB /* CustomAlert.m in Sources */, diff --git a/matrixConsole/API/MediaLoader.h b/matrixConsole/API/MediaLoader.h index 30749968f..2a9b475a4 100644 --- a/matrixConsole/API/MediaLoader.h +++ b/matrixConsole/API/MediaLoader.h @@ -16,14 +16,22 @@ #import -// 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 { - 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 diff --git a/matrixConsole/API/MediaLoader.m b/matrixConsole/API/MediaLoader.m index b988d8c25..86dddb507 100644 --- a/matrixConsole/API/MediaLoader.m +++ b/matrixConsole/API/MediaLoader.m @@ -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 \ No newline at end of file diff --git a/matrixConsole/API/MediaManager.h b/matrixConsole/API/MediaManager.h index 0b0671f01..8283617d3 100644 --- a/matrixConsole/API/MediaManager.h +++ b/matrixConsole/API/MediaManager.h @@ -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 diff --git a/matrixConsole/API/MediaManager.m b/matrixConsole/API/MediaManager.m index 8d29e9880..e5d1ab327 100644 --- a/matrixConsole/API/MediaManager.m +++ b/matrixConsole/API/MediaManager.m @@ -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 { diff --git a/matrixConsole/API/UploadManager.h b/matrixConsole/API/UploadManager.h deleted file mode 100644 index 073b61abb..000000000 --- a/matrixConsole/API/UploadManager.h +++ /dev/null @@ -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 - -@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 diff --git a/matrixConsole/API/UploadManager.m b/matrixConsole/API/UploadManager.m deleted file mode 100644 index 615aa5888..000000000 --- a/matrixConsole/API/UploadManager.m +++ /dev/null @@ -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 \ No newline at end of file diff --git a/matrixConsole/Model/RoomMessage.h b/matrixConsole/Model/RoomMessage.h index c44d23ecb..efa297333 100644 --- a/matrixConsole/Model/RoomMessage.h +++ b/matrixConsole/Model/RoomMessage.h @@ -21,6 +21,7 @@ #define ROOM_MESSAGE_TEXTVIEW_MARGIN 5 extern NSString *const kRoomMessageLocalPreviewKey; +extern NSString *const kRoomMessageUploadIdKey; typedef enum : NSUInteger { // Text type @@ -61,8 +62,9 @@ typedef enum : NSUInteger { @property (nonatomic) NSDictionary *attachmentInfo; @property (nonatomic) NSString *thumbnailURL; @property (nonatomic) NSDictionary *thumbnailInfo; -@property (nonatomic) CGFloat uploadProgress; @property (nonatomic) NSString *previewURL; +@property (nonatomic) NSString *uploadId; +@property (nonatomic) CGFloat uploadProgress; // Patch: Outgoing messages may be received from events stream whereas the app is waiting for our PUT to return. // In this case, some messages are temporary hidden diff --git a/matrixConsole/Model/RoomMessage.m b/matrixConsole/Model/RoomMessage.m index 1bd98e970..0d72a09f9 100644 --- a/matrixConsole/Model/RoomMessage.m +++ b/matrixConsole/Model/RoomMessage.m @@ -20,6 +20,7 @@ #import "AppSettings.h" NSString *const kRoomMessageLocalPreviewKey = @"kRoomMessageLocalPreviewKey"; +NSString *const kRoomMessageUploadIdKey = @"kRoomMessageUploadIdKey"; static NSAttributedString *messageSeparator = nil; @@ -91,8 +92,10 @@ static NSAttributedString *messageSeparator = nil; // Not supported yet // _messageType = RoomMessageTypeLocation; } - // Retriew local preview url (if any) + // Retrieve local preview url (if any) _previewURL = event.content[kRoomMessageLocalPreviewKey]; + // Retrieve upload id (if any) + _uploadId = event.content[kRoomMessageUploadIdKey]; } // Set first component of the current message diff --git a/matrixConsole/View/CustomImageView.m b/matrixConsole/View/CustomImageView.m index 0dad2ff67..b62ecea2c 100644 --- a/matrixConsole/View/CustomImageView.m +++ b/matrixConsole/View/CustomImageView.m @@ -426,7 +426,7 @@ // Set preview until the image is loaded self.image = previewImage; // update the progress UI with the current info - [self updateProgressUI:loader.downloadStatsDict]; + [self updateProgressUI:loader.statisticsDict]; // Add observers [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadProgress:) name:kMediaDownloadProgressNotification object:nil]; @@ -449,7 +449,7 @@ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadProgress:) name:kMediaDownloadProgressNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil]; - [MediaManager downloadMedia:imageURL mimeType:@"image/jpeg"]; + [MediaManager downloadMediaFromURL:imageURL withType:@"image/jpeg"]; } } } diff --git a/matrixConsole/View/RoomMessageTableCell.m b/matrixConsole/View/RoomMessageTableCell.m index 815324d7b..888a62b85 100644 --- a/matrixConsole/View/RoomMessageTableCell.m +++ b/matrixConsole/View/RoomMessageTableCell.m @@ -17,7 +17,6 @@ #import "RoomMessageTableCell.h" #import "MediaManager.h" #import "PieChartView.h" -#import "UploadManager.h" @implementation RoomMessageTableCell @@ -26,12 +25,12 @@ [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)updateProgressUI:(NSDictionary*)downloadStatsDict { +- (void)updateProgressUI:(NSDictionary*)statisticsDict { self.progressView.hidden = NO; - NSString* downloadRate = [downloadStatsDict valueForKey:kMediaLoaderProgressDownloadRateKey]; - NSString* remaingTime = [downloadStatsDict valueForKey:kMediaLoaderProgressRemaingTimeKey]; - NSString* progressString = [downloadStatsDict valueForKey:kMediaLoaderProgressStringKey]; + NSString* downloadRate = [statisticsDict valueForKey:kMediaLoaderProgressDownloadRateKey]; + NSString* remaingTime = [statisticsDict valueForKey:kMediaLoaderProgressRemaingTimeKey]; + NSString* progressString = [statisticsDict valueForKey:kMediaLoaderProgressStringKey]; NSMutableString* text = [[NSMutableString alloc] init]; @@ -49,7 +48,7 @@ self.statsLabel.text = text; - NSNumber* progressNumber = [downloadStatsDict valueForKey:kMediaLoaderProgressRateKey]; + NSNumber* progressNumber = [statisticsDict valueForKey:kMediaLoaderProgressRateKey]; if (progressNumber) { self.progressChartView.progress = progressNumber.floatValue; @@ -97,7 +96,7 @@ // check if there is a downlad in progress MediaLoader *loader = [MediaManager existingDownloaderForURL:self.message.attachmentURL]; - NSDictionary *dict = loader.downloadStatsDict; + NSDictionary *dict = loader.statisticsDict; if (dict) { isHidden = NO; @@ -157,11 +156,10 @@ self.activityIndicator.hidden = NO; [self.activityIndicator startAnimating]; - NSDictionary* uploadDict = [UploadManager statsInfoForURL:self.message.attachmentURL]; - - if (uploadDict) { + MediaLoader *uploader = [MediaManager existingUploaderWithId:self.message.uploadId]; + if (uploader && uploader.statisticsDict) { self.activityIndicator.hidden = YES; - [self updateProgressUI:uploadDict]; + [self updateProgressUI:uploader.statisticsDict]; } else { self.activityIndicator.hidden = NO; self.progressView.hidden = YES; @@ -177,9 +175,8 @@ - (void)onUploadProgress:(NSNotification *)notif { // sanity check if ([notif.object isKindOfClass:[NSString class]]) { - NSString* url = notif.object; - - if ([url isEqualToString:self.message.thumbnailURL] || [url isEqualToString:self.message.attachmentURL]) { + NSString *uploadId = notif.object; + if ([uploadId isEqualToString:self.message.uploadId]) { self.activityIndicator.hidden = YES; [self updateProgressUI:notif.userInfo]; diff --git a/matrixConsole/ViewController/MemberViewController.m b/matrixConsole/ViewController/MemberViewController.m index 9077ac373..b7d331e97 100644 --- a/matrixConsole/ViewController/MemberViewController.m +++ b/matrixConsole/ViewController/MemberViewController.m @@ -184,7 +184,7 @@ // Add observers [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil]; - imageLoader = [MediaManager downloadMedia:_mxRoomMember.avatarUrl mimeType:@"image/jpeg"]; + imageLoader = [MediaManager downloadMediaFromURL:_mxRoomMember.avatarUrl withType:@"image/jpeg"]; } } } else { diff --git a/matrixConsole/ViewController/RoomViewController.m b/matrixConsole/ViewController/RoomViewController.m index 4c04ee315..ec2faa885 100644 --- a/matrixConsole/ViewController/RoomViewController.m +++ b/matrixConsole/ViewController/RoomViewController.m @@ -29,7 +29,6 @@ #import "AppSettings.h" #import "MediaManager.h" -#import "UploadManager.h" #import "ConsoleTools.h" #define ROOMVIEWCONTROLLER_TYPING_TIMEOUT_MS 20000 @@ -929,7 +928,7 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; } else { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil]; - [MediaManager downloadMedia:selectedVideoURL mimeType:mimetype]; + [MediaManager downloadMediaFromURL:selectedVideoURL withType:mimetype]; } } } @@ -1021,15 +1020,12 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; [thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:thumbnailData.length] forKey:@"size"]; // Create the local event displayed during uploading - MXEvent *localEvent = [self addLocalEchoEventForAttachedImage:thumbnail]; - - NSString* dummyURL = [localEvent.content valueForKey:@"url"]; + MXEvent *localEvent = [self addLocalEchoEventForAttachedMedia:thumbnail]; // Upload thumbnail - MatrixHandler *mxHandler = [MatrixHandler sharedHandler]; - [mxHandler.mxRestClient uploadContent:thumbnailData mimeType:@"image/jpeg" timeout:30 success:^(NSString *url) { - - [UploadManager removeURL:dummyURL]; + MediaLoader *uploader = [MediaManager prepareUploaderWithId:localEvent.eventId initialRange:0 andRange:0.1]; + [uploader uploadData:thumbnailData mimeType:@"image/jpeg" success:^(NSString *url) { + [MediaManager removeUploaderWithId:localEvent.eventId]; // Prepare content of attached video NSMutableDictionary *videoContent = [[NSMutableDictionary alloc] init]; @@ -1084,17 +1080,18 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; if (videoData) { if (videoData.length < ROOMVIEWCONTROLLER_UPLOAD_FILE_SIZE) { [videoInfo setValue:[NSNumber numberWithUnsignedInteger:videoData.length] forKey:@"size"]; - [mxHandler.mxRestClient uploadContent:videoData mimeType:videoInfo[@"mimetype"] timeout:30 success:^(NSString *url) { - [UploadManager removeURL:dummyURL]; + + MediaLoader *videoUploader = [MediaManager prepareUploaderWithId:localEvent.eventId initialRange:0.1 andRange:0.9]; + [videoUploader uploadData:videoData mimeType:videoInfo[@"mimetype"] success:^(NSString *url) { + [MediaManager removeUploaderWithId:localEvent.eventId]; [videoContent setValue:url forKey:@"url"]; [videoContent setValue:videoInfo forKey:@"info"]; [videoContent setValue:@"Video" forKey:@"body"]; [self sendMessage:videoContent withLocalEvent:localEvent]; } failure:^(NSError *error) { - [UploadManager removeURL:dummyURL]; + NSLog(@"Video upload failed"); + [MediaManager removeUploaderWithId:localEvent.eventId]; [self handleError:error forLocalEvent:localEvent]; - } uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - [UploadManager onUploadProgress:dummyURL bytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite initialRange:0.1 range:0.9]; }]; } else { NSLog(@"Video is too large"); @@ -1107,7 +1104,6 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; } else { NSLog(@"Video export failed: %d", (int)[exportSession status]); - [UploadManager removeURL:dummyURL]; // remove tmp file (if any) [[NSFileManager defaultManager] removeItemAtPath:[tmpVideoLocation path] error:nil]; [self handleError:nil forLocalEvent:localEvent]; @@ -1115,10 +1111,8 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; }]; } failure:^(NSError *error) { NSLog(@"Video thumbnail upload failed"); - [UploadManager removeURL:dummyURL]; + [MediaManager removeUploaderWithId:localEvent.eventId]; [self handleError:error forLocalEvent:localEvent]; - } uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - [UploadManager onUploadProgress:dummyURL bytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite initialRange:0 range:0.1]; }]; } @@ -2103,7 +2097,7 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; } } -- (MXEvent*)addLocalEchoEventForAttachedImage:(UIImage*)image { +- (MXEvent*)addLocalEchoEventForAttachedMedia:(UIImage*)image { // Create new item MXEvent *localEvent = [self createLocalEchoEventWithoutContent]; @@ -2118,6 +2112,9 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; [tmpCachedAttachments addObject:cacheFilePath]; } + // Define a unique upload id (reuse the local event id) + [localEvent.content setValue:localEvent.eventId forKey:kRoomMessageUploadIdKey]; + // Prepare event content NSMutableDictionary *info = [[NSMutableDictionary alloc] init]; [info setValue:@"image/jpeg" forKey:@"mimetype"]; @@ -2392,7 +2389,7 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; - (void)sendImage:(UIImage*)image { // Add a temporary event while the image is attached (local echo) - MXEvent *localEvent = [self addLocalEchoEventForAttachedImage:image]; + MXEvent *localEvent = [self addLocalEchoEventForAttachedMedia:image]; // Prepare message to send NSMutableDictionary *imageInfo = [[NSMutableDictionary alloc] init]; [imageInfo setValue:@"image/jpeg" forKey:@"mimetype"]; @@ -2401,13 +2398,11 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; NSData *imageData = UIImageJPEGRepresentation(image, 0.8); [imageInfo setValue:[NSNumber numberWithUnsignedInteger:imageData.length] forKey:@"size"]; - NSString* dummyURL = [localEvent.content valueForKey:@"url"]; - - // Upload image - MatrixHandler *mxHandler = [MatrixHandler sharedHandler]; - [mxHandler.mxRestClient uploadContent:imageData mimeType:@"image/jpeg" timeout:30 success:^(NSString *url) { - [UploadManager removeURL:dummyURL]; - + // Upload data + MediaLoader *mediaLoader = [MediaManager prepareUploaderWithId:localEvent.eventId initialRange:0 andRange:1.0]; + [mediaLoader uploadData:imageData mimeType:@"image/jpeg" success:^(NSString *url) { + [MediaManager removeUploaderWithId:localEvent.eventId]; + NSMutableDictionary *imageMessage = [[NSMutableDictionary alloc] init]; [imageMessage setValue:@"m.image" forKey:@"msgtype"]; [imageMessage setValue:url forKey:@"url"]; @@ -2416,10 +2411,9 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; // Send message for this attachment [self sendMessage:imageMessage withLocalEvent:localEvent]; } failure:^(NSError *error) { - [UploadManager removeURL:dummyURL]; + [MediaManager removeUploaderWithId:localEvent.eventId]; + NSLog(@"Failed to upload image: %@", error); [self handleError:error forLocalEvent:localEvent]; - } uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - [UploadManager onUploadProgress:dummyURL bytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite initialRange:0 range:1.0]; }]; } diff --git a/matrixConsole/ViewController/SettingsViewController.m b/matrixConsole/ViewController/SettingsViewController.m index 30ceac8ff..4c47735b2 100644 --- a/matrixConsole/ViewController/SettingsViewController.m +++ b/matrixConsole/ViewController/SettingsViewController.m @@ -368,22 +368,18 @@ NSString* const kCommandsDescriptionText = @"The following commands are availabl if (uploadedPictureURL == nil) { // Upload picture - [mxHandler.mxRestClient uploadContent:UIImageJPEGRepresentation([self.userPicture imageForState:UIControlStateNormal], 0.5) - mimeType:@"image/jpeg" - timeout:30 - success:^(NSString *url) { - // Store uploaded picture url and trigger picture saving - uploadedPictureURL = url; - [self savePicture]; - } failure:^(NSError *error) { - NSLog(@"Upload image failed: %@", error); - [self stopUserInfoUploadAnimation]; - _userPicture.enabled = YES; - isAvatarUploading = NO; - [self handleErrorDuringPictureSaving:error]; - } uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - // need to display the progress ? - }]; + MediaLoader *uploader = [[MediaLoader alloc] initWithUploadId:nil initialRange:0 andRange:1.0]; + [uploader uploadData:UIImageJPEGRepresentation([self.userPicture imageForState:UIControlStateNormal], 0.5) mimeType:@"image/jpeg" success:^(NSString *url) { + // Store uploaded picture url and trigger picture saving + uploadedPictureURL = url; + [self savePicture]; + } failure:^(NSError *error) { + NSLog(@"Upload image failed: %@", error); + [self stopUserInfoUploadAnimation]; + _userPicture.enabled = YES; + isAvatarUploading = NO; + [self handleErrorDuringPictureSaving:error]; + }]; } else { [mxHandler.mxSession.myUser setAvatarUrl:uploadedPictureURL success:^{ @@ -473,7 +469,7 @@ NSString* const kCommandsDescriptionText = @"The following commands are availabl // Add observers [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil]; - imageLoader = [MediaManager downloadMedia:currentPictureURL mimeType:@"image/jpeg"]; + imageLoader = [MediaManager downloadMediaFromURL:currentPictureURL withType:@"image/jpeg"]; } } } else {