session-android/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt

194 lines
8.6 KiB
Kotlin
Raw Normal View History

2020-12-02 06:39:02 +01:00
package org.session.libsession.messaging.jobs
2020-11-25 02:06:41 +01:00
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import nl.komponents.kovenant.Promise
2021-05-13 05:19:08 +02:00
import okio.Buffer
2021-04-26 03:14:45 +02:00
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.file_server.FileServerAPIV2
2020-12-17 04:51:08 +01:00
import org.session.libsession.messaging.messages.Message
Merge remote-tracking branch 'upstream/dev' into open_groups_V2, working on compact poller implementation # Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java # app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java # app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt # app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt # app/src/main/java/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt # app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt # app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt # app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt # app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionManagerUtilities.kt # app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt # app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt # app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt # libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt # libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt # libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt # libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt # libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt # libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt # libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupMessageV2.kt # libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupV2.kt # libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt # libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt # libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt # libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt # libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt # libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt # libsession/src/main/java/org/session/libsession/utilities/mentions/MentionsManager.kt # libsignal/src/main/java/org/session/libsignal/service/loki/api/SwarmAPI.kt # libsignal/src/main/java/org/session/libsignal/service/loki/api/opengroups/PublicChat.kt # libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/MentionsManager.kt
2021-04-28 09:41:30 +02:00
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
2020-12-17 04:51:08 +01:00
import org.session.libsession.messaging.sending_receiving.MessageSender
2021-05-13 02:31:06 +02:00
import org.session.libsession.messaging.utilities.Data
import org.session.libsession.utilities.DecodedAudio
import org.session.libsession.utilities.InputStreamMediaDataSource
2021-05-21 07:02:34 +02:00
import org.session.libsession.utilities.UploadResult
2021-05-18 01:50:16 +02:00
import org.session.libsignal.messages.SignalServiceAttachmentStream
import org.session.libsignal.streams.*
import org.session.libsignal.utilities.Log
2021-05-18 01:50:16 +02:00
import org.session.libsignal.utilities.PushAttachmentData
2021-05-18 01:44:06 +02:00
import org.session.libsignal.utilities.Util
2020-12-17 04:51:08 +01:00
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
2020-12-02 06:39:02 +01:00
override var delegate: JobDelegate? = null
override var id: String? = null
override var failureCount: Int = 0
2020-12-17 04:51:08 +01:00
// Error
internal sealed class Error(val description: String) : Exception(description) {
2020-12-17 04:51:08 +01:00
object NoAttachment : Error("No such attachment.")
}
2020-12-02 06:39:02 +01:00
// Settings
override val maxFailureCount: Int = 20
2021-05-12 08:17:25 +02:00
2020-12-02 06:39:02 +01:00
companion object {
2021-03-02 02:24:09 +01:00
val TAG = AttachmentUploadJob::class.simpleName
2021-01-28 05:24:27 +01:00
val KEY: String = "AttachmentUploadJob"
2020-12-17 04:51:08 +01:00
2021-04-26 02:58:48 +02:00
// Keys used for database storage
2021-05-12 08:17:25 +02:00
private val ATTACHMENT_ID_KEY = "attachment_id"
private val THREAD_ID_KEY = "thread_id"
private val MESSAGE_KEY = "message"
private val MESSAGE_SEND_JOB_ID_KEY = "message_send_job_id"
2020-12-02 06:39:02 +01:00
}
override fun execute() {
try {
val storage = MessagingModuleConfiguration.shared.storage
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val attachment = messageDataProvider.getScaledSignalAttachmentStream(attachmentID)
2021-04-26 02:58:48 +02:00
?: return handleFailure(Error.NoAttachment)
2021-05-21 07:02:34 +02:00
val v2OpenGroup = storage.getV2OpenGroup(threadID.toLong())
if (v2OpenGroup != null) {
val keyAndResult = upload(attachment, v2OpenGroup.server, false) {
OpenGroupAPIV2.upload(it, v2OpenGroup.room, v2OpenGroup.server)
}
handleSuccess(attachment, keyAndResult.first, keyAndResult.second)
2021-05-21 07:02:34 +02:00
} else {
val keyAndResult = upload(attachment, FileServerAPIV2.server, true) {
FileServerAPIV2.upload(it)
}
handleSuccess(attachment, keyAndResult.first, keyAndResult.second)
}
} catch (e: java.lang.Exception) {
2021-04-26 02:58:48 +02:00
if (e == Error.NoAttachment) {
this.handlePermanentFailure(e)
} else {
this.handleFailure(e)
2020-12-17 04:51:08 +01:00
}
}
}
2021-05-21 07:02:34 +02:00
private fun upload(attachment: SignalServiceAttachmentStream, server: String, encrypt: Boolean, upload: (ByteArray) -> Promise<Long, Exception>): Pair<ByteArray, UploadResult> {
2021-05-13 05:19:08 +02:00
// Key
val key = if (encrypt) Util.getSecretBytes(64) else ByteArray(0)
2021-05-13 05:19:08 +02:00
// Length
val rawLength = attachment.length
2021-05-13 05:19:08 +02:00
val length = if (encrypt) {
val paddedLength = PaddingInputStream.getPaddedSize(rawLength)
AttachmentCipherOutputStream.getCiphertextLength(paddedLength)
} else {
attachment.length
}
// In & out streams
// PaddingInputStream adds padding as data is read out from it. AttachmentCipherOutputStream
// encrypts as it writes data.
val inputStream = if (encrypt) PaddingInputStream(attachment.inputStream, rawLength) else attachment.inputStream
val outputStreamFactory = if (encrypt) AttachmentCipherOutputStreamFactory(key) else PlaintextOutputStreamFactory()
// Create a digesting request body but immediately read it out to a buffer. Doing this makes
2021-05-13 05:19:08 +02:00
// it easier to deal with inputStream and outputStreamFactory.
val pad = PushAttachmentData(attachment.contentType, inputStream, length, outputStreamFactory, attachment.listener)
val contentType = "application/octet-stream"
2021-05-13 05:34:35 +02:00
val drb = DigestingRequestBody(pad.data, pad.outputStreamFactory, contentType, pad.dataSize, pad.listener)
2021-05-13 05:19:08 +02:00
Log.d("Loki", "File size: ${length.toDouble() / 1000} kb.")
val b = Buffer()
2021-05-13 05:34:35 +02:00
drb.writeTo(b)
2021-05-13 05:19:08 +02:00
val data = b.readByteArray()
// Upload the data
val id = upload(data).get()
val digest = drb.transmittedDigest
// Return
2021-05-21 07:02:34 +02:00
return Pair(key, UploadResult(id, "${server}/files/$id", digest))
}
2021-05-21 07:02:34 +02:00
private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) {
2021-05-12 08:17:25 +02:00
Log.d(TAG, "Attachment uploaded successfully.")
2020-12-17 04:51:08 +01:00
delegate?.handleJobSucceeded(this)
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
messageDataProvider.handleSuccessfulAttachmentUpload(attachmentID, attachment, attachmentKey, uploadResult)
if (attachment.contentType.startsWith("audio/")) {
// process the duration
try {
val inputStream = messageDataProvider.getAttachmentStream(attachmentID)!!.inputStream!!
InputStreamMediaDataSource(inputStream).use { mediaDataSource ->
val durationMs = (DecodedAudio.create(mediaDataSource).totalDuration / 1000.0).toLong()
messageDataProvider.getDatabaseAttachment(attachmentID)?.attachmentId?.let { attachmentId ->
messageDataProvider.updateAudioAttachmentDuration(attachmentId, durationMs, threadID.toLong())
}
}
} catch (e: Exception) {
Log.e("Loki", "Couldn't process audio attachment", e)
}
}
2021-04-26 03:14:45 +02:00
MessagingModuleConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
2020-12-17 04:51:08 +01:00
}
private fun handlePermanentFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed permanently due to error: $this.")
delegate?.handleJobFailedPermanently(this, e)
2021-05-13 05:34:35 +02:00
MessagingModuleConfiguration.shared.messageDataProvider.handleFailedAttachmentUpload(attachmentID)
2020-12-17 04:51:08 +01:00
failAssociatedMessageSendJob(e)
}
private fun handleFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed due to error: $this.")
delegate?.handleJobFailed(this, e)
if (failureCount + 1 >= maxFailureCount) {
2020-12-17 04:51:08 +01:00
failAssociatedMessageSendJob(e)
}
}
private fun failAssociatedMessageSendJob(e: Exception) {
2021-04-26 03:14:45 +02:00
val storage = MessagingModuleConfiguration.shared.storage
2020-12-17 04:51:08 +01:00
val messageSendJob = storage.getMessageSendJob(messageSendJobID)
MessageSender.handleFailedMessageSend(this.message, e)
2020-12-17 04:51:08 +01:00
if (messageSendJob != null) {
storage.markJobAsFailedPermanently(messageSendJobID)
2020-12-17 04:51:08 +01:00
}
2020-12-02 06:39:02 +01:00
}
2021-01-22 05:19:41 +01:00
override fun serialize(): Data {
val kryo = Kryo()
kryo.isRegistrationRequired = false
val serializedMessage = ByteArray(4096)
val output = Output(serializedMessage, Job.MAX_BUFFER_SIZE)
kryo.writeClassAndObject(output, message)
output.close()
2021-05-12 08:17:25 +02:00
return Data.Builder()
.putLong(ATTACHMENT_ID_KEY, attachmentID)
.putString(THREAD_ID_KEY, threadID)
.putByteArray(MESSAGE_KEY, output.toBytes())
2021-05-12 08:17:25 +02:00
.putString(MESSAGE_SEND_JOB_ID_KEY, messageSendJobID)
2021-05-21 01:04:32 +02:00
.build()
}
2021-01-28 05:24:27 +01:00
override fun getFactoryKey(): String {
2021-03-03 05:14:45 +01:00
return KEY
2021-01-28 05:24:27 +01:00
}
class Factory: Job.Factory<AttachmentUploadJob> {
2021-04-26 02:58:48 +02:00
override fun create(data: Data): AttachmentUploadJob? {
2021-05-12 08:17:25 +02:00
val serializedMessage = data.getByteArray(MESSAGE_KEY)
val kryo = Kryo()
kryo.isRegistrationRequired = false
val input = Input(serializedMessage)
val message: Message
try {
message = kryo.readClassAndObject(input) as Message
} catch (e: Exception) {
Log.e("Loki","Couldn't serialize the AttachmentUploadJob", e)
return null
}
input.close()
2021-05-12 08:17:25 +02:00
return AttachmentUploadJob(
data.getLong(ATTACHMENT_ID_KEY),
data.getString(THREAD_ID_KEY)!!,
message,
data.getString(MESSAGE_SEND_JOB_ID_KEY)!!
2021-05-12 08:17:25 +02:00
)
}
}
2020-11-25 02:06:41 +01:00
}