Add failure methods to thumbnail service.

pull/1/head
Matthew Chen 7 years ago
parent f6e792c707
commit 206432fdf0

@ -859,8 +859,12 @@ NS_ASSUME_NONNULL_BEGIN
stillImageView.image = [strongSelf stillImageView.image = [strongSelf
tryToLoadCellMedia:^{ tryToLoadCellMedia:^{
OWSCAssert([strongSelf.attachmentStream isImage]); OWSCAssert([strongSelf.attachmentStream isImage]);
return [strongSelf.attachmentStream thumbnailImageMediumWithCompletion:^(UIImage *image) { return [strongSelf.attachmentStream
thumbnailImageMediumWithSuccess:^(UIImage *image) {
weakImageView.image = image; weakImageView.image = image;
}
failure:^{
DDLogError(@"Could not load thumbnail.");
}]; }];
} }
mediaView:stillImageView mediaView:stillImageView

@ -35,7 +35,7 @@ public struct MediaGalleryItem: Equatable, Hashable {
public typealias AsyncThumbnailBlock = (UIImage) -> Void public typealias AsyncThumbnailBlock = (UIImage) -> Void
func thumbnailImage(async:@escaping AsyncThumbnailBlock) -> UIImage? { func thumbnailImage(async:@escaping AsyncThumbnailBlock) -> UIImage? {
return attachmentStream.thumbnailImageSmall(completion: async) return attachmentStream.thumbnailImageSmall(success: async, failure: {})
} }
var fullSizedImage: UIImage { var fullSizedImage: UIImage {

@ -323,10 +323,8 @@ typedef void (^OrphanDataBlock)(OWSOrphanData *);
OWSFail(@"%@ attachment has no file path.", self.logTag); OWSFail(@"%@ attachment has no file path.", self.logTag);
} }
NSString *_Nullable thumbnailPath = [attachmentStream legacyThumbnailPath]; [allAttachmentFilePaths
if (thumbnailPath.length > 0) { addObjectsFromArray:attachmentStream.allThumbnailPaths];
[allAttachmentFilePaths addObject:thumbnailPath];
}
}]; }];
if (shouldAbort) { if (shouldAbort) {

@ -227,7 +227,7 @@ NS_ASSUME_NONNULL_BEGIN
authorId:authorId authorId:authorId
body:quotedText body:quotedText
bodySource:TSQuotedMessageContentSourceLocal bodySource:TSQuotedMessageContentSourceLocal
thumbnailImage:quotedAttachment.legacyThumbnailImage thumbnailImage:quotedAttachment.thumbnailImageSmallSync
contentType:quotedAttachment.contentType contentType:quotedAttachment.contentType
sourceFilename:quotedAttachment.sourceFilename sourceFilename:quotedAttachment.sourceFilename
attachmentStream:quotedAttachment attachmentStream:quotedAttachment

@ -4,8 +4,33 @@
import Foundation import Foundation
@objc public class OWSLoadedThumbnail: NSObject {
public typealias DataSourceBlock = () throws -> Data
@objc public let image: UIImage
let dataSourceBlock: DataSourceBlock
@objc public init(image: UIImage, filePath: String) {
self.image = image
self.dataSourceBlock = {
return try Data(contentsOf: URL(fileURLWithPath: filePath))
}
}
@objc public init(image: UIImage, data: Data) {
self.image = image
self.dataSourceBlock = {
return data
}
}
@objc public func data() throws -> Data {
return try dataSourceBlock()
}
}
private struct OWSThumbnailRequest { private struct OWSThumbnailRequest {
public typealias SuccessBlock = (UIImage) -> Void public typealias SuccessBlock = (OWSLoadedThumbnail) -> Void
public typealias FailureBlock = () -> Void public typealias FailureBlock = () -> Void
let attachmentId: String let attachmentId: String
@ -28,7 +53,7 @@ private struct OWSThumbnailRequest {
@objc(shared) @objc(shared)
public static let shared = OWSThumbnailService() public static let shared = OWSThumbnailService()
public typealias SuccessBlock = (UIImage) -> Void public typealias SuccessBlock = (OWSLoadedThumbnail) -> Void
public typealias FailureBlock = () -> Void public typealias FailureBlock = () -> Void
private let serialQueue = DispatchQueue(label: "OWSThumbnailService") private let serialQueue = DispatchQueue(label: "OWSThumbnailService")
@ -97,9 +122,9 @@ private struct OWSThumbnailRequest {
} }
let thumbnailRequest = thumbnailRequestStack.removeLast() let thumbnailRequest = thumbnailRequestStack.removeLast()
if let image = process(thumbnailRequest: thumbnailRequest) { if let loadedThumbnail = process(thumbnailRequest: thumbnailRequest) {
DispatchQueue.main.async { DispatchQueue.main.async {
thumbnailRequest.success(image) thumbnailRequest.success(loadedThumbnail)
} }
} else { } else {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -109,7 +134,7 @@ private struct OWSThumbnailRequest {
} }
// This should only be called on the serialQueue. // This should only be called on the serialQueue.
private func process(thumbnailRequest: OWSThumbnailRequest) -> UIImage? { private func process(thumbnailRequest: OWSThumbnailRequest) -> OWSLoadedThumbnail? {
var possibleAttachment: TSAttachmentStream? var possibleAttachment: TSAttachmentStream?
self.dbConnection.read({ (transaction) in self.dbConnection.read({ (transaction) in
possibleAttachment = TSAttachmentStream.fetch(uniqueId: thumbnailRequest.attachmentId, transaction: transaction) possibleAttachment = TSAttachmentStream.fetch(uniqueId: thumbnailRequest.attachmentId, transaction: transaction)
@ -133,7 +158,7 @@ private struct OWSThumbnailRequest {
owsFail("Could not load thumbnail.") owsFail("Could not load thumbnail.")
return nil return nil
} }
return image return OWSLoadedThumbnail(image: image, filePath: filePath)
} }
} }
} }
@ -200,6 +225,6 @@ private struct OWSThumbnailRequest {
size: thumbnailSize, size: thumbnailSize,
transaction: transaction) transaction: transaction)
}) })
return thumbnailImage return OWSLoadedThumbnail(image: thumbnailImage, data: thumbnailData)
} }
} }

@ -17,7 +17,8 @@ NS_ASSUME_NONNULL_BEGIN
@class TSAttachmentPointer; @class TSAttachmentPointer;
@class YapDatabaseReadWriteTransaction; @class YapDatabaseReadWriteTransaction;
typedef void (^OWSThumbnailCompletion)(UIImage *image); typedef void (^OWSThumbnailSuccess)(UIImage *image);
typedef void (^OWSThumbnailFailure)(void);
@interface TSAttachmentThumbnail : MTLModel @interface TSAttachmentThumbnail : MTLModel
@ -66,10 +67,7 @@ typedef void (^OWSThumbnailCompletion)(UIImage *image);
- (nullable NSString *)originalFilePath; - (nullable NSString *)originalFilePath;
- (nullable NSURL *)originalMediaURL; - (nullable NSURL *)originalMediaURL;
// TODO: Rename to legacy... - (NSArray<NSString *> *)allThumbnailPaths;
- (nullable UIImage *)legacyThumbnailImage;
//- (nullable NSData *)legacyThumbnailData;
- (nullable NSString *)legacyThumbnailPath;
+ (BOOL)hasThumbnailForMimeType:(NSString *)contentType; + (BOOL)hasThumbnailForMimeType:(NSString *)contentType;
@ -96,16 +94,18 @@ typedef void (^OWSThumbnailCompletion)(UIImage *image);
// Non-nil for attachments which need "lazy backup restore." // Non-nil for attachments which need "lazy backup restore."
- (nullable OWSBackupFragment *)lazyRestoreFragment; - (nullable OWSBackupFragment *)lazyRestoreFragment;
#pragma mark - Thumbnails #pragma mark - Thumbnails
// On cache hit, the thumbnail will be returned synchronously and completion will never be invoked. // On cache hit, the thumbnail will be returned synchronously and completion will never be invoked.
// On cache miss, nil will be returned and the completion will be invoked async on main if // On cache miss, nil will be returned and the completion will be invoked async on main if
// thumbnail can be generated. // thumbnail can be generated.
- (nullable UIImage *)thumbnailImageWithSizeHint:(CGSize)sizeHint completion:(OWSThumbnailCompletion)completion; - (nullable UIImage *)thumbnailImageWithSizeHint:(CGSize)sizeHint
- (nullable UIImage *)thumbnailImageSmallWithCompletion:(OWSThumbnailCompletion)completion; completion:(OWSThumbnailSuccess)success
- (nullable UIImage *)thumbnailImageMediumWithCompletion:(OWSThumbnailCompletion)completion; failure:(OWSThumbnailFailure)failure;
- (nullable UIImage *)thumbnailImageLargeWithCompletion:(OWSThumbnailCompletion)completion; - (nullable UIImage *)thumbnailImageSmallWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure;
- (nullable UIImage *)thumbnailImageMediumWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure;
- (nullable UIImage *)thumbnailImageLargeWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure;
- (nullable UIImage *)thumbnailImageSmallSync;
// This method should only be invoked by OWSThumbnailService. // This method should only be invoked by OWSThumbnailService.
- (nullable NSString *)pathForThumbnail:(TSAttachmentThumbnail *)thumbnail; - (nullable NSString *)pathForThumbnail:(TSAttachmentThumbnail *)thumbnail;

@ -24,6 +24,8 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
return MAX(screenSizePoints.width, screenSizePoints.height); return MAX(screenSizePoints.width, screenSizePoints.height);
} }
typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
@implementation TSAttachmentThumbnail @implementation TSAttachmentThumbnail
- (instancetype)initWithFilename:(NSString *)filename - (instancetype)initWithFilename:(NSString *)filename
@ -128,18 +130,9 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
_creationTimestamp = [NSDate new]; _creationTimestamp = [NSDate new];
} }
// This is going to be slow the first time it runs.
[self ensureLegacyThumbnail];
return self; return self;
} }
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[super saveWithTransaction:transaction];
[self ensureLegacyThumbnail];
}
- (void)upgradeFromAttachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion - (void)upgradeFromAttachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion
{ {
[super upgradeFromAttachmentSchemaVersion:attachmentSchemaVersion]; [super upgradeFromAttachmentSchemaVersion:attachmentSchemaVersion];
@ -429,24 +422,6 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
[MIMETypeUtil isAnimated:contentType]); [MIMETypeUtil isAnimated:contentType]);
} }
- (nullable NSData *)legacyThumbnailData
{
NSString *thumbnailPath = self.legacyThumbnailPath;
if (!thumbnailPath) {
OWSAssert(!self.isImage && !self.isVideo && !self.isAnimated);
return nil;
}
if (![[NSFileManager defaultManager] fileExistsAtPath:thumbnailPath]) {
OWSFail(@"%@ missing thumbnail for attachmentId: %@", self.logTag, self.uniqueId);
return nil;
}
return [NSData dataWithContentsOfFile:thumbnailPath];
}
- (void)ensureLegacyThumbnail - (void)ensureLegacyThumbnail
{ {
NSString *thumbnailPath = self.legacyThumbnailPath; NSString *thumbnailPath = self.legacyThumbnailPath;
@ -764,7 +739,9 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
#pragma mark - Thumbnails #pragma mark - Thumbnails
- (nullable UIImage *)thumbnailImageWithSizeHint:(CGSize)sizeHint completion:(OWSThumbnailCompletion)completion - (nullable UIImage *)thumbnailImageWithSizeHint:(CGSize)sizeHint
success:(OWSThumbnailSuccess)success
failure:(OWSThumbnailFailure)failure
{ {
CGFloat maxDimensionHint = MAX(sizeHint.width, sizeHint.height); CGFloat maxDimensionHint = MAX(sizeHint.width, sizeHint.height);
NSUInteger thumbnailDimensionPoints; NSUInteger thumbnailDimensionPoints;
@ -776,26 +753,46 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
thumbnailDimensionPoints = ThumbnailDimensionPointsLarge(); thumbnailDimensionPoints = ThumbnailDimensionPointsLarge();
} }
return [self thumbnailImageWithThumbnailDimensionPoints:thumbnailDimensionPoints completion:completion]; return [self thumbnailImageWithThumbnailDimensionPoints:thumbnailDimensionPoints success:success failure:failure];
} }
- (nullable UIImage *)thumbnailImageSmallWithCompletion:(OWSThumbnailCompletion)completion - (nullable UIImage *)thumbnailImageSmallWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure
{ {
return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsSmall completion:completion]; return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsSmall
success:success
failure:failure];
} }
- (nullable UIImage *)thumbnailImageMediumWithCompletion:(OWSThumbnailCompletion)completion - (nullable UIImage *)thumbnailImageMediumWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure
{ {
return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsMedium completion:completion]; return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsMedium
success:success
failure:failure];
} }
- (nullable UIImage *)thumbnailImageLargeWithCompletion:(OWSThumbnailCompletion)completion - (nullable UIImage *)thumbnailImageLargeWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure
{ {
return [self thumbnailImageWithThumbnailDimensionPoints:ThumbnailDimensionPointsLarge() completion:completion]; return [self thumbnailImageWithThumbnailDimensionPoints:ThumbnailDimensionPointsLarge()
success:success
failure:failure];
} }
- (nullable UIImage *)thumbnailImageWithThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints - (nullable UIImage *)thumbnailImageWithThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints
completion:(OWSThumbnailCompletion)completion success:(OWSThumbnailSuccess)success
failure:(OWSThumbnailFailure)failure
{
OWSLoadedThumbnail *_Nullable loadedThumbnail;
loadedThumbnail = [self loadedThumbnailWithThumbnailDimensionPoints:thumbnailDimensionPoints
success:^(OWSLoadedThumbnail *loadedThumbnail) {
success(loadedThumbnail.image);
}
failure:failure];
return loadedThumbnail.image;
}
- (nullable OWSLoadedThumbnail *)loadedThumbnailWithThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints
success:(OWSLoadedThumbnailSuccess)success
failure:(OWSThumbnailFailure)failure
{ {
CGSize originalSize = self.imageSize; CGSize originalSize = self.imageSize;
if (originalSize.width < 1 || originalSize.height < 1) { if (originalSize.width < 1 || originalSize.height < 1) {
@ -804,7 +801,7 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
if (originalSize.width <= thumbnailDimensionPoints || originalSize.height <= thumbnailDimensionPoints) { if (originalSize.width <= thumbnailDimensionPoints || originalSize.height <= thumbnailDimensionPoints) {
// There's no point in generating a thumbnail if the original is smaller than the // There's no point in generating a thumbnail if the original is smaller than the
// thumbnail size. // thumbnail size.
return self.originalImage; return [[OWSLoadedThumbnail alloc] initWithImage:self.originalImage filePath:self.originalFilePath];
} }
for (TSAttachmentThumbnail *thumbnail in self.thumbnails) { for (TSAttachmentThumbnail *thumbnail in self.thumbnails) {
@ -817,15 +814,77 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
continue; continue;
} }
UIImage *_Nullable image = [UIImage imageWithContentsOfFile:thumbnailPath]; UIImage *_Nullable image = [UIImage imageWithContentsOfFile:thumbnailPath];
return image; if (!image) {
OWSFail(@"couldn't load image.");
continue;
}
return [[OWSLoadedThumbnail alloc] initWithImage:image filePath:thumbnailPath];
} }
[OWSThumbnailService.shared ensureThumbnailForAttachmentIdWithAttachmentId:self.uniqueId [OWSThumbnailService.shared ensureThumbnailForAttachmentIdWithAttachmentId:self.uniqueId
thumbnailDimensionPoints:thumbnailDimensionPoints thumbnailDimensionPoints:thumbnailDimensionPoints
completion:completion]; success:success
failure:failure];
return nil; return nil;
} }
- (nullable OWSLoadedThumbnail *)loadedThumbnailSmallSync
{
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block OWSLoadedThumbnail *_Nullable loadedThumbnail = nil;
loadedThumbnail = [self loadedThumbnailWithThumbnailDimensionPoints:kThumbnailDimensionPointsSmall
success:^(OWSLoadedThumbnail *asyncLoadedThumbnail) {
@synchronized(self) {
loadedThumbnail = asyncLoadedThumbnail;
}
dispatch_semaphore_signal(semaphore);
}
failure:^{
dispatch_semaphore_signal(semaphore);
}];
// Wait up to five seconds.
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)));
@synchronized(self) {
return loadedThumbnail;
}
}
- (nullable UIImage *)thumbnailImageSmallSync
{
return [self loadedThumbnailSmallSync].image;
}
- (nullable NSData *)thumbnailDataSmallSync
{
NSError *error;
NSData *_Nullable data = [[self loadedThumbnailSmallSync] dataAndReturnError:&error];
if (error || !data) {
OWSFail(@"Couldn't load thumbnail data: %@", error);
return nil;
}
return data;
}
- (NSArray<NSString *> *)allThumbnailPaths
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (TSAttachmentThumbnail *thumbnail in self.thumbnails) {
NSString *_Nullable thumbnailPath = [self pathForThumbnail:thumbnail];
if (!thumbnailPath) {
OWSFail(@"Missing thumbnail path.");
continue;
}
[result addObject:thumbnailPath];
}
NSString *_Nullable legacyThumbnailPath = self.legacyThumbnailPath;
if (legacyThumbnailPath && [[NSFileManager defaultManager] fileExistsAtPath:legacyThumbnailPath]) {
[result addObject:legacyThumbnailPath];
}
return result;
}
#pragma mark - Update With... Methods #pragma mark - Update With... Methods
- (void)markForLazyRestoreWithFragment:(OWSBackupFragment *)lazyRestoreFragment - (void)markForLazyRestoreWithFragment:(OWSBackupFragment *)lazyRestoreFragment
@ -858,7 +917,7 @@ const NSUInteger ThumbnailDimensionPointsLarge() {
- (nullable TSAttachmentStream *)cloneAsThumbnail - (nullable TSAttachmentStream *)cloneAsThumbnail
{ {
NSData *thumbnailData = self.legacyThumbnailData; NSData *_Nullable thumbnailData = self.thumbnailDataSmallSync;
// Only some media types have thumbnails // Only some media types have thumbnails
if (!thumbnailData) { if (!thumbnailData) {
return nil; return nil;

Loading…
Cancel
Save