diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt new file mode 100644 index 0000000000..072e694fac --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt @@ -0,0 +1,4 @@ +package org.thoughtcrime.securesms.loki.database + +class SessionJobDatabase { +} \ No newline at end of file diff --git a/libsession/build.gradle b/libsession/build.gradle index 67160f80ab..cb7d54107c 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.annimon:stream:1.1.8' implementation 'com.makeramen:roundedimageview:2.1.0' + implementation 'com.esotericsoftware:kryo:4.0.1' // from libsignal: implementation "com.google.protobuf:protobuf-java:$protobufVersion" diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 6289fcbf9c..cdce348b3d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -16,6 +16,8 @@ class AttachmentDownloadJob(val attachmentID: Long, val tsIncomingMessageID: Lon private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024 + + // Error internal sealed class Error(val description: String) : Exception() { object NoAttachment : Error("No such attachment.") @@ -25,6 +27,10 @@ class AttachmentDownloadJob(val attachmentID: Long, val tsIncomingMessageID: Lon override val maxFailureCount: Int = 20 companion object { val collection: String = "AttachmentDownloadJobCollection" + + //keys used for database storage purpose + private val KEY_ATTACHMENT_ID = "attachment_id" + private val KEY_TS_INCOMING_MESSAGE_ID = "tsIncoming_message_id" } override fun execute() { @@ -81,4 +87,21 @@ class AttachmentDownloadJob(val attachmentID: Long, val tsIncomingMessageID: Lon file.deleteOnExit() return file } + + //database functions + + override fun serialize(): JobData { + val builder = this.createJobDataBuilder() + return builder.putLong(KEY_ATTACHMENT_ID, attachmentID) + .putLong(KEY_TS_INCOMING_MESSAGE_ID, tsIncomingMessageID) + .build(); + } + + class Factory: Job.Factory { + override fun create(data: JobData): AttachmentDownloadJob { + val job = AttachmentDownloadJob(data.getLong(KEY_ATTACHMENT_ID), data.getLong(KEY_TS_INCOMING_MESSAGE_ID)) + job.initJob(data) + return job + } + } } \ No newline at end of file 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 1088c91461..1545736867 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,5 +1,8 @@ package org.session.libsession.messaging.jobs +import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.fileserver.FileServerAPI import org.session.libsession.messaging.messages.Message @@ -29,6 +32,12 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess val collection: String = "AttachmentUploadJobCollection" val maxFailureCount: Int = 20 + + //keys used for database storage purpose + private val KEY_ATTACHMENT_ID = "attachment_id" + private val KEY_THREAD_ID = "thread_id" + private val KEY_MESSAGE = "message" + private val KEY_MESSAGE_SEND_JOB_ID = "message_send_job_id" } override fun execute() { @@ -91,4 +100,36 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess storage.markJobAsFailed(messageSendJob) } } + + //database functions + + override fun serialize(): JobData { + val builder = this.createJobDataBuilder() + //serialize Message property + val kryo = Kryo() + kryo.isRegistrationRequired = false + val serializedMessage = ByteArray(4096) + val output = Output(serializedMessage) + kryo.writeObject(output, message) + output.close() + return builder.putLong(KEY_ATTACHMENT_ID, attachmentID) + .putString(KEY_THREAD_ID, threadID) + .putByteArray(KEY_MESSAGE, serializedMessage) + .putString(KEY_MESSAGE_SEND_JOB_ID, messageSendJobID) + .build(); + } + + class Factory: Job.Factory { + override fun create(data: JobData): AttachmentUploadJob { + val serializedMessage = data.getByteArray(KEY_MESSAGE) + //deserialize Message property + val kryo = Kryo() + val input = Input(serializedMessage) + val message: Message = kryo.readObject(input, Message::class.java) + input.close() + val job = AttachmentUploadJob(data.getLong(KEY_ATTACHMENT_ID), data.getString(KEY_THREAD_ID)!!, message, data.getString(KEY_MESSAGE_SEND_JOB_ID)!!) + job.initJob(data) + return job + } + } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt index c3108835cf..7e2ae3985c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt @@ -7,5 +7,29 @@ interface Job { val maxFailureCount: Int + companion object { + //keys used for database storage purpose + private val KEY_ID = "id" + private val KEY_FAILURE_COUNT = "failure_count" + } + fun execute() + + //database functions + + fun serialize(): JobData + + fun initJob(data: JobData) { + id = data.getString(KEY_ID) + failureCount = data.getInt(KEY_FAILURE_COUNT) + } + + fun createJobDataBuilder(): JobData.Builder { + return JobData.Builder().putString(KEY_ID, id) + .putInt(KEY_FAILURE_COUNT, failureCount) + } + + interface Factory { + fun create(data: JobData): T + } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobData.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobData.kt new file mode 100644 index 0000000000..a6f10372a5 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobData.kt @@ -0,0 +1,351 @@ +package org.session.libsession.messaging.jobs + +import com.fasterxml.jackson.annotation.JsonProperty +import java.util.* + +class JobData { + + val EMPTY: JobData = Builder().build() + + @JsonProperty + private var strings: Map + + @JsonProperty + private var stringArrays: Map> + + @JsonProperty + private var integers: Map + + @JsonProperty + private var integerArrays: Map + + @JsonProperty + private var longs: Map + + @JsonProperty + private var longArrays: Map + + @JsonProperty + private var floats: Map + + @JsonProperty + private var floatArrays: Map + + @JsonProperty + private var doubles: Map + + @JsonProperty + private var doubleArrays: Map + + @JsonProperty + private var booleans: Map + + @JsonProperty + private var booleanArrays: Map + + @JsonProperty + private var byteArrays: Map + + constructor(@JsonProperty("strings") strings: MutableMap, + @JsonProperty("stringArrays") stringArrays: Map>, + @JsonProperty("integers") integers: Map, + @JsonProperty("integerArrays") integerArrays: Map, + @JsonProperty("longs") longs: Map, + @JsonProperty("longArrays") longArrays: Map, + @JsonProperty("floats") floats: Map, + @JsonProperty("floatArrays") floatArrays: Map, + @JsonProperty("doubles") doubles: Map, + @JsonProperty("doubleArrays") doubleArrays: Map, + @JsonProperty("booleans") booleans: Map, + @JsonProperty("booleanArrays") booleanArrays: Map, + @JsonProperty("byteArrays") byteArrays: Map) { + this.strings = strings + this.stringArrays = stringArrays + this.integers = integers + this.integerArrays = integerArrays + this.longs = longs + this.longArrays = longArrays + this.floats = floats + this.floatArrays = floatArrays + this.doubles = doubles + this.doubleArrays = doubleArrays + this.booleans = booleans + this.booleanArrays = booleanArrays + this.byteArrays = byteArrays + } + + fun hasString(key: String): Boolean { + return strings.containsKey(key) + } + + fun getString(key: String): String { + throwIfAbsent(strings, key) + return strings[key]!! + } + + fun getStringOrDefault(key: String, defaultValue: String?): String? { + return if (hasString(key)) getString(key) else defaultValue + } + + + fun hasStringArray(key: String): Boolean { + return stringArrays.containsKey(key) + } + + fun getStringArray(key: String): Array? { + throwIfAbsent(stringArrays, key) + return stringArrays[key] + } + + + fun hasInt(key: String): Boolean { + return integers.containsKey(key) + } + + fun getInt(key: String): Int { + throwIfAbsent(integers, key) + return integers[key]!! + } + + fun getIntOrDefault(key: String, defaultValue: Int): Int { + return if (hasInt(key)) getInt(key) else defaultValue + } + + + fun hasIntegerArray(key: String): Boolean { + return integerArrays.containsKey(key) + } + + fun getIntegerArray(key: String): IntArray? { + throwIfAbsent(integerArrays, key) + return integerArrays[key] + } + + + fun hasLong(key: String): Boolean { + return longs.containsKey(key) + } + + fun getLong(key: String): Long { + throwIfAbsent(longs, key) + return longs[key]!! + } + + fun getLongOrDefault(key: String, defaultValue: Long): Long { + return if (hasLong(key)) getLong(key) else defaultValue + } + + + fun hasLongArray(key: String): Boolean { + return longArrays.containsKey(key) + } + + fun getLongArray(key: String): LongArray? { + throwIfAbsent(longArrays, key) + return longArrays[key] + } + + + fun hasFloat(key: String): Boolean { + return floats.containsKey(key) + } + + fun getFloat(key: String): Float { + throwIfAbsent(floats, key) + return floats[key]!! + } + + fun getFloatOrDefault(key: String, defaultValue: Float): Float { + return if (hasFloat(key)) getFloat(key) else defaultValue + } + + + fun hasFloatArray(key: String): Boolean { + return floatArrays.containsKey(key) + } + + fun getFloatArray(key: String): FloatArray? { + throwIfAbsent(floatArrays, key) + return floatArrays[key] + } + + + fun hasDouble(key: String): Boolean { + return doubles.containsKey(key) + } + + fun getDouble(key: String): Double { + throwIfAbsent(doubles, key) + return doubles[key]!! + } + + fun getDoubleOrDefault(key: String, defaultValue: Double): Double { + return if (hasDouble(key)) getDouble(key) else defaultValue + } + + + fun hasDoubleArray(key: String): Boolean { + return floatArrays.containsKey(key) + } + + fun getDoubleArray(key: String): DoubleArray? { + throwIfAbsent(doubleArrays, key) + return doubleArrays[key] + } + + + fun hasBoolean(key: String): Boolean { + return booleans.containsKey(key) + } + + fun getBoolean(key: String): Boolean { + throwIfAbsent(booleans, key) + return booleans[key]!! + } + + fun getBooleanOrDefault(key: String, defaultValue: Boolean): Boolean { + return if (hasBoolean(key)) getBoolean(key) else defaultValue + } + + + fun hasBooleanArray(key: String): Boolean { + return booleanArrays.containsKey(key) + } + + fun getBooleanArray(key: String): BooleanArray? { + throwIfAbsent(booleanArrays, key) + return booleanArrays[key] + } + + fun hasByteArray(key: String): Boolean { + return byteArrays.containsKey(key) + } + + fun getByteArray(key: String): ByteArray { + throwIfAbsent(byteArrays, key) + return byteArrays[key]!! + } + + fun hasParcelable(key: String): Boolean { + return byteArrays!!.containsKey(key) + } + + /*fun getParcelable(key: String, creator: Parcelable.Creator): T { + throwIfAbsent(byteArrays!!, key) + val bytes = byteArrays[key] + return ParcelableUtil.unmarshall(bytes, creator) + }*/ + + private fun throwIfAbsent(map: Map<*, *>, key: String) { + check(map.containsKey(key)) { "Tried to retrieve a value with key '$key', but it wasn't present." } + } + + + class Builder { + private val strings: MutableMap = HashMap() + private val stringArrays: MutableMap> = HashMap() + private val integers: MutableMap = HashMap() + private val integerArrays: MutableMap = HashMap() + private val longs: MutableMap = HashMap() + private val longArrays: MutableMap = HashMap() + private val floats: MutableMap = HashMap() + private val floatArrays: MutableMap = HashMap() + private val doubles: MutableMap = HashMap() + private val doubleArrays: MutableMap = HashMap() + private val booleans: MutableMap = HashMap() + private val booleanArrays: MutableMap = HashMap() + private val byteArrays: MutableMap = HashMap() + fun putString(key: String, value: String?): Builder { + value?.let { strings[key] = value } + return this + } + + fun putStringArray(key: String, value: Array): Builder { + stringArrays[key] = value + return this + } + + fun putInt(key: String, value: Int): Builder { + integers[key] = value + return this + } + + fun putIntArray(key: String, value: IntArray): Builder { + integerArrays[key] = value + return this + } + + fun putLong(key: String, value: Long): Builder { + longs[key] = value + return this + } + + fun putLongArray(key: String, value: LongArray): Builder { + longArrays[key] = value + return this + } + + fun putFloat(key: String, value: Float): Builder { + floats[key] = value + return this + } + + fun putFloatArray(key: String, value: FloatArray): Builder { + floatArrays[key] = value + return this + } + + fun putDouble(key: String, value: Double): Builder { + doubles[key] = value + return this + } + + fun putDoubleArray(key: String, value: DoubleArray): Builder { + doubleArrays[key] = value + return this + } + + fun putBoolean(key: String, value: Boolean): Builder { + booleans[key] = value + return this + } + + fun putBooleanArray(key: String, value: BooleanArray): Builder { + booleanArrays[key] = value + return this + } + + fun putByteArray(key: String, value: ByteArray): Builder { + byteArrays[key] = value + return this + } + + /*fun putParcelable(key: String, value: Parcelable): Builder { + val bytes: ByteArray = ParcelableUtil.marshall(value) + byteArrays[key] = bytes + return this + }*/ + + fun build(): JobData { + return JobData(strings, + stringArrays, + integers, + integerArrays, + longs, + longArrays, + floats, + floatArrays, + doubles, + doubleArrays, + booleans, + booleanArrays, + byteArrays) + } + } + + interface Serializer { + fun serialize(data: JobData): String + fun deserialize(serialized: String): JobData + } + +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index e5c3a4d23a..6a693945eb 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -18,6 +18,12 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val val TAG = MessageReceiveJob::class.qualifiedName val collection: String = "MessageReceiveJobCollection" + + //keys used for database storage purpose + private val KEY_DATA = "data" + private val KEY_IS_BACKGROUND_POLL = "is_background_poll" + private val KEY_OPEN_GROUP_MESSAGE_SERVER_ID = "openGroupMessageServerID" + private val KEY_OPEN_GROUP_ID = "open_group_id" } override fun execute() { @@ -54,4 +60,24 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val private fun handleFailure(e: Exception) { delegate?.handleJobFailed(this, e) } + + //database functions + + override fun serialize(): JobData { + val builder = this.createJobDataBuilder() + builder.putByteArray(KEY_DATA, data) + .putBoolean(KEY_IS_BACKGROUND_POLL, isBackgroundPoll) + openGroupMessageServerID?.let { builder.putLong(KEY_OPEN_GROUP_MESSAGE_SERVER_ID, openGroupMessageServerID) } + openGroupID?.let { builder.putString(KEY_OPEN_GROUP_ID, openGroupID) } + + return builder.build(); + } + + class Factory: Job.Factory { + override fun create(data: JobData): MessageReceiveJob { + val job = MessageReceiveJob(data.getByteArray(KEY_DATA), data.getBoolean(KEY_IS_BACKGROUND_POLL), data.getLong(KEY_OPEN_GROUP_MESSAGE_SERVER_ID), data.getString(KEY_OPEN_GROUP_ID)) + job.initJob(data) + return job + } + } } \ No newline at end of file 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 a8e88c8702..219b882c8b 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 @@ -1,12 +1,14 @@ package org.session.libsession.messaging.jobs +import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output 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 { @@ -20,6 +22,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { val TAG = MessageSendJob::class.qualifiedName val collection: String = "MessageSendJobCollection" + + //keys used for database storage purpose + private val KEY_MESSAGE = "message" + private val KEY_DESTINATION = "destination" } override fun execute() { @@ -67,4 +73,42 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { } delegate?.handleJobFailed(this, error) } + + //database functions + + override fun serialize(): JobData { + val builder = this.createJobDataBuilder() + //serialize Message and Destination properties + val kryo = Kryo() + kryo.isRegistrationRequired = false + val serializedMessage = ByteArray(4096) + val serializedDestination = ByteArray(4096) + var output = Output(serializedMessage) + kryo.writeObject(output, message) + output.close() + output = Output(serializedDestination) + kryo.writeObject(output, destination) + output.close() + return builder.putByteArray(KEY_MESSAGE, serializedMessage) + .putByteArray(KEY_DESTINATION, serializedDestination) + .build(); + } + + class Factory: Job.Factory { + override fun create(data: JobData): MessageSendJob { + val serializedMessage = data.getByteArray(KEY_MESSAGE) + val serializedDestination = data.getByteArray(KEY_DESTINATION) + //deserialize Message and Destination properties + val kryo = Kryo() + var input = Input(serializedMessage) + val message: Message = kryo.readObject(input, Message::class.java) + input.close() + input = Input(serializedDestination) + val destination: Destination = kryo.readObject(input, Destination::class.java) + input.close() + val job = MessageSendJob(message, destination) + job.initJob(data) + return job + } + } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index b9221efcf5..df5c409b1e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -1,5 +1,8 @@ package org.session.libsession.messaging.jobs +import com.esotericsoftware.kryo.Kryo +import com.esotericsoftware.kryo.io.Input +import com.esotericsoftware.kryo.io.Output import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request @@ -22,6 +25,9 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { override val maxFailureCount: Int = 20 companion object { val collection: String = "NotifyPNServerJobCollection" + + //keys used for database storage purpose + private val KEY_MESSAGE = "message" } // Running @@ -54,4 +60,33 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { private fun handleFailure(error: Exception) { delegate?.handleJobFailed(this, error) } + + //database functions + + override fun serialize(): JobData { + val builder = this.createJobDataBuilder() + //serialize SnodeMessage property + val kryo = Kryo() + kryo.isRegistrationRequired = false + val serializedMessage = ByteArray(4096) + val output = Output(serializedMessage) + kryo.writeObject(output, message) + output.close() + return builder.putByteArray(KEY_MESSAGE, serializedMessage) + .build(); + } + + class Factory: Job.Factory { + override fun create(data: JobData): NotifyPNServerJob { + val serializedMessage = data.getByteArray(KEY_MESSAGE) + //deserialize SnodeMessage property + val kryo = Kryo() + val input = Input(serializedMessage) + val message: SnodeMessage = kryo.readObject(input, SnodeMessage::class.java) + input.close() + val job = NotifyPNServerJob(message) + job.initJob(data) + return job + } + } } \ No newline at end of file