Skip to content

Commit df92a0e

Browse files
committed
AyuForward: force byte reupload; strip source TTL
PendingMessageUploadedContent reused inputMediaPhoto/Document for noforwards cloud resources even after Local MediaId remaps — so Saved Messages never got a real upload. Route LocalImage/LocalFile through multipart upload, prefer Saved Attachments for deleted media, and drop source Autoremove/Autoclear so reuploaded copies do not expire. Co-authored-by: D3C0Y <decoder-dev@users.noreply.github.com>
1 parent 3fd8f30 commit df92a0e

2 files changed

Lines changed: 89 additions & 2 deletions

File tree

‎submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift‎

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,15 +596,91 @@ private func forwardedMessageToBeReuploaded(transaction: Transaction, id: Messag
596596
/// Build a standalone media reference for AyuForward / secret-chat reupload, remapping to local MediaIds
597597
/// so PendingMessageManager uploads instead of calling forwardMessages.
598598
private func ayuForwardMediaReference(account: Account, sourceMessage: Message) -> AnyMediaReference? {
599+
// Locally deleted: prefer durable Saved Attachments before remapping stale cloud media ids.
600+
if sourceMessage.isLocallyDeleted {
601+
if let path = sourceMessage.locallyDeletedMediaPath
602+
?? MessageSavingAttachments.existingCopyPath(message: sourceMessage, mediaBox: account.postbox.mediaBox),
603+
FileManager.default.fileExists(atPath: path),
604+
let reference = ayuForwardMediaReferenceFromLocalPath(path, account: account) {
605+
return reference
606+
}
607+
}
599608
for media in sourceMessage.media {
600609
if media is TelegramMediaImage || media is TelegramMediaFile {
601610
return .standalone(media: convertMediaForAyuForward(media, mediaBox: account.postbox.mediaBox))
602611
}
603612
}
604-
// Deleted message whose cloud media is gone — fall back to Saved Attachments / mediaPath.
613+
// Cloud media gone / unsupported — fall back to Saved Attachments / mediaPath.
605614
let path = sourceMessage.locallyDeletedMediaPath
606615
?? MessageSavingAttachments.existingCopyPath(message: sourceMessage, mediaBox: account.postbox.mediaBox)
607-
if let path, FileManager.default.fileExists(atPath: path), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
616+
if let path, FileManager.default.fileExists(atPath: path), let reference = ayuForwardMediaReferenceFromLocalPath(path, account: account) {
617+
return reference
618+
}
619+
return nil
620+
}
621+
622+
/// Load a local attachment for AyuForward. Prefer MediaBox path references over reading whole files into RAM
623+
/// when the file is already present as a MediaBox resource; otherwise store via mapped read for modest sizes.
624+
private func ayuForwardMediaReferenceFromLocalPath(_ path: String, account: Account) -> AnyMediaReference? {
625+
guard FileManager.default.fileExists(atPath: path) else {
626+
return nil
627+
}
628+
// Bound sync read — huge videos should already be in mediaBox from save-media; skip if oversized.
629+
let attrs = try? FileManager.default.attributesOfItem(atPath: path)
630+
let fileSize = (attrs?[.size] as? NSNumber)?.int64Value ?? 0
631+
if fileSize <= 0 || fileSize > 64 * 1024 * 1024 {
632+
// Still try MediaBox-friendly LocalFileReference when possible without loading bytes.
633+
if fileSize > 0, fileSize <= 512 * 1024 * 1024 {
634+
let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: Int64.random(in: Int64.min ... Int64.max), size: fileSize)
635+
let ext = (path as NSString).pathExtension.lowercased()
636+
if ["jpg", "jpeg", "png", "webp", "heic", "gif"].contains(ext) {
637+
let representation = TelegramMediaImageRepresentation(
638+
dimensions: PixelDimensions(width: 1280, height: 1280),
639+
resource: resource,
640+
progressiveSizes: [],
641+
immediateThumbnailData: nil,
642+
hasVideo: false,
643+
isPersonal: false
644+
)
645+
let image = TelegramMediaImage(
646+
imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)),
647+
representations: [representation],
648+
immediateThumbnailData: nil,
649+
reference: nil,
650+
partialReference: nil,
651+
flags: []
652+
)
653+
return .standalone(media: image)
654+
}
655+
let mime: String
656+
switch ext {
657+
case "mp4", "m4v": mime = "video/mp4"
658+
case "mov": mime = "video/quicktime"
659+
case "mp3": mime = "audio/mpeg"
660+
case "ogg", "opus": mime = "audio/ogg"
661+
default: mime = "application/octet-stream"
662+
}
663+
var attributes: [TelegramMediaFileAttribute] = [.FileName(fileName: (path as NSString).lastPathComponent)]
664+
if mime.hasPrefix("video/") {
665+
attributes.append(.Video(duration: 0.0, size: PixelDimensions(width: 1280, height: 720), flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil))
666+
}
667+
let file = TelegramMediaFile(
668+
fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)),
669+
partialReference: nil,
670+
resource: resource,
671+
previewRepresentations: [],
672+
videoThumbnails: [],
673+
immediateThumbnailData: nil,
674+
mimeType: mime,
675+
size: fileSize,
676+
attributes: attributes,
677+
alternativeRepresentations: []
678+
)
679+
return .standalone(media: file)
680+
}
681+
return nil
682+
}
683+
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]) {
608684
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: Int64(data.count))
609685
account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
610686
let ext = (path as NSString).pathExtension.lowercased()
@@ -891,6 +967,8 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
891967
attributes.append(attribute)
892968
}
893969
}
970+
// Do not carry source chat TTL into the re-uploaded copy (would expire Saved Messages).
971+
attributes.removeAll { $0 is AutoremoveTimeoutMessageAttribute || $0 is AutoclearTimeoutMessageAttribute }
894972

895973
// Saved Messages (Избранное): associate with the source peer topic. applyUpdateMessage
896974
// preserves local threadId; SourceReference mirrors the normal forward→Saved path.

‎submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,22 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
253253
if peerId.namespace == Namespaces.Peer.SecretChat, let resource = largest.resource as? SecretFileMediaResource {
254254
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(.init(id: resource.fileId, accessHash: resource.accessHash)), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil)))
255255
}
256+
// AyuForward / remapped local MediaId: never reuse inputMediaPhoto — stream download+upload.
257+
if image.imageId.namespace == Namespaces.Media.LocalImage {
258+
return uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, peerId: peerId, image: image, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, auxiliaryMethods: auxiliaryMethods)
259+
}
256260
if peerId.namespace != Namespaces.Peer.SecretChat, let reference = image.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference {
257261
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaPhoto(.init(flags: 0, id: Api.InputPhoto.inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: nil, video: nil)), text), reuploadInfo: nil, cacheReferenceKey: nil)))
258262
} else {
259263
return uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, image: image, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, auxiliaryMethods: auxiliaryMethods)
260264
}
261265
} else if let file = media as? TelegramMediaFile {
262266
if let resource = file.resource as? CloudDocumentMediaResource {
267+
// AyuForward remaps to LocalFile while keeping the cloud resource until bytes are local.
268+
// Must multipart-upload — inputMediaDocument reuses the noforwards document id and fails.
269+
if file.fileId.namespace == Namespaces.Media.LocalFile {
270+
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: true, isGrouped: isGrouped, isPaid: false, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, file: file)
271+
}
263272
if peerId.namespace == Namespaces.Peer.SecretChat {
264273
for attribute in file.attributes {
265274
if case let .Sticker(_, packReferenceValue, _) = attribute {

0 commit comments

Comments
 (0)