AttachmentTransport: per-attachment транспортный сервер и тег, backward-compat Codable, download parity

This commit is contained in:
2026-03-29 21:08:02 +05:00
parent 44a74ad327
commit 8c64111bd6
13 changed files with 336 additions and 162 deletions

View File

@@ -233,7 +233,7 @@ struct MessageAvatarView: View {
private func downloadAvatar() {
guard !isDownloading else { return }
let tag = extractTag(from: attachment.preview)
let tag = attachment.effectiveDownloadTag
guard !tag.isEmpty else {
downloadError = true
return
@@ -247,9 +247,10 @@ struct MessageAvatarView: View {
isDownloading = true
downloadError = false
let server = attachment.transportServer
Task {
do {
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag)
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag, server: server)
let encryptedString = String(decoding: encryptedData, as: UTF8.self)
let passwords = MessageCrypto.attachmentPasswordCandidates(from: storedPassword)

View File

@@ -98,7 +98,10 @@ struct MessageFileView: View {
private var fileName: String { fileMetadata.name }
private var fileSize: Int { fileMetadata.size }
private var fileTag: String { fileMetadata.tag }
private var fileTag: String {
let effective = attachment.effectiveDownloadTag
return effective.isEmpty ? fileMetadata.tag : effective
}
private var formattedFileSize: String {
let bytes = fileSize
@@ -140,7 +143,7 @@ struct MessageFileView: View {
Task {
do {
let encryptedData = try await TransportManager.shared.downloadFile(tag: fileTag)
let encryptedData = try await TransportManager.shared.downloadFile(tag: fileTag, server: attachment.transportServer)
let encryptedString = String(decoding: encryptedData, as: UTF8.self)
let passwords = MessageCrypto.attachmentPasswordCandidates(from: storedPassword)

View File

@@ -251,7 +251,7 @@ struct MessageImageView: View {
private func downloadImage() {
guard !isDownloading, image == nil else { return }
let tag = extractTag(from: attachment.preview)
let tag = attachment.effectiveDownloadTag
guard !tag.isEmpty else {
downloadError = true
return
@@ -265,9 +265,10 @@ struct MessageImageView: View {
isDownloading = true
downloadError = false
let server = attachment.transportServer
Task {
do {
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag)
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag, server: server)
let encryptedString = String(decoding: encryptedData, as: UTF8.self)
let passwords = MessageCrypto.attachmentPasswordCandidates(from: storedPassword)

View File

@@ -901,10 +901,11 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
/// Downloads avatar from CDN, decrypts, caches to disk, and returns the image.
/// Shared logic with `MessageAvatarView.downloadAvatar()`.
private static func downloadAndCacheAvatar(
tag: String, attachmentId: String, storedPassword: String, senderKey: String
tag: String, attachmentId: String, storedPassword: String, senderKey: String,
server: String = ""
) async -> UIImage? {
do {
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag)
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag, server: server)
let encryptedString = String(decoding: encryptedData, as: UTF8.self)
let passwords = MessageCrypto.attachmentPasswordCandidates(from: storedPassword)
@@ -1109,7 +1110,7 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
// Already downloaded?
if AttachmentCache.shared.cachedImage(forAttachmentId: id) != nil { return }
// Download from CDN
let tag = AttachmentPreviewCodec.downloadTag(from: avatarAtt.preview)
let tag = avatarAtt.effectiveDownloadTag
guard !tag.isEmpty else { return }
guard let password = message.attachmentPassword, !password.isEmpty else { return }
@@ -1118,10 +1119,12 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
let messageId = message.id
let senderKey = message.fromPublicKey
let server = avatarAtt.transportServer
Task.detached(priority: .userInitiated) {
let downloaded = await Self.downloadAndCacheAvatar(
tag: tag, attachmentId: id,
storedPassword: password, senderKey: senderKey
storedPassword: password, senderKey: senderKey,
server: server
)
await MainActor.run { [weak self] in
guard let self, self.message?.id == messageId else { return }
@@ -1569,7 +1572,7 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
private func downloadPhotoAttachment(attachment: MessageAttachment, message: ChatMessage) {
if photoDownloadTasks[attachment.id] != nil { return }
let tag = Self.extractTag(from: attachment.preview)
let tag = attachment.effectiveDownloadTag
guard !tag.isEmpty,
let storedPassword = message.attachmentPassword,
!storedPassword.isEmpty else {
@@ -1593,9 +1596,10 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
photoTileDownloadArrows[tileIndex].isHidden = true
}
let server = attachment.transportServer
photoDownloadTasks[attachmentId] = Task { [weak self] in
do {
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag)
let encryptedData = try await TransportManager.shared.downloadFile(tag: tag, server: server)
let encryptedString = String(decoding: encryptedData, as: UTF8.self)
let passwords = MessageCrypto.attachmentPasswordCandidates(from: storedPassword)
let image = Self.decryptAndParseImage(encryptedString: encryptedString, passwords: passwords)