diff --git a/matrixConsole.xcodeproj/project.pbxproj b/matrixConsole.xcodeproj/project.pbxproj index 41e09490f..62ef93e3c 100644 --- a/matrixConsole.xcodeproj/project.pbxproj +++ b/matrixConsole.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 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 */; }; @@ -72,6 +73,8 @@ 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 = ""; }; @@ -207,6 +210,8 @@ F021FBEE1A5EF57300EA3AE6 /* MediaLoader.m */, F021FBF01A5F1F8E00EA3AE6 /* MediaManager.h */, F021FBF11A5F1F8E00EA3AE6 /* MediaManager.m */, + 71E94A781A5FB6D3009F52E5 /* UploadManager.h */, + 71E94A791A5FB6D3009F52E5 /* UploadManager.m */, ); path = API; sourceTree = ""; @@ -505,6 +510,7 @@ 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.m b/matrixConsole/API/MediaLoader.m index 75a012eab..e426a29e7 100644 --- a/matrixConsole/API/MediaLoader.m +++ b/matrixConsole/API/MediaLoader.m @@ -143,28 +143,8 @@ NSString *const kMediaLoaderProgressDownloadRateKey = @"kMediaLoaderProgressDown NSString* progressString = [NSString stringWithFormat:@"%@ / %@", [NSByteCountFormatter stringFromByteCount:downloadData.length countStyle:NSByteCountFormatterCountStyleFile], [NSByteCountFormatter stringFromByteCount:expectedSize countStyle:NSByteCountFormatterCountStyleFile]]; [dict setValue:progressString forKey:kMediaLoaderProgressStringKey]; - - NSMutableString* remaingTimeStr = [[NSMutableString alloc] init]; - - if (dataRemainingTime < 1) { - [remaingTimeStr appendString:@"< 1s"]; - } else if (dataRemainingTime < 60) - { - [remaingTimeStr appendFormat:@"%ds", (int)dataRemainingTime]; - } - else if (dataRemainingTime < 3600) - { - [remaingTimeStr appendFormat:@"%dm %2ds", (int)(dataRemainingTime/60), ((int)dataRemainingTime) % 60]; - } - else if (dataRemainingTime >= 3600) - { - [remaingTimeStr appendFormat:@"%dh %dm %ds", (int)(dataRemainingTime / 3600), - ((int)(dataRemainingTime) % 3600) / 60, - (int)(dataRemainingTime) % 60]; - } - [remaingTimeStr appendString:@" left"]; - - [dict setValue:remaingTimeStr forKey:kMediaLoaderProgressRemaingTimeKey]; + + [dict setValue:[MediaManager formatSecondsInterval:dataRemainingTime] forKey:kMediaLoaderProgressRemaingTimeKey]; NSString* downloadRateStr = [NSString stringWithFormat:@"%@/s", [NSByteCountFormatter stringFromByteCount:meanRate * 1024 countStyle:NSByteCountFormatterCountStyleFile]]; [dict setValue:downloadRateStr forKey:kMediaLoaderProgressDownloadRateKey]; diff --git a/matrixConsole/API/MediaManager.h b/matrixConsole/API/MediaManager.h index 219b55a13..91de7c5b4 100644 --- a/matrixConsole/API/MediaManager.h +++ b/matrixConsole/API/MediaManager.h @@ -28,6 +28,8 @@ extern NSString *const kMediaDownloadDidFailNotification; + (id)sharedInstance; ++ (NSString*)formatSecondsInterval:(CGFloat)secondsInterval; + + (UIImage *)resize:(UIImage *)image toFitInSize:(CGSize)size; // Load a picture from the local cache (Do not start any remote requests) diff --git a/matrixConsole/API/MediaManager.m b/matrixConsole/API/MediaManager.m index 6abcbb83a..b14f53936 100644 --- a/matrixConsole/API/MediaManager.m +++ b/matrixConsole/API/MediaManager.m @@ -39,6 +39,30 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; return sharedMediaManager; } ++ (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; +} + + (UIImage *)resize:(UIImage *)image toFitInSize:(CGSize)size { UIImage *resizedImage = image; diff --git a/matrixConsole/API/UploadManager.h b/matrixConsole/API/UploadManager.h new file mode 100644 index 000000000..073b61abb --- /dev/null +++ b/matrixConsole/API/UploadManager.h @@ -0,0 +1,37 @@ +/* + 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 new file mode 100644 index 000000000..4780e1a5d --- /dev/null +++ b/matrixConsole/API/UploadManager.m @@ -0,0 +1,112 @@ +/* + 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" + +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:[MediaManager 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/View/RoomMessageTableCell.m b/matrixConsole/View/RoomMessageTableCell.m index d23a69d3e..d3c30ed7c 100644 --- a/matrixConsole/View/RoomMessageTableCell.m +++ b/matrixConsole/View/RoomMessageTableCell.m @@ -17,7 +17,7 @@ #import "RoomMessageTableCell.h" #import "MediaManager.h" #import "PieChartView.h" - +#import "UploadManager.h" @implementation RoomMessageTableCell @@ -160,7 +160,15 @@ self.activityIndicator.hidden = NO; [self.activityIndicator startAnimating]; - [self initUploadProgressTo:self.message.uploadProgress]; + NSDictionary* uploadDict = [UploadManager statsInfoForURL:self.message.attachmentURL]; + + if (uploadDict) { + self.activityIndicator.hidden = YES; + [self updateProgressUI:uploadDict]; + } else { + self.activityIndicator.hidden = NO; + self.progressView.hidden = YES; + } } -(void)stopAnimating { @@ -174,22 +182,16 @@ NSString* url = notif.object; if ([url isEqualToString:self.message.thumbnailURL] || [url isEqualToString:self.message.attachmentURL]) { + [self updateProgressUI:notif.userInfo]; + + // the upload is ended + if (self.progressChartView.progress == 1.0) { + self.progressView.hidden = YES; + } } } } -- (void) initUploadProgressTo:(CGFloat)progress { - // nothing to display - if (progress <= 0) { - self.activityIndicator.hidden = NO; - } else { - self.message.uploadProgress = progress; - self.activityIndicator.hidden = YES; - - self.progressView.hidden = NO; - self.progressChartView.progress = progress; - } -} - (void)layoutSubviews { [super layoutSubviews]; diff --git a/matrixConsole/ViewController/RoomViewController.m b/matrixConsole/ViewController/RoomViewController.m index 22dda186f..7cb743044 100644 --- a/matrixConsole/ViewController/RoomViewController.m +++ b/matrixConsole/ViewController/RoomViewController.m @@ -29,6 +29,7 @@ #import "AppSettings.h" #import "MediaManager.h" +#import "UploadManager.h" #define ROOMVIEWCONTROLLER_TYPING_TIMEOUT_MS 20000 @@ -988,11 +989,14 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; // Create the local event displayed during uploading MXEvent *localEvent = [self addLocalEchoEventForAttachedImage:thumbnail]; - __block NSString* dummyURL = [localEvent.content valueForKey:@"url"]; + NSString* dummyURL = [localEvent.content valueForKey:@"url"]; // Upload thumbnail MatrixHandler *mxHandler = [MatrixHandler sharedHandler]; [mxHandler.mxRestClient uploadContent:thumbnailData mimeType:@"image/jpeg" timeout:30 success:^(NSString *url) { + + [UploadManager removeURL:dummyURL]; + // Prepare content of attached video NSMutableDictionary *videoContent = [[NSMutableDictionary alloc] init]; NSMutableDictionary *videoInfo = [[NSMutableDictionary alloc] init]; @@ -1047,26 +1051,16 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; 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]; [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]; [self handleError:error forLocalEvent:localEvent]; } uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - - // only one parameter by now - // but assume more could be expected (like download rates, remaining time...) - NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; - - CGFloat progress = ((float)totalBytesWritten) / ((float)totalBytesExpectedToWrite); - - // assume that the thumbnail is about 10% of the video - // the video is about 90 % - [dict setValue:[NSNumber numberWithFloat: 0.1 + (progress * 0.9)] forKey:kMediaLoaderProgressRateKey]; - - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaUploadProgressNotification object:dummyURL userInfo:dict]; - + [UploadManager onUploadProgress:dummyURL bytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite initialRange:0.1 range:0.9]; }]; } else { NSLog(@"Video is too large"); @@ -1079,6 +1073,7 @@ 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]; @@ -1086,18 +1081,10 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; }]; } failure:^(NSError *error) { NSLog(@"Video thumbnail upload failed"); + [UploadManager removeURL:dummyURL]; [self handleError:error forLocalEvent:localEvent]; } uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - // only one parameter by now - // but assume more could be expected (like download rates, remaining time...) - NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; - - CGFloat progress = ((float)totalBytesWritten) / ((float)totalBytesExpectedToWrite); - - // assume that the thumbnail is about 10% of the video - [dict setValue:[NSNumber numberWithFloat: (progress * 0.1)] forKey:kMediaLoaderProgressRateKey]; - - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaUploadProgressNotification object:dummyURL userInfo:dict]; + [UploadManager onUploadProgress:dummyURL bytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite initialRange:0 range:0.1]; }]; } @@ -2279,11 +2266,13 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; NSData *imageData = UIImageJPEGRepresentation(image, 0.8); [imageInfo setValue:[NSNumber numberWithUnsignedInteger:imageData.length] forKey:@"size"]; - __block NSString* dummyURL = [localEvent.content valueForKey:@"url"]; + 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]; + NSMutableDictionary *imageMessage = [[NSMutableDictionary alloc] init]; [imageMessage setValue:@"m.image" forKey:@"msgtype"]; [imageMessage setValue:url forKey:@"url"]; @@ -2292,14 +2281,10 @@ NSString *const kCmdResetUserPowerLevel = @"/deop"; // Send message for this attachment [self sendMessage:imageMessage withLocalEvent:localEvent]; } failure:^(NSError *error) { + [UploadManager removeURL:dummyURL]; [self handleError:error forLocalEvent:localEvent]; } uploadProgress:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - // only one parameter by now - // but assume more could be expected (like download rates, remaining time...) - NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; - [dict setValue:[NSNumber numberWithFloat: ((float)totalBytesWritten) / ((float)totalBytesExpectedToWrite)] forKey:kMediaLoaderProgressRateKey]; - - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaUploadProgressNotification object:dummyURL userInfo:dict]; + [UploadManager onUploadProgress:dummyURL bytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite initialRange:0 range:1.0]; }]; }