From 26b2ead673628a43542f0cda2e6c057fceaf58ea Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 17 Dec 2020 14:27:04 +1100 Subject: [PATCH 1/5] kotlin impl of SignalServiceAttachment classes --- .../database/dto/DatabaseAttachmentDTO.kt | 75 ----------- .../attachments/SignalServiceAttachment.kt | 123 ++++++++++++++++++ .../SignalServiceAttachmentPointer.kt | 30 +++++ .../SignalServiceAttachmentStream.kt | 75 +++++++++++ 4 files changed, 228 insertions(+), 75 deletions(-) delete mode 100644 libsession/src/main/java/org/session/libsession/database/dto/DatabaseAttachmentDTO.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachment.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentPointer.kt create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentStream.kt diff --git a/libsession/src/main/java/org/session/libsession/database/dto/DatabaseAttachmentDTO.kt b/libsession/src/main/java/org/session/libsession/database/dto/DatabaseAttachmentDTO.kt deleted file mode 100644 index bf5f31f822..0000000000 --- a/libsession/src/main/java/org/session/libsession/database/dto/DatabaseAttachmentDTO.kt +++ /dev/null @@ -1,75 +0,0 @@ -package org.session.libsession.database.dto - -import android.util.Size -import com.google.protobuf.ByteString -import org.session.libsignal.service.internal.push.SignalServiceProtos -import kotlin.math.round - -class DatabaseAttachmentDTO { - var attachmentId: Long = 0 - - var contentType: String? = null - - var fileName: String? = null - - var url: String? = null - - var caption: String? = null - - var size: Int = 0 - - var key: ByteString? = null - - var digest: ByteString? = null - - var flags: Int = 0 - - var width: Int = 0 - - var height: Int = 0 - - val isVoiceNote: Boolean = false - - var shouldHaveImageSize: Boolean = false - - val isUploaded: Boolean = false - - fun toProto(): SignalServiceProtos.AttachmentPointer? { - val builder = SignalServiceProtos.AttachmentPointer.newBuilder() - builder.contentType = this.contentType - - if (!this.fileName.isNullOrEmpty()) { - builder.fileName = this.fileName - } - if (!this.caption.isNullOrEmpty()) { - builder.caption = this.caption - } - - builder.size = this.size - builder.key = this.key - builder.digest = this.digest - builder.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0 - - //TODO I did copy the behavior of iOS below, not sure if that's relevant here... - if (this.shouldHaveImageSize) { - if (this.width < Int.MAX_VALUE && this.height < Int.MAX_VALUE) { - val imageSize= Size(this.width, this.height) - val imageWidth = round(imageSize.width.toDouble()) - val imageHeight = round(imageSize.height.toDouble()) - if (imageWidth > 0 && imageHeight > 0) { - builder.width = imageWidth.toInt() - builder.height = imageHeight.toInt() - } - } - } - - builder.url = this.url - - try { - return builder.build() - } catch (e: Exception) { - return null - } - } - -} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachment.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachment.kt new file mode 100644 index 0000000000..7c8a1f2543 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachment.kt @@ -0,0 +1,123 @@ +package org.session.libsession.messaging.sending_receiving.attachments + +import com.google.protobuf.ByteString +import org.session.libsignal.libsignal.util.guava.Optional +import org.session.libsignal.service.api.messages.SignalServiceAttachment +import java.io.InputStream + +abstract class SignalServiceAttachment protected constructor(val contentType: String?) { + + var attachmentId: Long = 0 + var isGif: Boolean = false + var isImage: Boolean = false + var isVideo: Boolean = false + var isAudio: Boolean = false + var url: String? = null + var key: ByteString? = null + + abstract fun isStream(): Boolean + abstract fun isPointer(): Boolean + fun asStream(): SignalServiceAttachmentStream { + return this as SignalServiceAttachmentStream + } + + fun asPointer(): SignalServiceAttachmentPointer { + return this as SignalServiceAttachmentPointer + } + + fun shouldHaveImageSize(): Boolean { + return (isVideo || isImage || isGif); + } + + class Builder internal constructor() { + private var inputStream: InputStream? = null + private var contentType: String? = null + private var fileName: String? = null + private var length: Long = 0 + private var listener: SignalServiceAttachment.ProgressListener? = null + private var voiceNote = false + private var width = 0 + private var height = 0 + private var caption: String? = null + fun withStream(inputStream: InputStream?): Builder { + this.inputStream = inputStream + return this + } + + fun withContentType(contentType: String?): Builder { + this.contentType = contentType + return this + } + + fun withLength(length: Long): Builder { + this.length = length + return this + } + + fun withFileName(fileName: String?): Builder { + this.fileName = fileName + return this + } + + fun withListener(listener: SignalServiceAttachment.ProgressListener?): Builder { + this.listener = listener + return this + } + + fun withVoiceNote(voiceNote: Boolean): Builder { + this.voiceNote = voiceNote + return this + } + + fun withWidth(width: Int): Builder { + this.width = width + return this + } + + fun withHeight(height: Int): Builder { + this.height = height + return this + } + + fun withCaption(caption: String?): Builder { + this.caption = caption + return this + } + + fun build(): SignalServiceAttachmentStream { + requireNotNull(inputStream) { "Must specify stream!" } + requireNotNull(contentType) { "No content type specified!" } + require(length != 0L) { "No length specified!" } + return SignalServiceAttachmentStream(inputStream, contentType, length, Optional.fromNullable(fileName), voiceNote, Optional.absent(), width, height, Optional.fromNullable(caption), listener) + } + } + + /** + * An interface to receive progress information on upload/download of + * an attachment. + */ + /*interface ProgressListener { + /** + * Called on a progress change event. + * + * @param total The total amount to transmit/receive in bytes. + * @param progress The amount that has been transmitted/received in bytes thus far + */ + fun onAttachmentProgress(total: Long, progress: Long) + }*/ + + companion object { + @JvmStatic + fun newStreamBuilder(): Builder { + return Builder() + } + } +} + +// matches values in AttachmentDatabase.java +enum class AttachmentState(val value: Int) { + DONE(0), + STARTED(1), + PENDING(2), + FAILED(3) +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentPointer.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentPointer.kt new file mode 100644 index 0000000000..07f39e2609 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentPointer.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014-2017 Open Whisper Systems + * + * Licensed according to the LICENSE file in this repository. + */ +package org.session.libsession.messaging.sending_receiving.attachments + +import org.session.libsignal.libsignal.util.guava.Optional +import org.session.libsignal.service.api.messages.SignalServiceAttachment + +/** + * Represents a received SignalServiceAttachment "handle." This + * is a pointer to the actual attachment content, which needs to be + * retrieved using [SignalServiceMessageReceiver.retrieveAttachment] + * + * @author Moxie Marlinspike + */ +class SignalServiceAttachmentPointer(val id: Long, contentType: String?, val key: ByteArray?, + val size: Optional, val preview: Optional, + val width: Int, val height: Int, + val digest: Optional, val fileName: Optional, + val voiceNote: Boolean, val caption: Optional, val url: String) : SignalServiceAttachment(contentType) { + override fun isStream(): Boolean { + return false + } + + override fun isPointer(): Boolean { + return true + } +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentStream.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentStream.kt new file mode 100644 index 0000000000..24fd214bee --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentStream.kt @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2014-2016 Open Whisper Systems + * + * Licensed according to the LICENSE file in this repository. + */ +package org.session.libsession.messaging.sending_receiving.attachments + +import android.util.Size +import com.google.protobuf.ByteString +import org.session.libsignal.libsignal.util.guava.Optional +import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.service.api.messages.SignalServiceAttachment as SAttachment +import java.io.InputStream +import kotlin.math.round + +/** + * Represents a local SignalServiceAttachment to be sent. + */ +class SignalServiceAttachmentStream(val inputStream: InputStream?, contentType: String?, val length: Long, val fileName: Optional?, val voiceNote: Boolean, val preview: Optional, val width: Int, val height: Int, val caption: Optional, val listener: SAttachment.ProgressListener?) : SignalServiceAttachment(contentType) { + + constructor(inputStream: InputStream?, contentType: String?, length: Long, fileName: Optional?, voiceNote: Boolean, listener: SAttachment.ProgressListener?) : this(inputStream, contentType, length, fileName, voiceNote, Optional.absent(), 0, 0, Optional.absent(), listener) {} + + // Though now required, `digest` may be null for pre-existing records or from + // messages received from other clients + var digest: ByteArray? = null + + // This only applies for attachments being uploaded. + var isUploaded: Boolean = false + + override fun isStream(): Boolean { + return true + } + + override fun isPointer(): Boolean { + return false + } + + fun toProto(): SignalServiceProtos.AttachmentPointer? { + val builder = SignalServiceProtos.AttachmentPointer.newBuilder() + builder.contentType = this.contentType + + if (!this.fileName?.get().isNullOrEmpty()) { + builder.fileName = this.fileName?.get() + } + if (!this.caption.get().isNullOrEmpty()) { + builder.caption = this.caption.get() + } + + builder.size = this.length.toInt() + builder.key = this.key + builder.digest = ByteString.copyFrom(this.digest) + builder.flags = if (this.voiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0 + + //TODO I did copy the behavior of iOS below, not sure if that's relevant here... + if (this.shouldHaveImageSize()) { + if (this.width < Int.MAX_VALUE && this.height < Int.MAX_VALUE) { + val imageSize= Size(this.width, this.height) + val imageWidth = round(imageSize.width.toDouble()) + val imageHeight = round(imageSize.height.toDouble()) + if (imageWidth > 0 && imageHeight > 0) { + builder.width = imageWidth.toInt() + builder.height = imageHeight.toInt() + } + } + } + + builder.url = this.url + + try { + return builder.build() + } catch (e: Exception) { + return null + } + } +} \ No newline at end of file From c286efae9dd08cf38d31b3d63b005f30442ceee7 Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 17 Dec 2020 14:36:32 +1100 Subject: [PATCH 2/5] SignalServiceAttachment classes renamed to SessionServiceAttachment to avoid confusion --- ...ceAttachment.kt => SessionServiceAttachment.kt} | 14 +++++++------- ...inter.kt => SessionServiceAttachmentPointer.kt} | 11 +++++------ ...Stream.kt => SessionServiceAttachmentStream.kt} | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) rename libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/{SignalServiceAttachment.kt => SessionServiceAttachment.kt} (85%) rename libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/{SignalServiceAttachmentPointer.kt => SessionServiceAttachmentPointer.kt} (51%) rename libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/{SignalServiceAttachmentStream.kt => SessionServiceAttachmentStream.kt} (88%) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachment.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachment.kt similarity index 85% rename from libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachment.kt rename to libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachment.kt index 7c8a1f2543..800bbed060 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachment.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachment.kt @@ -5,7 +5,7 @@ import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachment import java.io.InputStream -abstract class SignalServiceAttachment protected constructor(val contentType: String?) { +abstract class SessionServiceAttachment protected constructor(val contentType: String?) { var attachmentId: Long = 0 var isGif: Boolean = false @@ -17,12 +17,12 @@ abstract class SignalServiceAttachment protected constructor(val contentType: St abstract fun isStream(): Boolean abstract fun isPointer(): Boolean - fun asStream(): SignalServiceAttachmentStream { - return this as SignalServiceAttachmentStream + fun asStream(): SessionServiceAttachmentStream { + return this as SessionServiceAttachmentStream } - fun asPointer(): SignalServiceAttachmentPointer { - return this as SignalServiceAttachmentPointer + fun asPointer(): SessionServiceAttachmentPointer { + return this as SessionServiceAttachmentPointer } fun shouldHaveImageSize(): Boolean { @@ -84,11 +84,11 @@ abstract class SignalServiceAttachment protected constructor(val contentType: St return this } - fun build(): SignalServiceAttachmentStream { + fun build(): SessionServiceAttachmentStream { requireNotNull(inputStream) { "Must specify stream!" } requireNotNull(contentType) { "No content type specified!" } require(length != 0L) { "No length specified!" } - return SignalServiceAttachmentStream(inputStream, contentType, length, Optional.fromNullable(fileName), voiceNote, Optional.absent(), width, height, Optional.fromNullable(caption), listener) + return SessionServiceAttachmentStream(inputStream, contentType, length, Optional.fromNullable(fileName), voiceNote, Optional.absent(), width, height, Optional.fromNullable(caption), listener) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentPointer.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachmentPointer.kt similarity index 51% rename from libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentPointer.kt rename to libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachmentPointer.kt index 07f39e2609..ece1b30c3d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentPointer.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachmentPointer.kt @@ -6,7 +6,6 @@ package org.session.libsession.messaging.sending_receiving.attachments import org.session.libsignal.libsignal.util.guava.Optional -import org.session.libsignal.service.api.messages.SignalServiceAttachment /** * Represents a received SignalServiceAttachment "handle." This @@ -15,11 +14,11 @@ import org.session.libsignal.service.api.messages.SignalServiceAttachment * * @author Moxie Marlinspike */ -class SignalServiceAttachmentPointer(val id: Long, contentType: String?, val key: ByteArray?, - val size: Optional, val preview: Optional, - val width: Int, val height: Int, - val digest: Optional, val fileName: Optional, - val voiceNote: Boolean, val caption: Optional, val url: String) : SignalServiceAttachment(contentType) { +class SessionServiceAttachmentPointer(val id: Long, contentType: String?, key: ByteArray?, + val size: Optional, val preview: Optional, + val width: Int, val height: Int, + val digest: Optional, val fileName: Optional, + val voiceNote: Boolean, val caption: Optional, url: String) : SessionServiceAttachment(contentType) { override fun isStream(): Boolean { return false } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentStream.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachmentStream.kt similarity index 88% rename from libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentStream.kt rename to libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachmentStream.kt index 24fd214bee..caebe874a6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SignalServiceAttachmentStream.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachmentStream.kt @@ -16,7 +16,7 @@ import kotlin.math.round /** * Represents a local SignalServiceAttachment to be sent. */ -class SignalServiceAttachmentStream(val inputStream: InputStream?, contentType: String?, val length: Long, val fileName: Optional?, val voiceNote: Boolean, val preview: Optional, val width: Int, val height: Int, val caption: Optional, val listener: SAttachment.ProgressListener?) : SignalServiceAttachment(contentType) { +class SessionServiceAttachmentStream(val inputStream: InputStream?, contentType: String?, val length: Long, val fileName: Optional?, val voiceNote: Boolean, val preview: Optional, val width: Int, val height: Int, val caption: Optional, val listener: SAttachment.ProgressListener?) : SessionServiceAttachment(contentType) { constructor(inputStream: InputStream?, contentType: String?, length: Long, fileName: Optional?, voiceNote: Boolean, listener: SAttachment.ProgressListener?) : this(inputStream, contentType, length, fileName, voiceNote, Optional.absent(), 0, 0, Optional.absent(), listener) {} From 0467147cfe9f7c8270b78ad3f142782b862d551c Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 17 Dec 2020 14:45:57 +1100 Subject: [PATCH 3/5] use of SessionServiceAttachment instead if SignalSA --- .../attachments/DatabaseAttachmentProvider.kt | 57 ++++++++++--------- .../database/MessageDataProvider.kt | 14 +++-- .../messaging/jobs/MessageSendJob.kt | 2 +- .../messaging/messages/visible/LinkPreview.kt | 2 +- .../messaging/messages/visible/Quote.kt | 4 +- .../messages/visible/VisibleMessage.kt | 2 +- 6 files changed, 45 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index 468aae89ee..c1fe16fcd8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -2,27 +2,35 @@ package org.thoughtcrime.securesms.attachments import android.content.Context import com.google.protobuf.ByteString -import org.session.libsession.database.dto.DatabaseAttachmentDTO import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.dto.AttachmentState -import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer +import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream +import org.session.libsignal.libsignal.util.guava.Optional import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.database.SmsDatabase import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.jobs.AttachmentUploadJob +import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.MediaUtil class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider { - override fun getAttachment(attachmentId: Long): DatabaseAttachmentDTO? { + + override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? { val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null - return databaseAttachment.toDTO() + return databaseAttachment.toAttachmentStream(context) } - override fun setAttachmentState(attachmentState: AttachmentState, attachment: DatabaseAttachmentDTO, messageID: Long) { + override fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? { val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) - attachmentDatabase.setTransferState(messageID, AttachmentId(attachment.attachmentId, 0), attachmentState.value) + val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null + return databaseAttachment.toAttachmentPointer() + } + + override fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) { + val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) + attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value) } @Throws(Exception::class) @@ -38,29 +46,26 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) } -// Extension to DatabaseAttachment class +fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer { + return SessionServiceAttachmentPointer(attachmentId.rowId, contentType, key?.toByteArray(), Optional.fromNullable(size.toInt()), Optional.absent(), width, height, Optional.fromNullable(digest), Optional.fromNullable(fileName), isVoiceNote, Optional.fromNullable(caption), url) +} -fun DatabaseAttachment.toDTO(): DatabaseAttachmentDTO { - var databaseAttachmentDTO = DatabaseAttachmentDTO() - databaseAttachmentDTO.attachmentId = this.attachmentId.rowId - databaseAttachmentDTO.contentType = this.contentType - databaseAttachmentDTO.fileName = this.fileName - databaseAttachmentDTO.caption = this.caption +fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttachmentStream { + val stream = PartAuthority.getAttachmentStream(context, this.dataUri!!) + var attachmentStream = SessionServiceAttachmentStream(stream, this.contentType, this.size, Optional.fromNullable(this.fileName), this.isVoiceNote, Optional.absent(), this.width, this.height, Optional.fromNullable(this.caption), null) + attachmentStream.attachmentId = this.attachmentId.rowId + attachmentStream.isAudio = MediaUtil.isAudio(this) + attachmentStream.isGif = MediaUtil.isGif(this) + attachmentStream.isVideo = MediaUtil.isVideo(this) + attachmentStream.isImage = MediaUtil.isImage(this) - databaseAttachmentDTO.size = this.size.toInt() - databaseAttachmentDTO.key = ByteString.copyFrom(this.key?.toByteArray()) - databaseAttachmentDTO.digest = ByteString.copyFrom(this.digest) - databaseAttachmentDTO.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0 + attachmentStream.key = ByteString.copyFrom(this.key?.toByteArray()) + attachmentStream.digest = this.digest + //attachmentStream.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0 - databaseAttachmentDTO.url = this.url + attachmentStream.url = this.url - if (this.shouldHaveImageSize()) { - databaseAttachmentDTO.shouldHaveImageSize = true - databaseAttachmentDTO.width = this.width - databaseAttachmentDTO.height = this.height - } - - return databaseAttachmentDTO + return attachmentStream } fun DatabaseAttachment.shouldHaveImageSize(): Boolean { diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index fc232cfda0..f2efdb6be6 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -1,14 +1,18 @@ package org.session.libsession.database -import org.session.libsession.database.dto.AttachmentState -import org.session.libsession.database.dto.DatabaseAttachmentDTO -import org.session.libsession.messaging.messages.visible.Attachment +import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState +import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer +import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream interface MessageDataProvider { - fun getAttachment(attachmentId: Long): DatabaseAttachmentDTO? + //fun getAttachment(attachmentId: Long): SignalServiceAttachmentStream? - fun setAttachmentState(attachmentState: AttachmentState, attachment: DatabaseAttachmentDTO, messageID: Long) + fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? + + fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? + + fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) fun isOutgoingMessage(timestamp: Long): Boolean diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt index 5d707d5e93..ff12856545 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt @@ -27,7 +27,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { val message = message as? VisibleMessage message?.let { if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted - val attachments = message.attachmentIDs.map { messageDataProvider.getAttachment(it) }.filterNotNull() + val attachments = message.attachmentIDs.map { messageDataProvider.getAttachmentStream(it) }.filterNotNull() val attachmentsToUpload = attachments.filter { !it.isUploaded } attachmentsToUpload.forEach { if(MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId) != null) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/LinkPreview.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/LinkPreview.kt index 3999148dff..19f9b69a10 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/LinkPreview.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/LinkPreview.kt @@ -44,7 +44,7 @@ class LinkPreview() { title?.let { linkPreviewProto.title = title } val attachmentID = attachmentID attachmentID?.let { - val attachmentProto = MessagingConfiguration.shared.messageDataProvider.getAttachment(attachmentID) + val attachmentProto = MessagingConfiguration.shared.messageDataProvider.getAttachmentStream(attachmentID) attachmentProto?.let { linkPreviewProto.image = attachmentProto.toProto() } } // Build diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Quote.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Quote.kt index ed6dbb29ea..178d715f66 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Quote.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Quote.kt @@ -60,7 +60,7 @@ class Quote() { private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder, messageDataProvider: MessageDataProvider) { val attachmentID = attachmentID ?: return - val attachmentProto = messageDataProvider.getAttachment(attachmentID) + val attachmentProto = messageDataProvider.getAttachmentStream(attachmentID) if (attachmentProto == null) { Log.w(TAG, "Ignoring invalid attachment for quoted message.") return @@ -74,7 +74,7 @@ class Quote() { } val quotedAttachmentProto = SignalServiceProtos.DataMessage.Quote.QuotedAttachment.newBuilder() quotedAttachmentProto.contentType = attachmentProto.contentType - val fileName = attachmentProto.fileName + val fileName = attachmentProto.fileName?.get() fileName?.let { quotedAttachmentProto.fileName = fileName } quotedAttachmentProto.thumbnail = attachmentProto.toProto() try { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index b761eb2846..781c62399a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -90,7 +90,7 @@ class VisibleMessage : Message() { } } //Attachments - val attachments = attachmentIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getAttachment(it) } + val attachments = attachmentIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getAttachmentStream(it) } if (!attachments.all { it.isUploaded }) { if (BuildConfig.DEBUG) { //TODO equivalent to iOS's preconditionFailure From 9304971e5088d7e6795b730bb3e93fcf358ae95c Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 17 Dec 2020 14:51:08 +1100 Subject: [PATCH 4/5] AttachmentUploadJob implementation --- .../messaging/jobs/AttachmentUploadJob.kt | 81 ++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index d0fc7ce7e7..1088c91461 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -1,17 +1,94 @@ package org.session.libsession.messaging.jobs -class AttachmentUploadJob : Job { +import org.session.libsession.messaging.MessagingConfiguration +import org.session.libsession.messaging.fileserver.FileServerAPI +import org.session.libsession.messaging.messages.Message +import org.session.libsession.messaging.sending_receiving.MessageSender +import org.session.libsession.messaging.utilities.DotNetAPI +import org.session.libsignal.libsignal.logging.Log +import org.session.libsignal.service.internal.push.PushAttachmentData +import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory +import org.session.libsignal.service.internal.util.Util +import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory + +class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job { + override var delegate: JobDelegate? = null override var id: String? = null override var failureCount: Int = 0 + // Error + internal sealed class Error(val description: String) : Exception() { + object NoAttachment : Error("No such attachment.") + } + // Settings override val maxFailureCount: Int = 20 companion object { + val TAG = AttachmentUploadJob::class.qualifiedName + val collection: String = "AttachmentUploadJobCollection" + val maxFailureCount: Int = 20 } override fun execute() { - TODO("Not yet implemented") + try { + val attachmentStream = MessagingConfiguration.shared.messageDataProvider.getAttachmentStream(attachmentID) + ?: return handleFailure(Error.NoAttachment) + + val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID) + val server = openGroup?.server ?: FileServerAPI.server + + //TODO add some encryption stuff here + val isEncryptionRequired = false + //val isEncryptionRequired = (server == FileServerAPI.server) + + val attachmentKey = Util.getSecretBytes(64) + val outputStreamFactory = if (isEncryptionRequired) AttachmentCipherOutputStreamFactory(attachmentKey) else PlaintextOutputStreamFactory() + val ciphertextLength = attachmentStream.length + + val attachmentData = PushAttachmentData(attachmentStream.contentType, attachmentStream.inputStream, ciphertextLength, outputStreamFactory, attachmentStream.listener) + + FileServerAPI.shared.uploadAttachment(server, attachmentData) + + } catch (e: java.lang.Exception) { + if (e is Error && e == Error.NoAttachment) { + this.handlePermanentFailure(e) + } else if (e is DotNetAPI.Error && !e.isRetryable) { + this.handlePermanentFailure(e) + } else { + this.handleFailure(e) + } + } + } + + private fun handleSuccess() { + Log.w(TAG, "Attachment uploaded successfully.") + delegate?.handleJobSucceeded(this) + MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID) + //TODO interaction stuff, not sure how to deal with that + } + + private fun handlePermanentFailure(e: Exception) { + Log.w(TAG, "Attachment upload failed permanently due to error: $this.") + delegate?.handleJobFailedPermanently(this, e) + failAssociatedMessageSendJob(e) + } + + private fun handleFailure(e: Exception) { + Log.w(TAG, "Attachment upload failed due to error: $this.") + delegate?.handleJobFailed(this, e) + if (failureCount + 1 == AttachmentUploadJob.maxFailureCount) { + failAssociatedMessageSendJob(e) + } + } + + private fun failAssociatedMessageSendJob(e: Exception) { + val storage = MessagingConfiguration.shared.storage + val messageSendJob = storage.getMessageSendJob(messageSendJobID) + MessageSender.handleFailedMessageSend(this.message!!, e) + if (messageSendJob != null) { + storage.markJobAsFailed(messageSendJob) + } } } \ No newline at end of file From 873e0099f93114027ebde523d11561adde784bd3 Mon Sep 17 00:00:00 2001 From: Brice Date: Thu, 17 Dec 2020 16:52:22 +1100 Subject: [PATCH 5/5] fix --- .../securesms/attachments/DatabaseAttachmentProvider.kt | 2 +- .../sending_receiving/attachments/SessionServiceAttachment.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index c1fe16fcd8..90a3834d2f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.attachments import android.content.Context import com.google.protobuf.ByteString import org.session.libsession.database.MessageDataProvider -import org.session.libsession.database.dto.AttachmentState +import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream import org.session.libsignal.libsignal.util.guava.Optional diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachment.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachment.kt index 800bbed060..8b91c0f541 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachment.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/attachments/SessionServiceAttachment.kt @@ -12,7 +12,7 @@ abstract class SessionServiceAttachment protected constructor(val contentType: S var isImage: Boolean = false var isVideo: Boolean = false var isAudio: Boolean = false - var url: String? = null + var url: String = "" var key: ByteString? = null abstract fun isStream(): Boolean