Added incoming gif previews
This commit is contained in:
parent
54de8bc221
commit
815c8a97fb
|
@ -43,7 +43,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
|
|||
#pragma mark - Media Album
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSString *caption;
|
||||
@property (nonatomic, readonly, nullable) NSString *albumMessageId;
|
||||
@property (nonatomic, nullable) NSString *albumMessageId;
|
||||
- (nullable TSMessage *)fetchAlbumMessageWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
|
||||
// `migrateAlbumMessageId` is only used in the migration to the new multi-attachment message scheme,
|
||||
|
|
|
@ -290,6 +290,7 @@ NSUInteger const TSAttachmentSchemaVersion = 4;
|
|||
|
||||
#pragma mark - Relationships
|
||||
|
||||
|
||||
- (nullable TSMessage *)fetchAlbumMessageWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
if (self.albumMessageId == nil) {
|
||||
|
|
|
@ -13,6 +13,8 @@ public enum LinkPreviewError: Int, Error {
|
|||
case couldNotDownload
|
||||
case featureDisabled
|
||||
case invalidContent
|
||||
case invalidMediaContent
|
||||
case attachmentFailedToSave
|
||||
}
|
||||
|
||||
// MARK: - OWSLinkPreviewDraft
|
||||
|
@ -81,12 +83,17 @@ public class OWSLinkPreview: MTLModel {
|
|||
|
||||
@objc
|
||||
public var imageAttachmentId: String?
|
||||
|
||||
// Whether this preview can be rendered as an attachment
|
||||
@objc
|
||||
public var isDirectAttachment: Bool = false
|
||||
|
||||
@objc
|
||||
public init(urlString: String, title: String?, imageAttachmentId: String?) {
|
||||
public init(urlString: String, title: String?, imageAttachmentId: String?, isDirectAttachment: Bool = false) {
|
||||
self.urlString = urlString
|
||||
self.title = title
|
||||
self.imageAttachmentId = imageAttachmentId
|
||||
self.isDirectAttachment = isDirectAttachment
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
@ -113,6 +120,15 @@ public class OWSLinkPreview: MTLModel {
|
|||
}
|
||||
return error == .noPreview
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func isInvalidContentError(_ error: Error) -> Bool {
|
||||
guard let error = error as? LinkPreviewError else {
|
||||
return false
|
||||
}
|
||||
|
||||
return error == .invalidContent
|
||||
}
|
||||
|
||||
@objc
|
||||
public class func buildValidatedLinkPreview(dataMessage: SSKProtoDataMessage,
|
||||
|
@ -203,42 +219,46 @@ public class OWSLinkPreview: MTLModel {
|
|||
|
||||
return linkPreview
|
||||
}
|
||||
|
||||
|
||||
private class func saveAttachmentIfPossible(jpegImageData: Data?,
|
||||
transaction: YapDatabaseReadWriteTransaction) -> String? {
|
||||
guard let jpegImageData = jpegImageData else {
|
||||
return nil
|
||||
}
|
||||
let fileSize = jpegImageData.count
|
||||
return saveAttachmentIfPossible(imageData: jpegImageData, mimeType: OWSMimeTypeImageJpeg, transaction: transaction);
|
||||
}
|
||||
|
||||
private class func saveAttachmentIfPossible(imageData: Data?, mimeType: String, transaction: YapDatabaseReadWriteTransaction) -> String? {
|
||||
guard let imageData = imageData else { return nil }
|
||||
|
||||
let fileSize = imageData.count
|
||||
guard fileSize > 0 else {
|
||||
owsFailDebug("Invalid file size for image data.")
|
||||
return nil
|
||||
}
|
||||
let fileExtension = "jpg"
|
||||
let contentType = OWSMimeTypeImageJpeg
|
||||
|
||||
|
||||
guard let fileExtension = fileExtension(forMimeType: mimeType) else { return nil }
|
||||
let filePath = OWSFileSystem.temporaryFilePath(withFileExtension: fileExtension)
|
||||
do {
|
||||
try jpegImageData.write(to: NSURL.fileURL(withPath: filePath), options: .atomicWrite)
|
||||
try imageData.write(to: NSURL.fileURL(withPath: filePath), options: .atomicWrite)
|
||||
} catch let error as NSError {
|
||||
owsFailDebug("file write failed: \(filePath), \(error)")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
guard let dataSource = DataSourcePath.dataSource(withFilePath: filePath, shouldDeleteOnDeallocation: true) else {
|
||||
owsFailDebug("Could not create data source for path: \(filePath)")
|
||||
return nil
|
||||
}
|
||||
let attachment = TSAttachmentStream(contentType: contentType, byteCount: UInt32(fileSize), sourceFilename: nil, caption: nil, albumMessageId: nil)
|
||||
let attachment = TSAttachmentStream(contentType: mimeType, byteCount: UInt32(fileSize), sourceFilename: nil, caption: nil, albumMessageId: nil)
|
||||
guard attachment.write(dataSource) else {
|
||||
owsFailDebug("Could not write data source for path: \(filePath)")
|
||||
return nil
|
||||
}
|
||||
attachment.save(with: transaction)
|
||||
|
||||
|
||||
return attachment.uniqueId
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func isValid() -> Bool {
|
||||
var hasTitle = false
|
||||
if let titleValue = title {
|
||||
|
@ -318,7 +338,12 @@ public class OWSLinkPreview: MTLModel {
|
|||
// Pinterest
|
||||
"pinterest.com",
|
||||
"www.pinterest.com",
|
||||
"pin.it"
|
||||
"pin.it",
|
||||
|
||||
// Giphy
|
||||
"giphy.com",
|
||||
"media.giphy.com",
|
||||
"gph.is",
|
||||
]
|
||||
|
||||
// For media domains, we DO NOT require an exact match - subdomains are allowed.
|
||||
|
@ -337,7 +362,10 @@ public class OWSLinkPreview: MTLModel {
|
|||
"fbcdn.net",
|
||||
|
||||
// Pinterest
|
||||
"pinimg.com"
|
||||
"pinimg.com",
|
||||
|
||||
// Giphy
|
||||
"giphy.com",
|
||||
]
|
||||
|
||||
private static let protocolWhitelist = [
|
||||
|
@ -672,6 +700,66 @@ public class OWSLinkPreview: MTLModel {
|
|||
})
|
||||
return promise
|
||||
}
|
||||
|
||||
public class func getImagePreview(fromUrl imageUrl: String, transaction: YapDatabaseReadWriteTransaction) -> Promise<OWSLinkPreview> {
|
||||
// Get the mime types the url
|
||||
guard let imageFileExtension = fileExtension(forImageUrl: imageUrl),
|
||||
let imageMimeType = mimetype(forImageFileExtension: imageFileExtension) else {
|
||||
return Promise(error: LinkPreviewError.invalidInput)
|
||||
}
|
||||
|
||||
return downloadImage(url: imageUrl).map { data in
|
||||
// Make sure the downloaded image has the correct mime type
|
||||
guard let newImageMimeType = NSData(data: data).ows_guessMimeType() else {
|
||||
throw LinkPreviewError.invalidContent
|
||||
}
|
||||
|
||||
// Save the attachment
|
||||
guard let attachmentId = saveAttachmentIfPossible(imageData: data, mimeType: newImageMimeType, transaction: transaction) else {
|
||||
Logger.verbose("Error: Failed to save attachment for \(imageUrl)")
|
||||
throw LinkPreviewError.attachmentFailedToSave
|
||||
}
|
||||
|
||||
// If we had a GIF and the data we have is not a GIF then we need to render a link preview without attachments
|
||||
if (imageMimeType == OWSMimeTypeImageGif && newImageMimeType != OWSMimeTypeImageGif) {
|
||||
return OWSLinkPreview(urlString: imageUrl, title: nil, imageAttachmentId: attachmentId)
|
||||
}
|
||||
|
||||
return OWSLinkPreview(urlString: imageUrl, title: nil, imageAttachmentId: attachmentId, isDirectAttachment: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc(getImagePreviewFromUrl:transaction:)
|
||||
public class func objc_getImagePreview(url imageUrl: String, transaction: YapDatabaseReadWriteTransaction) -> AnyPromise {
|
||||
return AnyPromise.from(getImagePreview(fromUrl: imageUrl, transaction: transaction))
|
||||
}
|
||||
|
||||
public class func downloadImage(url imageUrl: String) -> Promise<Data> {
|
||||
guard OWSLinkPreview.featureEnabled else {
|
||||
return Promise(error: LinkPreviewError.featureDisabled)
|
||||
}
|
||||
|
||||
guard SSKPreferences.areLinkPreviewsEnabled else {
|
||||
return Promise(error: LinkPreviewError.featureDisabled)
|
||||
}
|
||||
|
||||
guard isValidMediaUrl(imageUrl) else {
|
||||
Logger.error("Invalid image URL.")
|
||||
return Promise.init(error: LinkPreviewError.invalidInput)
|
||||
}
|
||||
|
||||
guard let imageFileExtension = fileExtension(forImageUrl: imageUrl) else {
|
||||
Logger.error("Image URL has unknown or invalid file extension: \(imageUrl).")
|
||||
return Promise.init(error: LinkPreviewError.invalidInput)
|
||||
}
|
||||
|
||||
guard let imageMimeType = mimetype(forImageFileExtension: imageFileExtension) else {
|
||||
Logger.error("Image URL has unknown or invalid content type: \(imageUrl).")
|
||||
return Promise.init(error: LinkPreviewError.invalidInput)
|
||||
}
|
||||
|
||||
return downloadImage(url: imageUrl, imageMimeType: imageMimeType)
|
||||
}
|
||||
|
||||
private class func downloadImage(url urlString: String, imageMimeType: String) -> Promise<Data> {
|
||||
|
||||
|
@ -710,6 +798,9 @@ public class OWSLinkPreview: MTLModel {
|
|||
Logger.error("Could not parse image.")
|
||||
return Promise(error: LinkPreviewError.invalidContent)
|
||||
}
|
||||
|
||||
// If we have a gif then don't download it as a jpg and also we need to ensure that it's a valid GIF
|
||||
if (imageMimeType == OWSMimeTypeImageGif && NSData(data: data).ows_isValidImage(withMimeType: OWSMimeTypeImageGif)) { return Promise.value(data) }
|
||||
|
||||
let maxImageSize: CGFloat = 1024
|
||||
let shouldResize = imageSize.width > maxImageSize || imageSize.height > maxImageSize
|
||||
|
@ -830,6 +921,19 @@ public class OWSLinkPreview: MTLModel {
|
|||
}
|
||||
return imageFileExtension
|
||||
}
|
||||
|
||||
class func fileExtension(forMimeType mimeType: String) -> String? {
|
||||
switch mimeType {
|
||||
case OWSMimeTypeImageGif:
|
||||
return "gif"
|
||||
case OWSMimeTypeImagePng:
|
||||
return "png"
|
||||
case OWSMimeTypeImageJpeg:
|
||||
return "jpg"
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
class func mimetype(forImageFileExtension imageFileExtension: String) -> String? {
|
||||
guard imageFileExtension.count > 0 else {
|
||||
|
@ -841,7 +945,8 @@ public class OWSLinkPreview: MTLModel {
|
|||
}
|
||||
let kValidMimeTypes = [
|
||||
OWSMimeTypeImagePng,
|
||||
OWSMimeTypeImageJpeg
|
||||
OWSMimeTypeImageJpeg,
|
||||
OWSMimeTypeImageGif,
|
||||
]
|
||||
guard kValidMimeTypes.contains(imageMimeType) else {
|
||||
Logger.error("Image URL has invalid content type: \(imageMimeType).")
|
||||
|
|
|
@ -67,6 +67,7 @@ typedef NS_ENUM(NSInteger, LKMessageFriendRequestStatus) {
|
|||
- (NSArray<TSAttachment *> *)attachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
- (NSArray<TSAttachment *> *)mediaAttachmentsWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
- (nullable TSAttachment *)oversizeTextAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
- (void)addAttachmentId:(NSString *)attachmentId transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
- (void)removeAttachment:(TSAttachment *)attachment
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(removeAttachment(_:transaction:));
|
||||
|
|
|
@ -250,6 +250,12 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
|
|||
[self saveWithTransaction:transaction];
|
||||
}
|
||||
|
||||
- (void)addAttachmentId:(NSString *)attachmentId transaction:(YapDatabaseReadWriteTransaction *)transaction {
|
||||
if (!self.attachmentIds) { return; }
|
||||
[self.attachmentIds addObject:attachmentId];
|
||||
[self saveWithTransaction:transaction];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
if ([self hasAttachments] && self.body.length > 0) {
|
||||
|
|
|
@ -1525,6 +1525,38 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
incomingMessage.linkPreview = linkPreview;
|
||||
[incomingMessage saveWithTransaction:transaction];
|
||||
}];
|
||||
})
|
||||
.catchOn(dispatch_get_main_queue(), ^(NSError *error) {
|
||||
// If we failed to get link preview due to invalid content then maybe it's a link to a direct image?
|
||||
if ([OWSLinkPreview isInvalidContentError:error]) {
|
||||
__block AnyPromise *promise;
|
||||
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
promise = [OWSLinkPreview getImagePreviewFromUrl:linkPreviewURL transaction:transaction];
|
||||
}];
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Return the error
|
||||
return [AnyPromise promiseWithValue:error];
|
||||
})
|
||||
.thenOn(dispatch_get_main_queue(), ^(OWSLinkPreview *linkPreview) {
|
||||
// If we managed to get direct previews then render them
|
||||
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
if (linkPreview.isDirectAttachment) {
|
||||
if (!incomingMessage.hasAttachments) {
|
||||
[incomingMessage addAttachmentId:linkPreview.imageAttachmentId transaction:transaction];
|
||||
|
||||
// Set the message id in attachment
|
||||
TSAttachment *linkPreviewAttachment = [TSAttachment fetchObjectWithUniqueID:linkPreview.imageAttachmentId transaction:transaction];
|
||||
linkPreviewAttachment.albumMessageId = incomingMessage.uniqueId;
|
||||
[linkPreviewAttachment saveWithTransaction:transaction];
|
||||
}
|
||||
} else {
|
||||
incomingMessage.linkPreview = linkPreview;
|
||||
[incomingMessage saveWithTransaction:transaction];
|
||||
}
|
||||
|
||||
}];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
+ (BOOL)ows_isValidImageAtPath:(NSString *)filePath mimeType:(nullable NSString *)mimeType;
|
||||
- (BOOL)ows_isValidImage;
|
||||
- (BOOL)ows_isValidImageWithMimeType:(nullable NSString *)mimeType;
|
||||
- (NSString *_Nullable)ows_guessMimeType;
|
||||
|
||||
// Returns the image size in pixels.
|
||||
//
|
||||
|
|
|
@ -263,6 +263,21 @@ typedef NS_ENUM(NSInteger, ImageFormat) {
|
|||
return ImageFormat_Unknown;
|
||||
}
|
||||
|
||||
- (NSString *_Nullable)ows_guessMimeType
|
||||
{
|
||||
ImageFormat format = [self ows_guessImageFormat];
|
||||
switch (format) {
|
||||
case ImageFormat_Gif:
|
||||
return OWSMimeTypeImageGif;
|
||||
case ImageFormat_Png:
|
||||
return OWSMimeTypeImagePng;
|
||||
case ImageFormat_Jpeg:
|
||||
return OWSMimeTypeImageJpeg;
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)ows_areByteArraysEqual:(NSUInteger)length left:(unsigned char *)left right:(unsigned char *)right
|
||||
{
|
||||
for (NSUInteger i = 0; i < length; i++) {
|
||||
|
|
Loading…
Reference in New Issue