TM-SGNL-iOS/SignalServiceKit/MessageBackup/Attachments/QueuedBackupAttachmentUpload.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

119 lines
4.3 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public import GRDB
/// Record type for BackupAttachmentUploadQueue rows.
///
/// The table is used only as an intermediary to ensure proper ordering. As we create (archive) a backup,
/// we insert attachments that need uploading into this table first. Once we are done archiving,
/// we walk over this table in reverse order and insert rows into the AttachmentUploadQueue table
/// for _actual_ uploading.
/// In this way we ensure proper upload ordering; AttachmentUploadQueue uploads FIFO,
/// but we want to upload things we archive in the backup in LIFO order (newest first). This table
/// allows us to do that reordering after we are done processing the backup in its normal order.
public struct QueuedBackupAttachmentUpload: Codable, FetchableRecord, MutablePersistableRecord, UInt64SafeRecord {
public typealias IDType = Int64
/// Sqlite row id
public private(set) var id: IDType?
/// Row id of the associated attachment (the one we want to upload) in the Attachments table.
public let attachmentRowId: Attachment.IDType
/// What type of owner owns this attachment (the "source" of the upload).
/// Some sources are prioritized and uploaded first; thus used for sorting.
public var sourceType: SourceType
public enum SourceType {
case threadWallpaper
/// Timestamp of the newest message that owns this attachment.
/// Used to determine priority of upload (ordering of the pop-off-queue query).
case message(timestamp: UInt64)
fileprivate var timestamp: UInt64? {
switch self {
case .threadWallpaper:
return nil
case .message(let timestamp):
return timestamp
}
}
}
public init(
id: Int64? = nil,
attachmentRowId: Attachment.IDType,
sourceType: SourceType
) {
self.id = id
self.attachmentRowId = attachmentRowId
self.sourceType = sourceType
}
// MARK: FetchableRecord
public static var databaseTableName: String { "BackupAttachmentUploadQueue" }
// MARK: MutablePersistableRecord
public mutating func didInsert(with rowID: Int64, for column: String?) {
self.id = rowID
}
// MARK: - UInt64SafeRecord
static var uint64Fields: [KeyPath<QueuedBackupAttachmentUpload, UInt64>] = []
static var uint64OptionalFields: [KeyPath<QueuedBackupAttachmentUpload, UInt64?>] = [\.sourceType.timestamp]
// MARK: - Codable
enum CodingKeys: String, CodingKey {
case id
case attachmentRowId
case sourceType
case timestamp
}
/// Note the raw values. Leaving gaps as raw value influences sort order, and we may add a new
/// type of attachment that should go before after or in between.
private enum SourceTypeRaw: Int, Codable {
case threadWallpaper = 100
case message = 200
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
self.attachmentRowId = try container.decode(Attachment.IDType.self, forKey: .attachmentRowId)
let sourceTypeRaw = try container.decode(SourceTypeRaw.self, forKey: .sourceType)
let timestamp = try container.decodeIfPresent(UInt64.self, forKey: .timestamp)
switch sourceTypeRaw {
case .threadWallpaper:
self.sourceType = .threadWallpaper
case .message:
guard let timestamp else {
throw OWSAssertionError("Message attachment upload without a timestamp!")
}
self.sourceType = .message(timestamp: timestamp)
}
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(attachmentRowId, forKey: .attachmentRowId)
switch sourceType {
case .threadWallpaper:
try container.encode(SourceTypeRaw.threadWallpaper, forKey: .sourceType)
case .message(let timestamp):
try container.encode(SourceTypeRaw.message, forKey: .sourceType)
try container.encode(timestamp, forKey: .timestamp)
}
}
}