mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-11 19:07:40 +00:00
Merge branch 'refactor' of https://github.com/RyanRory/loki-messenger-android into refactor
This commit is contained in:
@@ -1,14 +1,28 @@
|
||||
package org.session.libsession.database
|
||||
|
||||
import org.session.libsession.database.dto.DatabaseAttachmentDTO
|
||||
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.service.api.messages.SignalServiceAttachmentPointer
|
||||
|
||||
interface MessageDataProvider {
|
||||
|
||||
fun getAttachment(uniqueID: String): DatabaseAttachmentDTO?
|
||||
//fun getAttachment(attachmentId: Long): SignalServiceAttachmentStream?
|
||||
|
||||
fun getAttachmentPointer(attachmentID: String): SignalServiceAttachmentPointer?
|
||||
|
||||
fun getMessageID(serverID: Long): Long?
|
||||
fun deleteMessage(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
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun uploadAttachment(attachmentId: Long)
|
||||
|
||||
}
|
@@ -1,73 +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 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -38,7 +38,7 @@ interface StorageProtocol {
|
||||
fun markJobAsSucceeded(job: Job)
|
||||
fun markJobAsFailed(job: Job)
|
||||
fun getAllPendingJobs(type: String): List<Job>
|
||||
fun getAttachmentUploadJob(attachmentID: String): AttachmentUploadJob?
|
||||
fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob?
|
||||
fun getMessageSendJob(messageSendJobID: String): MessageSendJob?
|
||||
fun resumeMessageSendJobIfNeeded(messageSendJobID: String)
|
||||
fun isJobCanceled(job: Job): Boolean
|
||||
|
@@ -4,12 +4,12 @@ import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.bind
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Request
|
||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.libsignal.util.Hex
|
||||
import org.session.libsignal.service.internal.util.Base64
|
||||
import org.session.libsignal.service.internal.util.JsonUtil
|
||||
import org.session.libsignal.service.loki.api.SnodeAPI
|
||||
import org.session.libsignal.service.loki.api.LokiDotNetAPI
|
||||
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
|
||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
|
||||
@@ -18,7 +18,7 @@ import java.net.URL
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.collections.set
|
||||
|
||||
class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : LokiDotNetAPI(userPublicKey, userPrivateKey, database) {
|
||||
class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() {
|
||||
|
||||
companion object {
|
||||
// region Settings
|
||||
@@ -49,6 +49,7 @@ class FileServerAPI(public val server: String, userPublicKey: String, userPrivat
|
||||
* possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds.
|
||||
*/
|
||||
public val fileSizeORMultiplier = 2 // TODO: It should be possible to set this to 1.5?
|
||||
val server = "https://file.getsession.org"
|
||||
public val fileStorageBucketURL = "https://file-static.lokinet.org"
|
||||
// endregion
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,13 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.deferred
|
||||
import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
||||
import org.session.libsession.messaging.sending_receiving.handle
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
|
||||
class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
|
||||
|
||||
override var delegate: JobDelegate? = null
|
||||
override var id: String? = null
|
||||
override var failureCount: Int = 0
|
||||
@@ -10,6 +15,8 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
|
||||
// Settings
|
||||
override val maxFailureCount: Int = 10
|
||||
companion object {
|
||||
val TAG = MessageReceiveJob::class.qualifiedName
|
||||
|
||||
val collection: String = "MessageReceiveJobCollection"
|
||||
}
|
||||
|
||||
@@ -18,6 +25,33 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
|
||||
}
|
||||
|
||||
fun executeAsync(): Promise<Unit, Exception> {
|
||||
TODO("Not yet implemented")
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
try {
|
||||
val (message, proto) = MessageReceiver.parse(this.data, this.openGroupMessageServerID)
|
||||
MessageReceiver.handle(message, proto, this.openGroupID)
|
||||
this.handleSuccess()
|
||||
deferred.resolve(Unit)
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Couldn't receive message due to error: $e.")
|
||||
val error = e as? MessageReceiver.Error
|
||||
error?.let {
|
||||
if (!error.isRetryable) this.handlePermanentFailure(error)
|
||||
}
|
||||
this.handleFailure(e)
|
||||
deferred.resolve(Unit) // The promise is just used to keep track of when we're done
|
||||
}
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
private fun handleSuccess() {
|
||||
delegate?.handleJobSucceeded(this)
|
||||
}
|
||||
|
||||
private fun handlePermanentFailure(e: Exception) {
|
||||
delegate?.handleJobFailedPermanently(this, e)
|
||||
}
|
||||
|
||||
private fun handleFailure(e: Exception) {
|
||||
delegate?.handleJobFailed(this, e)
|
||||
}
|
||||
}
|
@@ -1,9 +1,15 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import org.session.libsession.messaging.MessagingConfiguration
|
||||
import org.session.libsession.messaging.messages.Destination
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsignal.libsignal.logging.Log
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
|
||||
class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
|
||||
override var delegate: JobDelegate? = null
|
||||
override var id: String? = null
|
||||
override var failureCount: Int = 0
|
||||
@@ -11,10 +17,54 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
// Settings
|
||||
override val maxFailureCount: Int = 10
|
||||
companion object {
|
||||
val TAG = MessageSendJob::class.qualifiedName
|
||||
|
||||
val collection: String = "MessageSendJobCollection"
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
TODO("Not yet implemented")
|
||||
val messageDataProvider = MessagingConfiguration.shared.messageDataProvider
|
||||
val message = message as? VisibleMessage
|
||||
message?.let {
|
||||
if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
|
||||
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) {
|
||||
// Wait for it to finish
|
||||
} else {
|
||||
val job = AttachmentUploadJob(it.attachmentId, message.threadID!!, message, id!!)
|
||||
JobQueue.shared.add(job)
|
||||
}
|
||||
}
|
||||
if (attachmentsToUpload.isNotEmpty()) return // Wait for all attachments to upload before continuing
|
||||
}
|
||||
MessageSender.send(this.message, this.destination).success {
|
||||
this.handleSuccess()
|
||||
}.fail { exception ->
|
||||
Log.e(TAG, "Couldn't send message due to error: $exception.")
|
||||
val e = exception as? MessageSender.Error
|
||||
e?.let {
|
||||
if (!e.isRetryable) this.handlePermanentFailure(e)
|
||||
}
|
||||
this.handleFailure(exception)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSuccess() {
|
||||
delegate?.handleJobSucceeded(this)
|
||||
}
|
||||
|
||||
private fun handlePermanentFailure(error: Exception) {
|
||||
delegate?.handleJobFailedPermanently(this, error)
|
||||
}
|
||||
|
||||
private fun handleFailure(error: Exception) {
|
||||
Log.w(TAG, "Failed to send $message::class.simpleName.")
|
||||
val message = message as? VisibleMessage
|
||||
message?.let {
|
||||
if(!MessagingConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
|
||||
}
|
||||
delegate?.handleJobFailed(this, error)
|
||||
}
|
||||
}
|
@@ -8,7 +8,7 @@ class LinkPreview() {
|
||||
|
||||
var title: String? = null
|
||||
var url: String? = null
|
||||
var attachmentID: String? = null
|
||||
var attachmentID: Long? = 0
|
||||
|
||||
companion object {
|
||||
const val TAG = "LinkPreview"
|
||||
@@ -21,7 +21,7 @@ class LinkPreview() {
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(title: String?, url: String, attachmentID: String?) : this() {
|
||||
internal constructor(title: String?, url: String, attachmentID: Long?) : this() {
|
||||
this.title = title
|
||||
this.url = url
|
||||
this.attachmentID = attachmentID
|
||||
@@ -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
|
||||
|
@@ -11,7 +11,7 @@ class Quote() {
|
||||
var timestamp: Long? = 0
|
||||
var publicKey: String? = null
|
||||
var text: String? = null
|
||||
var attachmentID: String? = null
|
||||
var attachmentID: Long? = null
|
||||
|
||||
companion object {
|
||||
const val TAG = "Quote"
|
||||
@@ -25,7 +25,7 @@ class Quote() {
|
||||
}
|
||||
|
||||
//constructor
|
||||
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: String?) : this() {
|
||||
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: Long?) : this() {
|
||||
this.timestamp = timestamp
|
||||
this.publicKey = publicKey
|
||||
this.text = text
|
||||
@@ -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 {
|
||||
|
@@ -11,7 +11,7 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||
class VisibleMessage : Message() {
|
||||
|
||||
var text: String? = null
|
||||
var attachmentIDs = ArrayList<String>()
|
||||
var attachmentIDs = ArrayList<Long>()
|
||||
var quote: Quote? = null
|
||||
var linkPreview: LinkPreview? = null
|
||||
var contact: Contact? = null
|
||||
@@ -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
|
||||
|
@@ -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 SessionServiceAttachment 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 = ""
|
||||
var key: ByteString? = null
|
||||
|
||||
abstract fun isStream(): Boolean
|
||||
abstract fun isPointer(): Boolean
|
||||
fun asStream(): SessionServiceAttachmentStream {
|
||||
return this as SessionServiceAttachmentStream
|
||||
}
|
||||
|
||||
fun asPointer(): SessionServiceAttachmentPointer {
|
||||
return this as SessionServiceAttachmentPointer
|
||||
}
|
||||
|
||||
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(): SessionServiceAttachmentStream {
|
||||
requireNotNull(inputStream) { "Must specify stream!" }
|
||||
requireNotNull(contentType) { "No content type specified!" }
|
||||
require(length != 0L) { "No length specified!" }
|
||||
return SessionServiceAttachmentStream(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)
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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 SessionServiceAttachmentPointer(val id: Long, contentType: String?, key: ByteArray?,
|
||||
val size: Optional<Int>, val preview: Optional<ByteArray>,
|
||||
val width: Int, val height: Int,
|
||||
val digest: Optional<ByteArray>, val fileName: Optional<String>,
|
||||
val voiceNote: Boolean, val caption: Optional<String>, url: String) : SessionServiceAttachment(contentType) {
|
||||
override fun isStream(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isPointer(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
@@ -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 SessionServiceAttachmentStream(val inputStream: InputStream?, contentType: String?, val length: Long, val fileName: Optional<String?>?, val voiceNote: Boolean, val preview: Optional<ByteArray?>, val width: Int, val height: Int, val caption: Optional<String?>, val listener: SAttachment.ProgressListener?) : SessionServiceAttachment(contentType) {
|
||||
|
||||
constructor(inputStream: InputStream?, contentType: String?, length: Long, fileName: Optional<String?>?, voiceNote: Boolean, listener: SAttachment.ProgressListener?) : this(inputStream, contentType, length, fileName, voiceNote, Optional.absent<ByteArray?>(), 0, 0, Optional.absent<String?>(), 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
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user