diff --git a/matrixConsole/MediaManager.h b/matrixConsole/MediaManager.h index 5e9e077b1..0d3cb42a1 100644 --- a/matrixConsole/MediaManager.h +++ b/matrixConsole/MediaManager.h @@ -40,7 +40,6 @@ extern NSString *const kMediaDownloadDidFinishNotification; extern NSString *const kMediaDownloadDidFailNotification; // The callback blocks -typedef void (^blockMediaManager_onImageReady)(UIImage *image); typedef void (^blockMediaManager_onMediaReady)(NSString *cacheFilePath); typedef void (^blockMediaManager_onError)(NSError *error); @@ -50,15 +49,12 @@ typedef void (^blockMediaManager_onError)(NSError *error); + (UIImage *)resize:(UIImage *)image toFitInSize:(CGSize)size; -// get a picture from the local cache -// do not start any remote requests +// Load a picture from the local cache (Do not start any remote requests) + (UIImage*)loadCachePicture:(NSString*)pictureURL; -// Load a picture from the local cache or download it if it is not available yet. -// In this second case a mediaLoader reference is returned in order to let the user cancel this action. -+ (id)loadPicture:(NSString *)pictureURL - success:(blockMediaManager_onImageReady)success - failure:(blockMediaManager_onError)failure; +// Launch picture downloading. Return a mediaLoader reference in order to let the user cancel this action. ++ (id)downloadPicture:(NSString*)pictureURL; + // Prepare a media from the local cache or download it if it is not available yet. // In this second case a mediaLoader reference is returned in order to let the user cancel this action. + (id)prepareMedia:(NSString *)mediaURL diff --git a/matrixConsole/MediaManager.m b/matrixConsole/MediaManager.m index e0397b194..dc12f36ef 100644 --- a/matrixConsole/MediaManager.m +++ b/matrixConsole/MediaManager.m @@ -36,7 +36,7 @@ static NSString *mediaDir = @"mediacache"; static MediaManager *sharedMediaManager = nil; @interface MediaLoader : NSObject { - NSString *mediaURL; + NSString *mimeType; blockMediaManager_onMediaReady onMediaReady; @@ -54,45 +54,14 @@ static MediaManager *sharedMediaManager = nil; NSTimer* progressCheckTimer; } -+ (MediaLoader*)mediaLoaderForURL:(NSString*)url; - @property (strong, nonatomic) NSMutableDictionary* downloadStatsDict; +@property (strong, readonly) NSString *mediaURL; @end #pragma mark - MediaLoader @implementation MediaLoader -@synthesize downloadStatsDict; - -// find a MediaLoader -static NSMutableDictionary* pendingMediaLoadersByURL = nil; - -+ (MediaLoader*)mediaLoaderForURL:(NSString*)url { - MediaLoader* res = nil; - - if (pendingMediaLoadersByURL && url) { - res = [pendingMediaLoadersByURL valueForKey:url]; - } - - return res; -} - -+ (void) setMediaLoader:(MediaLoader*)mediaLoader forURL:(NSString*)url { - if (!pendingMediaLoadersByURL) { - pendingMediaLoadersByURL = [[NSMutableDictionary alloc] init]; - } - - // sanity check - if (mediaLoader && url) { - [pendingMediaLoadersByURL setValue:mediaLoader forKey:url]; - } -} - -+ (void) removeMediaLoaderWithUrl:(NSString*)url { - if (url) { - [pendingMediaLoadersByURL removeObjectForKey:url]; - } -} +@synthesize downloadStatsDict, mediaURL; - (NSString*)validateContentURL:(NSString*)contentURL { // Detect matrix content url @@ -105,41 +74,6 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; return contentURL; } - -- (void)downloadPicture:(NSString*)pictureURL - success:(blockMediaManager_onImageReady)success - failure:(blockMediaManager_onError)failure { - - [MediaLoader setMediaLoader:self forURL:pictureURL]; - - // Download picture content - [self downloadMedia:pictureURL mimeType:@"image/jpeg" success:^(NSString *cacheFilePath) { - [MediaLoader removeMediaLoaderWithUrl:pictureURL]; - - if (success) { - NSData* imageContent = [NSData dataWithContentsOfFile:cacheFilePath options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:nil]; - if (imageContent) { - UIImage *image = [UIImage imageWithData:imageContent]; - if (image) { - success(image); - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFinishNotification object:pictureURL userInfo:nil]; - } else { - NSLog(@"ERROR: picture download failed: %@", pictureURL); - if (failure){ - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:pictureURL userInfo:nil]; - failure(nil); - } - } - } - } - } failure:^(NSError *error) { - [MediaLoader removeMediaLoaderWithUrl:pictureURL]; - failure(error); - NSLog(@"Failed to download image (%@): %@", pictureURL, error); - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:pictureURL userInfo:nil]; - }]; -} - - (void)downloadMedia:(NSString*)aMediaURL mimeType:(NSString *)aMimeType success:(blockMediaManager_onMediaReady)success @@ -153,8 +87,6 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; downloadStartTime = statsStartTime = CFAbsoluteTimeGetCurrent(); lastProgressEventTimeStamp = -1; - [MediaLoader setMediaLoader:self forURL:mediaURL]; - // Start downloading NSURL *url = [NSURL URLWithString:[self validateContentURL:aMediaURL]]; downloadData = [[NSMutableData alloc] init]; @@ -162,7 +94,6 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; } - (void)cancel { - [MediaLoader removeMediaLoaderWithUrl:mediaURL]; // Reset blocks onMediaReady = nil; onError = nil; @@ -186,15 +117,9 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"ERROR: media download failed: %@, %@", error, mediaURL); - - [MediaLoader removeMediaLoaderWithUrl:mediaURL]; - // send the latest known upload info [self progressCheckTimeout:nil]; downloadStatsDict = nil; - - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:mediaURL userInfo:nil]; - if (onError) { onError (error); } @@ -295,8 +220,6 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { - [MediaLoader removeMediaLoaderWithUrl:mediaURL]; - // send the latest known upload info [self progressCheckTimeout:nil]; downloadStatsDict = nil; @@ -308,14 +231,11 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; if (onMediaReady) { onMediaReady(cacheFilePath); } - - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFinishNotification object:mediaURL userInfo:nil]; } else { NSLog(@"ERROR: media download failed: %@", mediaURL); if (onError){ onError(nil); } - [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:mediaURL userInfo:nil]; } downloadData = nil; @@ -328,6 +248,9 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; @implementation MediaManager +// Table of mediaLoaders in progress +static NSMutableDictionary* pendingMediaLoadersByURL = nil; + + (id)sharedInstance { @synchronized(self) { if(sharedMediaManager == nil) @@ -374,32 +297,23 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; return resizedImage; } -// Load a picture from the local cache or download it if it is not available yet. -// In this second case a mediaLoader reference is returned in order to let the user cancel this action. -+ (id)loadPicture:(NSString*)pictureURL - success:(blockMediaManager_onImageReady)success - failure:(blockMediaManager_onError)failure { - id ret = nil; - // Check cached pictures - UIImage *image = [MediaManager loadCachePicture:pictureURL]; - if (image) { - if (success) { - // Reply synchronously - success (image); - } - } - else if ([pictureURL hasPrefix:kMediaManagerPrefixForDummyURL] == NO) { ++ (id)downloadPicture:(NSString*)pictureURL { + if ([pictureURL hasPrefix:kMediaManagerPrefixForDummyURL] == NO) { // Create a media loader to download picture MediaLoader *mediaLoader = [[MediaLoader alloc] init]; - [mediaLoader downloadPicture:pictureURL success:success failure:failure]; - ret = mediaLoader; - } else { - NSLog(@"Load tmp picture from cache failed: %@", pictureURL); - if (failure){ - failure(nil); - } + [self setMediaLoader:mediaLoader forURL:pictureURL]; + [mediaLoader downloadMedia:pictureURL mimeType:@"image/jpeg" success:^(NSString *cacheFilePath) { + [self removeMediaLoaderWithUrl:pictureURL]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFinishNotification object:pictureURL userInfo:nil]; + } failure:^(NSError *error) { + [self removeMediaLoaderWithUrl:pictureURL]; + NSLog(@"Failed to download image (%@): %@", pictureURL, error); + [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:pictureURL userInfo:nil]; + }]; + return mediaLoader; } - return ret; + [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:pictureURL userInfo:nil]; + return nil; } + (id)prepareMedia:(NSString *)mediaURL @@ -429,9 +343,29 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; return ret; } -// try to find out a media loder from a media URL +// try to find out a media loader from a media URL + (id)mediaLoaderForURL:(NSString*)url { - return [MediaLoader mediaLoaderForURL:url]; + if (pendingMediaLoadersByURL && url) { + return [pendingMediaLoadersByURL valueForKey:url]; + } + return nil; +} + ++ (void)setMediaLoader:(MediaLoader*)mediaLoader forURL:(NSString*)url { + if (!pendingMediaLoadersByURL) { + pendingMediaLoadersByURL = [[NSMutableDictionary alloc] init]; + } + + // sanity check + if (mediaLoader && url) { + [pendingMediaLoadersByURL setValue:mediaLoader forKey:url]; + } +} + ++ (void)removeMediaLoaderWithUrl:(NSString*)url { + if (url) { + [pendingMediaLoadersByURL removeObjectForKey:url]; + } } + (NSDictionary*)downloadStatsDict:(id)mediaLoader { @@ -440,6 +374,12 @@ static NSMutableDictionary* pendingMediaLoadersByURL = nil; + (void)cancel:(id)mediaLoader { [((MediaLoader*)mediaLoader) cancel]; + NSString *mediaURL = ((MediaLoader*)mediaLoader).mediaURL; + if (mediaURL) { + [self removeMediaLoaderWithUrl:mediaURL]; + NSLog(@"Image download has been cancelled (%@)", mediaURL); + [[NSNotificationCenter defaultCenter] postNotificationName:kMediaDownloadDidFailNotification object:mediaURL userInfo:nil]; + } } + (NSString*)cacheMediaData:(NSData*)mediaData forURL:(NSString *)mediaURL mimeType:(NSString *)mimeType { diff --git a/matrixConsole/View/CustomImageView.m b/matrixConsole/View/CustomImageView.m index 1c1620b15..ae8e4b955 100644 --- a/matrixConsole/View/CustomImageView.m +++ b/matrixConsole/View/CustomImageView.m @@ -414,6 +414,12 @@ [[NSNotificationCenter defaultCenter] removeObserver:self]; imageURL = anImageURL; + if (!imageURL) { + // Set preview by default + self.image = previewImage; + return; + } + // Check whether the image download is in progress id loader = [MediaManager mediaLoaderForURL:imageURL]; if (loader) { @@ -443,11 +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 loadPicture:imageURL - success:^(UIImage *anImage) { - } - failure:^(NSError *error) { - }]; + [MediaManager downloadPicture:imageURL]; } } } diff --git a/matrixConsole/ViewController/MemberViewController.m b/matrixConsole/ViewController/MemberViewController.m index fa5e8cbd2..ddad23476 100644 --- a/matrixConsole/ViewController/MemberViewController.m +++ b/matrixConsole/ViewController/MemberViewController.m @@ -52,6 +52,8 @@ self.actionMenu = nil; } + // Remove any pending observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; if (imageLoader) { [MediaManager cancel:imageLoader]; imageLoader = nil; @@ -137,6 +139,8 @@ - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; + // Remove any pending observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; if (imageLoader) { [MediaManager cancel:imageLoader]; imageLoader = nil; @@ -149,28 +153,73 @@ } } -- (void) updateMemberInfo { +- (void)updateMemberInfo { self.title = _mxRoomMember.displayname ? _mxRoomMember.displayname : _mxRoomMember.userId; // set the thumbnail info [[self.memberThumbnailButton imageView] setContentMode: UIViewContentModeScaleAspectFill]; [[self.memberThumbnailButton imageView] setClipsToBounds:YES]; + // Remove any pending observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; if (_mxRoomMember.avatarUrl) { - imageLoader = [MediaManager loadPicture:_mxRoomMember.avatarUrl - success:^(UIImage *image) { - [self.memberThumbnailButton setImage:image forState:UIControlStateNormal]; - [self.memberThumbnailButton setImage:image forState:UIControlStateHighlighted]; - } - failure:^(NSError *error) { - NSLog(@"Failed to download image (%@): %@", _mxRoomMember.avatarUrl, error); - }]; + // Check whether the image download is in progress + id loader = [MediaManager mediaLoaderForURL:_mxRoomMember.avatarUrl]; + if (loader) { + // Add observers + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil]; + } else { + // Retrieve the image from cache + UIImage* image = [MediaManager loadCachePicture:_mxRoomMember.avatarUrl]; + if (image) { + [self.memberThumbnailButton setImage:image forState:UIControlStateNormal]; + [self.memberThumbnailButton setImage:image forState:UIControlStateHighlighted]; + } else { + // Cancel potential download in progress + if (imageLoader) { + [MediaManager cancel:imageLoader]; + } + // 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 downloadPicture:_mxRoomMember.avatarUrl]; + } + } + } else { + UIImage *image = [UIImage imageNamed:@"default-profile"]; + if (image) { + [self.memberThumbnailButton setImage:image forState:UIControlStateNormal]; + [self.memberThumbnailButton setImage:image forState:UIControlStateHighlighted]; + } } self.roomMemberMID.text = _mxRoomMember.userId; } +- (void)onMediaDownloadEnd:(NSNotification *)notif { + // sanity check + if ([notif.object isKindOfClass:[NSString class]]) { + NSString* url = notif.object; + + if ([url isEqualToString:_mxRoomMember.avatarUrl]) { + // update the image + UIImage* image = [MediaManager loadCachePicture:_mxRoomMember.avatarUrl]; + if (image == nil) { + image = [UIImage imageNamed:@"default-profile"]; + } + if (image) { + [self.memberThumbnailButton setImage:image forState:UIControlStateNormal]; + [self.memberThumbnailButton setImage:image forState:UIControlStateHighlighted]; + } + // remove the observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; + imageLoader = nil; + } + } +} + - (void)setRoomMember:(MXRoomMember*) aRoomMember { // ignore useless update if (![_mxRoomMember.userId isEqualToString:aRoomMember.userId]) { diff --git a/matrixConsole/ViewController/SettingsViewController.m b/matrixConsole/ViewController/SettingsViewController.m index 704857679..f2fe4929b 100644 --- a/matrixConsole/ViewController/SettingsViewController.m +++ b/matrixConsole/ViewController/SettingsViewController.m @@ -197,6 +197,8 @@ NSString* const kCommandsDescriptionText = @"The following commands are availabl } - (void)reset { + // Remove observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; // Cancel picture loader (if any) if (imageLoader) { [MediaManager cancel:imageLoader]; @@ -434,6 +436,8 @@ NSString* const kCommandsDescriptionText = @"The following commands are availabl - (void)updateUserPicture:(NSString *)avatar_url { if (currentPictureURL == nil || [currentPictureURL isEqualToString:avatar_url] == NO) { + // Remove any pending observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; // Cancel previous loader (if any) if (imageLoader) { [MediaManager cancel:imageLoader]; @@ -442,13 +446,28 @@ NSString* const kCommandsDescriptionText = @"The following commands are availabl currentPictureURL = [avatar_url isEqual:[NSNull null]] ? nil : avatar_url; if (currentPictureURL) { - // Load user's picture - imageLoader = [MediaManager loadPicture:currentPictureURL success:^(UIImage *image) { - [self updateAvatarImage:image]; - } failure:^(NSError *error) { - // Reset picture URL in order to try next time - currentPictureURL = nil; - }]; + // Check whether the image download is in progress + id loader = [MediaManager mediaLoaderForURL:currentPictureURL]; + if (loader) { + // Add observers + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFinishNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMediaDownloadDidFailNotification object:nil]; + } else { + // Retrieve the image from cache + UIImage* image = [MediaManager loadCachePicture:currentPictureURL]; + if (image) { + [self updateAvatarImage:image]; + } else { + // Cancel potential download in progress + if (imageLoader) { + [MediaManager cancel:imageLoader]; + } + // 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 downloadPicture:currentPictureURL]; + } + } } else { // Set placeholder [self updateAvatarImage:[UIImage imageNamed:@"default-profile"]]; @@ -456,6 +475,31 @@ NSString* const kCommandsDescriptionText = @"The following commands are availabl } } +- (void)onMediaDownloadEnd:(NSNotification *)notif { + // sanity check + if ([notif.object isKindOfClass:[NSString class]]) { + NSString* url = notif.object; + + if ([url isEqualToString:currentPictureURL]) { + // update the image + UIImage* image = [MediaManager loadCachePicture:currentPictureURL]; + if (image == nil) { + image = [UIImage imageNamed:@"default-profile"]; + } + [self updateAvatarImage:image]; + + // remove the observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; + imageLoader = nil; + + if ([notif.name isEqualToString:kMediaDownloadDidFailNotification]) { + // Reset picture URL in order to try next time + currentPictureURL = nil; + } + } + } +} + #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {