Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Backups/Attachments/QueuedBackupAttachmentDownload.swift
1 views
//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
public import GRDB

/// Record type for BackupAttachmentDownloadQueue rows.
///
/// The table is used only as an intermediary to ensure proper ordering. As we restore a backup,
/// we insert attachments that need downloading into this table first. Once we are done restoring,
/// we walk over this table in reverse order and insert rows into the AttachmentDownloadQueue table
/// for _actual_ downloading.
/// In this way we ensure proper download ordering; AttachmentDownloadQueue downloads FIFO,
/// but we want to download things we see in the backup in LIFO order. This table allows us to
/// do that reordering after we are done processing the backup in its normal order.
public struct QueuedBackupAttachmentDownload: Codable, FetchableRecord, MutablePersistableRecord {

    public typealias IDType = Int64

    /// Sqlite row id
    public var id: IDType?

    /// Row id of the associated attachment (the one we want to download) in the Attachments table.
    public let attachmentRowId: Attachment.IDType

    // If true, this row represents the download of a thumbnail, which
    // always comes from the media tier.
    // If false, represents the download of the fullsize attachment,
    // which may come from the media or transit tier.
    public let isThumbnail: Bool

    /// If true, we _believe_ this attachment can be downloaded from the media tier (and may also
    /// be downloadable from the transit tier, as a fallback).
    /// If false, only downloadable from the transit tier.
    /// Always true for thumbnails, as those only exist on media tier.
    public var canDownloadFromMediaTier: Bool

    /// Timestamp of the newest message that owns this attachment (or nil if non-message attachment).
    /// May get out of date with source; if the newest owner for an attachment is deleted we won't
    /// update this value in this table, but that just means the attachment has a higher sort priority
    /// than it otherwise would which is fine.
    @DBUInt64Optional
    public var maxOwnerTimestamp: UInt64?

    /// This timestamp should only be used to sort and to compare to the current time.
    /// It should NOT be interpreted as being the timestamp of the attachment or some owning
    /// message.
    /// We initialize this to (now - maxOwnerTimestamp) so that:
    /// 1. It is eligible to attempt
    /// 2. Newer attachments have lower values and therefore sorted first
    /// Because of this the timestamp value can be arbitrary, and in fact technically
    /// older attachments might have a lower value than newer ones, depending
    /// on the enqueue time relative to the attachment's timestamp. This is mostly
    /// fine as we will get to everything eventually and it keeps things simple.
    @DBUInt64
    public var minRetryTimestamp: UInt64

    public var numRetries: UInt8

    public var state: State

    public enum State: Int, Codable {
        /// We may download this in the future, but current state prevents us from doing so.
        /// Will transition to ready if state changes.
        case ineligible = 0
        /// Ready to download.
        case ready = 1
        /// Downloaded; maintained to keep track of already-downloaded byte count.
        case done = 2
    }

    /// Estimated byte count for the download.
    /// Should NOT be considered definitively accurate, but okay to use
    /// for estimation in UI and such.
    public let estimatedByteCount: UInt32

    // MARK: - API

    public init(
        id: Int64? = nil,
        attachmentRowId: Attachment.IDType,
        isThumbnail: Bool,
        canDownloadFromMediaTier: Bool,
        maxOwnerTimestamp: UInt64?,
        minRetryTimestamp: UInt64,
        state: State,
        estimatedByteCount: UInt32,
    ) {
        self.id = id
        self.attachmentRowId = attachmentRowId
        self.isThumbnail = isThumbnail
        self.canDownloadFromMediaTier = canDownloadFromMediaTier
        self._maxOwnerTimestamp = DBUInt64Optional(wrappedValue: maxOwnerTimestamp)
        self._minRetryTimestamp = DBUInt64(wrappedValue: minRetryTimestamp)
        self.numRetries = 0
        self.state = state
        self.estimatedByteCount = estimatedByteCount
    }

    // MARK: FetchableRecord

    public static var databaseTableName: String { "BackupAttachmentDownloadQueue" }

    // MARK: MutablePersistableRecord

    public mutating func didInsert(with rowID: Int64, for column: String?) {
        self.id = rowID
    }

    // MARK: Codable

    public enum CodingKeys: String, CodingKey {
        case id
        case attachmentRowId
        case isThumbnail
        case canDownloadFromMediaTier
        case maxOwnerTimestamp
        case minRetryTimestamp
        case numRetries
        case state
        case estimatedByteCount
    }
}