Path: blob/main/SignalServiceKit/Messages/Attachments/V2/AttachmentPointer.swift
1 views
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
/// Some attachment whose full-sized contents can be downloaded from a remote CDN.
///
/// In other words, either an ``AttachmentTransitPointer`` or an ``AttachmentBackupPointer`` (or both).
public class AttachmentPointer {
// Required to have at least one but both download sources could be present.
public enum Source {
case transitTier(AttachmentTransitPointer)
case mediaTier(AttachmentBackupPointer)
}
public let attachment: Attachment
public let source: Source
public var id: Attachment.IDType { attachment.id }
init(
attachment: Attachment,
source: Source,
) {
self.attachment = attachment
self.source = source
}
public convenience init?(attachment: Attachment) {
// It doesn't matter which we pick first if we have both
// transit and media tier, as we can always recover
if let mediaTier = AttachmentBackupPointer(attachment: attachment) {
self.init(attachment: attachment, source: .mediaTier(mediaTier))
return
}
if let transitTier = AttachmentTransitPointer(attachment: attachment) {
self.init(attachment: attachment, source: .transitTier(transitTier))
return
}
return nil
}
// MARK: - Convenience
public var lastDownloadAttemptTimestamp: UInt64? {
return [
AttachmentTransitPointer(attachment: attachment)?.lastDownloadAttemptTimestamp,
AttachmentBackupPointer(attachment: attachment)?.lastDownloadAttemptTimestamp,
].compacted().max()
}
public var unencryptedByteCount: UInt32? {
switch source {
case .mediaTier(let mediaTier):
return mediaTier.unencryptedByteCount
case .transitTier(let transitTier):
return transitTier.unencryptedByteCount
?? AttachmentBackupPointer(attachment: attachment)?.unencryptedByteCount
}
}
// MARK: - Download state
public func downloadState(tx: DBReadTransaction) -> AttachmentDownloadState {
if attachment.asStream() != nil {
owsFailDebug("Checking download state of stream")
return .enqueuedOrDownloading
}
do {
if
let record = DependenciesBridge.shared.attachmentDownloadStore.enqueuedDownload(
for: attachment.id,
tx: tx,
),
record.minRetryTimestamp ?? 0 <= Date().ows_millisecondsSince1970
{
return .enqueuedOrDownloading
}
}
if lastDownloadAttemptTimestamp != nil {
return .failed
}
return .none
}
}