|
|
|
@ -7,36 +7,42 @@ import AVFoundation
|
|
|
|
|
|
|
|
|
|
public enum OWSThumbnailError: Error {
|
|
|
|
|
case failure(description: String)
|
|
|
|
|
case assertionFailure(description: String)
|
|
|
|
|
case externalError(description: String, underlyingError:Error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc public class OWSLoadedThumbnail: NSObject {
|
|
|
|
|
public typealias DataSourceBlock = () throws -> Data
|
|
|
|
|
|
|
|
|
|
@objc public let image: UIImage
|
|
|
|
|
@objc
|
|
|
|
|
public let image: UIImage
|
|
|
|
|
let dataSourceBlock: DataSourceBlock
|
|
|
|
|
|
|
|
|
|
@objc public init(image: UIImage, filePath: String) {
|
|
|
|
|
@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) {
|
|
|
|
|
@objc
|
|
|
|
|
public init(image: UIImage, data: Data) {
|
|
|
|
|
self.image = image
|
|
|
|
|
self.dataSourceBlock = {
|
|
|
|
|
return data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc public func data() throws -> Data {
|
|
|
|
|
@objc
|
|
|
|
|
public func data() throws -> Data {
|
|
|
|
|
return try dataSourceBlock()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private struct OWSThumbnailRequest {
|
|
|
|
|
public typealias SuccessBlock = (OWSLoadedThumbnail) -> Void
|
|
|
|
|
public typealias FailureBlock = () -> Void
|
|
|
|
|
public typealias FailureBlock = (Error) -> Void
|
|
|
|
|
|
|
|
|
|
let attachment: TSAttachmentStream
|
|
|
|
|
let thumbnailDimensionPoints: UInt
|
|
|
|
@ -59,12 +65,10 @@ private struct OWSThumbnailRequest {
|
|
|
|
|
public static let shared = OWSThumbnailService()
|
|
|
|
|
|
|
|
|
|
public typealias SuccessBlock = (OWSLoadedThumbnail) -> Void
|
|
|
|
|
public typealias FailureBlock = () -> Void
|
|
|
|
|
public typealias FailureBlock = (Error) -> Void
|
|
|
|
|
|
|
|
|
|
private let serialQueue = DispatchQueue(label: "OWSThumbnailService")
|
|
|
|
|
|
|
|
|
|
private let dbConnection: YapDatabaseConnection
|
|
|
|
|
|
|
|
|
|
// This property should only be accessed on the serialQueue.
|
|
|
|
|
//
|
|
|
|
|
// We want to process requests in _reverse_ order in which they
|
|
|
|
@ -72,9 +76,6 @@ private struct OWSThumbnailRequest {
|
|
|
|
|
private var thumbnailRequestStack = [OWSThumbnailRequest]()
|
|
|
|
|
|
|
|
|
|
private override init() {
|
|
|
|
|
|
|
|
|
|
dbConnection = OWSPrimaryStorage.shared().newDatabaseConnection()
|
|
|
|
|
|
|
|
|
|
super.init()
|
|
|
|
|
|
|
|
|
|
SwiftSingletons.register(self)
|
|
|
|
@ -86,7 +87,8 @@ private struct OWSThumbnailRequest {
|
|
|
|
|
|
|
|
|
|
// completion will only be called on success.
|
|
|
|
|
// completion will be called async on the main thread.
|
|
|
|
|
@objc public func ensureThumbnail(forAttachment attachment: TSAttachmentStream,
|
|
|
|
|
@objc
|
|
|
|
|
public func ensureThumbnail(forAttachment attachment: TSAttachmentStream,
|
|
|
|
|
thumbnailDimensionPoints: UInt,
|
|
|
|
|
success: @escaping SuccessBlock,
|
|
|
|
|
failure: @escaping FailureBlock) {
|
|
|
|
@ -106,10 +108,9 @@ private struct OWSThumbnailRequest {
|
|
|
|
|
|
|
|
|
|
// This should only be called on the serialQueue.
|
|
|
|
|
private func processNextRequestSync() {
|
|
|
|
|
guard !thumbnailRequestStack.isEmpty else {
|
|
|
|
|
guard let thumbnailRequest = thumbnailRequestStack.popLast() else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
let thumbnailRequest = thumbnailRequestStack.removeLast()
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
let loadedThumbnail = try process(thumbnailRequest: thumbnailRequest)
|
|
|
|
@ -120,7 +121,7 @@ private struct OWSThumbnailRequest {
|
|
|
|
|
Logger.error("Could not create thumbnail: \(error)")
|
|
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
thumbnailRequest.failure()
|
|
|
|
|
thumbnailRequest.failure(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -146,8 +147,8 @@ private struct OWSThumbnailRequest {
|
|
|
|
|
return OWSLoadedThumbnail(image: image, filePath: thumbnailPath)
|
|
|
|
|
}
|
|
|
|
|
let thumbnailDirPath = (thumbnailPath as NSString).deletingLastPathComponent
|
|
|
|
|
if !FileManager.default.fileExists(atPath: thumbnailDirPath) {
|
|
|
|
|
try FileManager.default.createDirectory(atPath: thumbnailDirPath, withIntermediateDirectories: true, attributes: nil)
|
|
|
|
|
guard OWSFileSystem.ensureDirectoryExists(thumbnailDirPath) else {
|
|
|
|
|
throw OWSThumbnailError.failure(description: "Could not create attachment's thumbnail directory.")
|
|
|
|
|
}
|
|
|
|
|
guard let originalFilePath = attachment.originalFilePath else {
|
|
|
|
|
throw OWSThumbnailError.failure(description: "Missing original file path.")
|
|
|
|
@ -157,18 +158,17 @@ private struct OWSThumbnailRequest {
|
|
|
|
|
if attachment.isImage || attachment.isAnimated {
|
|
|
|
|
thumbnailImage = try OWSMediaUtils.thumbnail(forImageAtPath: originalFilePath, maxDimension: maxDimension)
|
|
|
|
|
} else if attachment.isVideo {
|
|
|
|
|
let maxSize = CGSize(width: maxDimension, height: maxDimension)
|
|
|
|
|
thumbnailImage = try OWSMediaUtils.thumbnail(forVideoAtPath: originalFilePath, maxSize: maxSize)
|
|
|
|
|
thumbnailImage = try OWSMediaUtils.thumbnail(forVideoAtPath: originalFilePath, maxDimension: maxDimension)
|
|
|
|
|
} else {
|
|
|
|
|
throw OWSThumbnailError.failure(description: "Invalid attachment type.")
|
|
|
|
|
throw OWSThumbnailError.assertionFailure(description: "Invalid attachment type.")
|
|
|
|
|
}
|
|
|
|
|
guard let thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 0.85) else {
|
|
|
|
|
throw OWSThumbnailError.failure(description: "Could not convert thumbnail to JPEG.")
|
|
|
|
|
}
|
|
|
|
|
do {
|
|
|
|
|
try thumbnailData.write(to: NSURL.fileURL(withPath: thumbnailPath), options: .atomicWrite)
|
|
|
|
|
try thumbnailData.write(to: URL(fileURLWithPath: thumbnailPath), options: .atomicWrite)
|
|
|
|
|
} catch let error as NSError {
|
|
|
|
|
throw OWSThumbnailError.failure(description: "File write failed: \(thumbnailPath), \(error)")
|
|
|
|
|
throw OWSThumbnailError.externalError(description: "File write failed: \(thumbnailPath), \(error)", underlyingError: error)
|
|
|
|
|
}
|
|
|
|
|
return OWSLoadedThumbnail(image: thumbnailImage, data: thumbnailData)
|
|
|
|
|
}
|
|
|
|
|