Path: blob/main/SignalServiceKit/Messages/Attachments/V2/AttachmentReference/AttachmentReference.swift
1 views
//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
/// Represents an edge between some owner (a message, a story, a thread, etc) and an attachment.
public class AttachmentReference {
/// We keep the raw type, without any metadata, on the reference table.
public typealias ContentType = Attachment.ContentTypeRaw
// MARK: - Vars
/// Sqlite row id of the attachment on the Attachments table.
/// Multiple AttachmentReferences can point to the same Attachment.
public let attachmentRowId: Int64
/// We compute/validate this once, when we read from disk (or instantate an instance in memory).
public let owner: Owner
/// Filename from the sender, used for rendering as a file attachment.
/// NOT the same as the file name on disk.
/// Comes from ``SSKProtoAttachmentPointer.fileName``.
public let sourceFilename: String?
/// Byte count from the sender of this attachment (can therefore be spoofed).
/// Comes from ``SSKProtoAttachmentPointer.size``.
public let sourceUnencryptedByteCount: UInt32?
/// Width/height from the sender of this attachment (can therefore be spoofed).
/// Comes from ``SSKProtoAttachmentPointer.width`` and ``SSKProtoAttachmentPointer.height``.
public let sourceMediaSizePixels: CGSize?
// MARK: - Init
init(record: MessageAttachmentReferenceRecord) throws {
self.owner = try Owner.validateAndBuild(record: record)
self.attachmentRowId = record.attachmentRowId
self.sourceFilename = record.sourceFilename
self.sourceUnencryptedByteCount = record.sourceUnencryptedByteCount
self.sourceMediaSizePixels = Self.buildSourceMediaSizePixels(
sourceMediaWidthPixels: record.sourceMediaWidthPixels,
sourceMediaHeightPixels: record.sourceMediaHeightPixels,
)
}
init(record: StoryMessageAttachmentReferenceRecord) throws {
self.owner = try Owner.validateAndBuild(record: record)
self.attachmentRowId = record.attachmentRowId
self.sourceFilename = record.sourceFilename
self.sourceUnencryptedByteCount = record.sourceUnencryptedByteCount
self.sourceMediaSizePixels = Self.buildSourceMediaSizePixels(
sourceMediaWidthPixels: record.sourceMediaWidthPixels,
sourceMediaHeightPixels: record.sourceMediaHeightPixels,
)
}
init(record: ThreadAttachmentReferenceRecord) throws {
self.owner = try Owner.validateAndBuild(record: record)
self.attachmentRowId = record.attachmentRowId
self.sourceFilename = nil
self.sourceUnencryptedByteCount = nil
self.sourceMediaSizePixels = nil
}
// MARK: -
private static func buildSourceMediaSizePixels(
sourceMediaWidthPixels: UInt32?,
sourceMediaHeightPixels: UInt32?,
) -> CGSize? {
guard
let sourceMediaWidthPixels,
let sourceMediaHeightPixels
else {
owsAssertDebug(
sourceMediaWidthPixels == nil
&& sourceMediaHeightPixels == nil,
"Got partial source media size",
)
return nil
}
// Safe Int conversion: all UInt32 can be an Int on 64b systems
return CGSize(
width: Int(sourceMediaWidthPixels),
height: Int(sourceMediaHeightPixels),
)
}
}
// MARK: Convenience
extension AttachmentReference {
/// Hint from the sender telling us how to render the attachment.
/// Always `default` for types that dont support the flag.
public var renderingFlag: RenderingFlag {
switch owner {
case .message(.bodyAttachment(let metadata)):
return metadata.renderingFlag
case .message(.quotedReply(let metadata)):
return metadata.renderingFlag
case .storyMessage(.media(let metadata)):
return metadata.shouldLoop ? .shouldLoop : .default
default:
return .default
}
}
/// Caption for story message media attachments. Nil for other reference types.
public var storyMediaCaption: StyleOnlyMessageBody? {
switch owner {
case .storyMessage(.media(let metadata)):
return metadata.caption
default:
return nil
}
}
/// Caption for message body attachments.
/// Unused in the modern app but may be set for old messages.
public var legacyMessageCaption: String? {
switch owner {
case .message(.bodyAttachment(let metadata)):
return metadata.caption
default:
return nil
}
}
public var orderInOwningMessage: UInt32? {
switch owner {
case .message(.bodyAttachment(let metadata)):
return metadata.orderInMessage
default:
return nil
}
}
public var knownIdInOwningMessage: UUID? {
switch owner {
case .message(.bodyAttachment(let metadata)):
return metadata.idInOwner
default:
return nil
}
}
public func hasSameOwner(as other: AttachmentReference) -> Bool {
return self.owner.id == other.owner.id
}
}