Signal-iOS/SignalUI/Utils/AttachmentMultisend.swift
2021-10-19 09:39:53 -03:00

165 lines
8.5 KiB
Swift

//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalMessaging
public class AttachmentMultisend: Dependencies {
public class func sendApprovedMedia(conversations: [ConversationItem],
approvalMessageBody: MessageBody?,
approvedAttachments: [SignalAttachment]) -> Promise<[TSThread]> {
return firstly(on: ThreadUtil.enqueueSendQueue) {
// Duplicate attachments per conversation
let conversationAttachments: [(ConversationItem, [SignalAttachment])] =
try conversations.map { conversation in
return (conversation, try approvedAttachments.map { try $0.cloneAttachment() })
}
// We only upload one set of attachments, and then copy the upload details into
// each conversation before sending.
let attachmentsToUpload: [OutgoingAttachmentInfo] = approvedAttachments.map { attachment in
return OutgoingAttachmentInfo(dataSource: attachment.dataSource,
contentType: attachment.mimeType,
sourceFilename: attachment.filenameOrDefault,
caption: attachment.captionText,
albumMessageId: nil,
isBorderless: attachment.isBorderless,
isLoopingVideo: attachment.isLoopingVideo)
}
var threads: [TSThread] = []
self.databaseStorage.write { transaction in
var messages: [TSOutgoingMessage] = []
for (conversation, attachments) in conversationAttachments {
guard let thread = conversation.thread(transaction: transaction) else {
owsFailDebug("Missing thread for conversation")
continue
}
// If this thread has a pending message request, treat it as accepted.
ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: thread,
transaction: transaction)
let message = try! ThreadUtil.createUnsentMessage(body: approvalMessageBody,
mediaAttachments: attachments,
thread: thread,
quotedReplyModel: nil,
linkPreviewDraft: nil,
transaction: transaction)
messages.append(message)
threads.append(thread)
thread.donateSendMessageIntent(transaction: transaction)
}
// map of attachments we'll upload to their copies in each recipient thread
var attachmentIdMap: [String: [String]] = [:]
let correspondingAttachmentIds = transpose(messages.map { $0.attachmentIds })
for (index, attachmentInfo) in attachmentsToUpload.enumerated() {
do {
let attachmentToUpload = try attachmentInfo.asStreamConsumingDataSource(withIsVoiceMessage: false)
attachmentToUpload.anyInsert(transaction: transaction)
attachmentIdMap[attachmentToUpload.uniqueId] = correspondingAttachmentIds[index]
} catch {
owsFailDebug("error: \(error)")
}
}
self.broadcastMediaMessageJobQueue.add(attachmentIdMap: attachmentIdMap,
transaction: transaction)
}
return threads
}
}
public class func sendApprovedMediaFromShareExtension(
conversations: [ConversationItem],
approvalMessageBody: MessageBody?,
approvedAttachments: [SignalAttachment],
messagesReadyToSend: (([TSOutgoingMessage]) -> Void)? = nil
) -> Promise<[TSThread]> {
return firstly(on: .sharedUserInitiated) { () -> (Promise<[TSThread]>) in
// Duplicate attachments per conversation
let conversationAttachments: [(ConversationItem, [SignalAttachment])] =
try conversations.map { conversation in
return (conversation, try approvedAttachments.map { try $0.cloneAttachment() })
}
// We only upload one set of attachments, and then copy the upload details into
// each conversation before sending.
let attachmentsToUpload: [OutgoingAttachmentInfo] = approvedAttachments.map { attachment in
return OutgoingAttachmentInfo(dataSource: attachment.dataSource,
contentType: attachment.mimeType,
sourceFilename: attachment.filenameOrDefault,
caption: attachment.captionText,
albumMessageId: nil,
isBorderless: attachment.isBorderless,
isLoopingVideo: attachment.isLoopingVideo)
}
var threads: [TSThread] = []
var attachmentIdMap: [String: [String]] = [:]
var messages: [TSOutgoingMessage] = []
self.databaseStorage.write { transaction in
for (conversation, attachments) in conversationAttachments {
guard let thread = conversation.thread(transaction: transaction) else {
owsFailDebug("Missing thread for conversation")
continue
}
// If this thread has a pending message request, treat it as accepted.
ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: thread,
transaction: transaction)
let message = try! ThreadUtil.createUnsentMessage(body: approvalMessageBody,
mediaAttachments: attachments,
thread: thread,
quotedReplyModel: nil,
linkPreviewDraft: nil,
transaction: transaction)
messages.append(message)
threads.append(thread)
thread.donateSendMessageIntent(transaction: transaction)
}
// map of attachments we'll upload to their copies in each recipient thread
let correspondingAttachmentIds = transpose(messages.map { $0.attachmentIds })
for (index, attachmentInfo) in attachmentsToUpload.enumerated() {
do {
let attachmentToUpload = try attachmentInfo.asStreamConsumingDataSource(withIsVoiceMessage: false)
attachmentToUpload.anyInsert(transaction: transaction)
attachmentIdMap[attachmentToUpload.uniqueId] = correspondingAttachmentIds[index]
} catch {
owsFailDebug("error: \(error)")
}
}
}
messagesReadyToSend?(messages)
let outgoingMessages = try BroadcastMediaUploader.upload(attachmentIdMap: attachmentIdMap)
var messageSendPromises = [Promise<Void>]()
databaseStorage.write { transaction in
for message in outgoingMessages {
messageSendPromises.append(ThreadUtil.enqueueMessagePromise(
message: message,
isHighPriority: true,
transaction: transaction
))
}
}
return Promise.when(fulfilled: messageSendPromises).map { threads }
}
}
}