This commit is contained in:
Anton Chekulaev 2020-12-15 17:22:05 +11:00
commit d7b0c84f22
10 changed files with 181 additions and 63 deletions

View File

@ -1,52 +1,68 @@
//package org.thoughtcrime.securesms.attachments package org.thoughtcrime.securesms.attachments
//
//import android.content.Context import android.content.Context
//import com.google.protobuf.ByteString import com.google.protobuf.ByteString
//import org.session.libsession.database.dto.DatabaseAttachmentDTO import org.session.libsession.database.dto.DatabaseAttachmentDTO
//import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
//import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsession.database.dto.AttachmentState
//import org.thoughtcrime.securesms.database.Database import org.session.libsignal.service.internal.push.SignalServiceProtos
//import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.Database
//import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.DatabaseFactory
//import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.database.SmsDatabase
// import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
//class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider { import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
// override fun getAttachment(uniqueID: String): DatabaseAttachmentDTO? { import org.thoughtcrime.securesms.util.MediaUtil
//
// val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
// val uniqueID = uniqueID.toLongOrNull() ?: return null override fun getAttachment(attachmentId: Long): DatabaseAttachmentDTO? {
// val attachmentID = AttachmentId(0, uniqueID) val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
// val databaseAttachment = attachmentDatabase.getAttachment(attachmentID) ?: return null val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
// return databaseAttachment.toDTO()
// return databaseAttachment.toDTO() }
// }
// override fun setAttachmentState(attachmentState: AttachmentState, attachment: DatabaseAttachmentDTO, messageID: Long) {
//} val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
// attachmentDatabase.setTransferState(messageID, AttachmentId(attachment.attachmentId, 0), attachmentState.value)
//// Extension to DatabaseAttachment class }
//
//fun DatabaseAttachment.toDTO(): DatabaseAttachmentDTO { @Throws(Exception::class)
// var databaseAttachmentDTO = DatabaseAttachmentDTO() override fun uploadAttachment(attachmentId: Long) {
// databaseAttachmentDTO.contentType = this.contentType val attachmentUploadJob = AttachmentUploadJob(AttachmentId(attachmentId, 0), null)
// databaseAttachmentDTO.fileName = this.fileName attachmentUploadJob.onRun()
// databaseAttachmentDTO.caption = this.caption }
//
// databaseAttachmentDTO.size = this.size.toInt() override fun isOutgoingMessage(timestamp: Long): Boolean {
// databaseAttachmentDTO.key = ByteString.copyFrom(this.key?.toByteArray()) val smsDatabase = DatabaseFactory.getSmsDatabase(context)
// databaseAttachmentDTO.digest = ByteString.copyFrom(this.digest) return smsDatabase.isOutgoingMessage(timestamp)
// databaseAttachmentDTO.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0 }
//
// databaseAttachmentDTO.url = this.url }
//
// if (this.shouldHaveImageSize()) { // Extension to DatabaseAttachment class
// databaseAttachmentDTO.shouldHaveImageSize = true
// databaseAttachmentDTO.width = this.width fun DatabaseAttachment.toDTO(): DatabaseAttachmentDTO {
// databaseAttachmentDTO.height = this.height var databaseAttachmentDTO = DatabaseAttachmentDTO()
// } databaseAttachmentDTO.attachmentId = this.attachmentId.rowId
// databaseAttachmentDTO.contentType = this.contentType
// return databaseAttachmentDTO databaseAttachmentDTO.fileName = this.fileName
//} databaseAttachmentDTO.caption = this.caption
//
//fun DatabaseAttachment.shouldHaveImageSize(): Boolean { databaseAttachmentDTO.size = this.size.toInt()
// return (MediaUtil.isVideo(this) || MediaUtil.isImage(this) || MediaUtil.isGif(this)); 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
databaseAttachmentDTO.url = this.url
if (this.shouldHaveImageSize()) {
databaseAttachmentDTO.shouldHaveImageSize = true
databaseAttachmentDTO.width = this.width
databaseAttachmentDTO.height = this.height
}
return databaseAttachmentDTO
}
fun DatabaseAttachment.shouldHaveImageSize(): Boolean {
return (MediaUtil.isVideo(this) || MediaUtil.isImage(this) || MediaUtil.isGif(this));
}

View File

@ -1,9 +1,18 @@
package org.session.libsession.database package org.session.libsession.database
import org.session.libsession.database.dto.AttachmentState
import org.session.libsession.database.dto.DatabaseAttachmentDTO import org.session.libsession.database.dto.DatabaseAttachmentDTO
import org.session.libsession.messaging.messages.visible.Attachment
interface MessageDataProvider { interface MessageDataProvider {
fun getAttachment(uniqueID: String): DatabaseAttachmentDTO? fun getAttachment(attachmentId: Long): DatabaseAttachmentDTO?
fun setAttachmentState(attachmentState: AttachmentState, attachment: DatabaseAttachmentDTO, messageID: Long)
fun isOutgoingMessage(timestamp: Long): Boolean
@Throws(Exception::class)
fun uploadAttachment(attachmentId: Long)
} }

View File

@ -6,6 +6,8 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
import kotlin.math.round import kotlin.math.round
class DatabaseAttachmentDTO { class DatabaseAttachmentDTO {
var attachmentId: Long = 0
var contentType: String? = null var contentType: String? = null
var fileName: String? = null var fileName: String? = null

View File

@ -35,7 +35,7 @@ interface StorageProtocol {
fun markJobAsSucceeded(job: Job) fun markJobAsSucceeded(job: Job)
fun markJobAsFailed(job: Job) fun markJobAsFailed(job: Job)
fun getAllPendingJobs(type: String): List<Job> fun getAllPendingJobs(type: String): List<Job>
fun getAttachmentUploadJob(attachmentID: String): AttachmentUploadJob? fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob?
fun getMessageSendJob(messageSendJobID: String): MessageSendJob? fun getMessageSendJob(messageSendJobID: String): MessageSendJob?
fun resumeMessageSendJobIfNeeded(messageSendJobID: String) fun resumeMessageSendJobIfNeeded(messageSendJobID: String)
fun isJobCanceled(job: Job): Boolean fun isJobCanceled(job: Job): Boolean

View File

@ -4,12 +4,12 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import okhttp3.Request import okhttp3.Request
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.libsignal.logging.Log import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.libsignal.util.Hex import org.session.libsignal.libsignal.util.Hex
import org.session.libsignal.service.internal.util.Base64 import org.session.libsignal.service.internal.util.Base64
import org.session.libsignal.service.internal.util.JsonUtil import org.session.libsignal.service.internal.util.JsonUtil
import org.session.libsignal.service.loki.api.SnodeAPI 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.api.onionrequests.OnionRequestAPI
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
@ -18,7 +18,7 @@ import java.net.URL
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.set 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 { companion object {
// region Settings // 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. * 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? 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" public val fileStorageBucketURL = "https://file-static.lokinet.org"
// endregion // endregion

View File

@ -1,6 +1,13 @@
package org.session.libsession.messaging.jobs 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 { class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -8,10 +15,43 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
// Settings // Settings
override val maxFailureCount: Int = 10 override val maxFailureCount: Int = 10
companion object { companion object {
val TAG = MessageReceiveJob::class.qualifiedName
val collection: String = "MessageReceiveJobCollection" val collection: String = "MessageReceiveJobCollection"
} }
override fun execute() { override fun execute() {
TODO("Not yet implemented") exec()
}
fun exec(): Promise<Unit, Exception> {
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)
} }
} }

View File

@ -1,9 +1,15 @@
package org.session.libsession.messaging.jobs 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.Destination
import org.session.libsession.messaging.messages.Message 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 { class MessageSendJob(val message: Message, val destination: Destination) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -11,10 +17,54 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
// Settings // Settings
override val maxFailureCount: Int = 10 override val maxFailureCount: Int = 10
companion object { companion object {
val TAG = MessageSendJob::class.qualifiedName
val collection: String = "MessageSendJobCollection" val collection: String = "MessageSendJobCollection"
} }
override fun execute() { 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.getAttachment(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)
} }
} }

View File

@ -8,7 +8,7 @@ class LinkPreview() {
var title: String? = null var title: String? = null
var url: String? = null var url: String? = null
var attachmentID: String? = null var attachmentID: Long? = 0
companion object { companion object {
const val TAG = "LinkPreview" const val TAG = "LinkPreview"
@ -21,7 +21,7 @@ class LinkPreview() {
} }
//constructor //constructor
internal constructor(title: String?, url: String, attachmentID: String?) : this() { internal constructor(title: String?, url: String, attachmentID: Long?) : this() {
this.title = title this.title = title
this.url = url this.url = url
this.attachmentID = attachmentID this.attachmentID = attachmentID

View File

@ -11,7 +11,7 @@ class Quote() {
var timestamp: Long? = 0 var timestamp: Long? = 0
var publicKey: String? = null var publicKey: String? = null
var text: String? = null var text: String? = null
var attachmentID: String? = null var attachmentID: Long? = null
companion object { companion object {
const val TAG = "Quote" const val TAG = "Quote"
@ -25,7 +25,7 @@ class Quote() {
} }
//constructor //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.timestamp = timestamp
this.publicKey = publicKey this.publicKey = publicKey
this.text = text this.text = text

View File

@ -11,7 +11,7 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
class VisibleMessage : Message() { class VisibleMessage : Message() {
var text: String? = null var text: String? = null
var attachmentIDs = ArrayList<String>() var attachmentIDs = ArrayList<Long>()
var quote: Quote? = null var quote: Quote? = null
var linkPreview: LinkPreview? = null var linkPreview: LinkPreview? = null
var contact: Contact? = null var contact: Contact? = null