Path: blob/main/SignalServiceKit/Messages/Interactions/LinkPreview/OWSLinkPreview.swift
1 views
//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public enum LinkPreviewError: Error {
/// A preview could not be generated from available input
case noPreview
/// A preview should have been generated, but something unexpected caused it to fail
case invalidPreview
/// A preview could not be generated due to an issue fetching a network resource
case fetchFailure
/// A preview could not be generated because the feature is disabled
case featureDisabled
}
// MARK: - OWSLinkPreviewDraft
// This contains the info for a link preview "draft".
public class OWSLinkPreviewDraft: Equatable {
public let url: URL
public var urlString: String {
return url.absoluteString
}
public let title: String?
public let imageData: Data?
public let imageMimeType: String?
public let previewDescription: String?
public let date: Date?
public let isForwarded: Bool
public init(
url: URL,
title: String?,
imageData: Data? = nil,
imageMimeType: String? = nil,
previewDescription: String? = nil,
date: Date? = nil,
isForwarded: Bool,
) {
self.url = url
self.title = title
self.imageData = imageData
self.imageMimeType = imageMimeType
self.previewDescription = previewDescription
self.date = date
self.isForwarded = isForwarded
}
public var displayDomain: String? {
return URL(string: urlString).flatMap(LinkPreviewHelper.displayDomain(forUrl:))
}
/// Uses identity equatability even though comparing fields seems like it would make more sense because this
/// object used to inherit from `NSObject` without overridding `isEqual(_)` so it would have inherited
/// identity equatability.
public static func ==(lhs: OWSLinkPreviewDraft, rhs: OWSLinkPreviewDraft) -> Bool {
return lhs === rhs
}
}
// MARK: - OWSLinkPreview
@objc
public final class OWSLinkPreview: NSObject, NSSecureCoding, NSCopying, Codable {
public static var supportsSecureCoding: Bool { true }
public init?(coder: NSCoder) {
self.date = coder.decodeObject(of: NSDate.self, forKey: "date") as Date?
self.previewDescription = coder.decodeObject(of: NSString.self, forKey: "previewDescription") as String?
self.title = coder.decodeObject(of: NSString.self, forKey: "title") as String?
self.urlString = coder.decodeObject(of: NSString.self, forKey: "urlString") as String?
}
public func encode(with coder: NSCoder) {
if let date {
coder.encode(date, forKey: "date")
}
if let previewDescription {
coder.encode(previewDescription, forKey: "previewDescription")
}
if let title {
coder.encode(title, forKey: "title")
}
if let urlString {
coder.encode(urlString, forKey: "urlString")
}
}
override public var hash: Int {
var hasher = Hasher()
hasher.combine(date)
hasher.combine(previewDescription)
hasher.combine(title)
hasher.combine(urlString)
return hasher.finalize()
}
override public func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Self else { return false }
guard type(of: self) == type(of: object) else { return false }
guard self.date == object.date else { return false }
guard self.previewDescription == object.previewDescription else { return false }
guard self.title == object.title else { return false }
guard self.urlString == object.urlString else { return false }
return true
}
public func copy(with zone: NSZone? = nil) -> Any {
return self
}
public struct Metadata {
public let urlString: String
public let title: String?
public let previewDescription: String?
public let date: Date?
}
public let urlString: String?
public let title: String?
public let previewDescription: String?
public let date: Date?
public init(
urlString: String,
title: String? = nil,
previewDescription: String? = nil,
date: Date? = nil,
) {
self.urlString = urlString
self.title = title
self.previewDescription = previewDescription
self.date = date
super.init()
}
public convenience init(metadata: Metadata) {
self.init(
urlString: metadata.urlString,
title: metadata.title,
previewDescription: metadata.previewDescription,
date: metadata.date,
)
}
@objc
public class func isNoPreviewError(_ error: Error) -> Bool {
guard let error = error as? LinkPreviewError else {
return false
}
return error == .noPreview
}
public var displayDomain: String? {
urlString.flatMap(URL.init(string:)).flatMap(LinkPreviewHelper.displayDomain(forUrl:))
}
// MARK: - Codable
enum CodingKeys: String, CodingKey {
case urlString
case title
case usesV2AttachmentReferenceValue
case imageAttachmentId
case previewDescription
case date
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
urlString = try container.decodeIfPresent(String.self, forKey: .urlString)
title = try container.decodeIfPresent(String.self, forKey: .title)
previewDescription = try container.decodeIfPresent(String.self, forKey: .previewDescription)
date = try container.decodeIfPresent(Date.self, forKey: .date)
super.init()
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if let urlString {
try container.encode(urlString, forKey: .urlString)
}
if let title {
try container.encode(title, forKey: .title)
}
if let previewDescription {
try container.encode(previewDescription, forKey: .previewDescription)
}
if let date {
try container.encode(date, forKey: .date)
}
}
}