This commit is contained in:
Ryan ZHAO
2021-01-22 16:15:21 +11:00
48 changed files with 639 additions and 412 deletions

View File

@@ -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"

View File

@@ -25,6 +25,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 +85,21 @@ class AttachmentDownloadJob(val attachmentID: Long, val tsIncomingMessageID: Lon
file.deleteOnExit()
return file
}
//database functions
override fun serialize(): Data {
val builder = this.createJobDataBuilder()
return builder.putLong(KEY_ATTACHMENT_ID, attachmentID)
.putLong(KEY_TS_INCOMING_MESSAGE_ID, tsIncomingMessageID)
.build();
}
class Factory: Job.Factory<AttachmentDownloadJob> {
override fun create(data: Data): AttachmentDownloadJob {
val job = AttachmentDownloadJob(data.getLong(KEY_ATTACHMENT_ID), data.getLong(KEY_TS_INCOMING_MESSAGE_ID))
job.initJob(data)
return job
}
}
}

View File

@@ -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(): Data {
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<AttachmentUploadJob> {
override fun create(data: Data): 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
}
}
}

View File

@@ -0,0 +1,348 @@
package org.session.libsession.messaging.jobs;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.session.libsession.utilities.ParcelableUtil;
import java.util.HashMap;
import java.util.Map;
// TODO AC: For now parcelable objects utilize byteArrays field to store their data into.
// Introduce a dedicated Map<String, byte[]> field specifically for parcelable needs.
public class Data {
public static final Data EMPTY = new Data.Builder().build();
@JsonProperty private final Map<String, String> strings;
@JsonProperty private final Map<String, String[]> stringArrays;
@JsonProperty private final Map<String, Integer> integers;
@JsonProperty private final Map<String, int[]> integerArrays;
@JsonProperty private final Map<String, Long> longs;
@JsonProperty private final Map<String, long[]> longArrays;
@JsonProperty private final Map<String, Float> floats;
@JsonProperty private final Map<String, float[]> floatArrays;
@JsonProperty private final Map<String, Double> doubles;
@JsonProperty private final Map<String, double[]> doubleArrays;
@JsonProperty private final Map<String, Boolean> booleans;
@JsonProperty private final Map<String, boolean[]> booleanArrays;
@JsonProperty private final Map<String, byte[]> byteArrays;
public Data(@JsonProperty("strings") @NonNull Map<String, String> strings,
@JsonProperty("stringArrays") @NonNull Map<String, String[]> stringArrays,
@JsonProperty("integers") @NonNull Map<String, Integer> integers,
@JsonProperty("integerArrays") @NonNull Map<String, int[]> integerArrays,
@JsonProperty("longs") @NonNull Map<String, Long> longs,
@JsonProperty("longArrays") @NonNull Map<String, long[]> longArrays,
@JsonProperty("floats") @NonNull Map<String, Float> floats,
@JsonProperty("floatArrays") @NonNull Map<String, float[]> floatArrays,
@JsonProperty("doubles") @NonNull Map<String, Double> doubles,
@JsonProperty("doubleArrays") @NonNull Map<String, double[]> doubleArrays,
@JsonProperty("booleans") @NonNull Map<String, Boolean> booleans,
@JsonProperty("booleanArrays") @NonNull Map<String, boolean[]> booleanArrays,
@JsonProperty("byteArrays") @NonNull Map<String, byte[]> byteArrays)
{
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;
}
public boolean hasString(@NonNull String key) {
return strings.containsKey(key);
}
public String getString(@NonNull String key) {
throwIfAbsent(strings, key);
return strings.get(key);
}
public String getStringOrDefault(@NonNull String key, String defaultValue) {
if (hasString(key)) return getString(key);
else return defaultValue;
}
public boolean hasStringArray(@NonNull String key) {
return stringArrays.containsKey(key);
}
public String[] getStringArray(@NonNull String key) {
throwIfAbsent(stringArrays, key);
return stringArrays.get(key);
}
public boolean hasInt(@NonNull String key) {
return integers.containsKey(key);
}
public int getInt(@NonNull String key) {
throwIfAbsent(integers, key);
return integers.get(key);
}
public int getIntOrDefault(@NonNull String key, int defaultValue) {
if (hasInt(key)) return getInt(key);
else return defaultValue;
}
public boolean hasIntegerArray(@NonNull String key) {
return integerArrays.containsKey(key);
}
public int[] getIntegerArray(@NonNull String key) {
throwIfAbsent(integerArrays, key);
return integerArrays.get(key);
}
public boolean hasLong(@NonNull String key) {
return longs.containsKey(key);
}
public long getLong(@NonNull String key) {
throwIfAbsent(longs, key);
return longs.get(key);
}
public long getLongOrDefault(@NonNull String key, long defaultValue) {
if (hasLong(key)) return getLong(key);
else return defaultValue;
}
public boolean hasLongArray(@NonNull String key) {
return longArrays.containsKey(key);
}
public long[] getLongArray(@NonNull String key) {
throwIfAbsent(longArrays, key);
return longArrays.get(key);
}
public boolean hasFloat(@NonNull String key) {
return floats.containsKey(key);
}
public float getFloat(@NonNull String key) {
throwIfAbsent(floats, key);
return floats.get(key);
}
public float getFloatOrDefault(@NonNull String key, float defaultValue) {
if (hasFloat(key)) return getFloat(key);
else return defaultValue;
}
public boolean hasFloatArray(@NonNull String key) {
return floatArrays.containsKey(key);
}
public float[] getFloatArray(@NonNull String key) {
throwIfAbsent(floatArrays, key);
return floatArrays.get(key);
}
public boolean hasDouble(@NonNull String key) {
return doubles.containsKey(key);
}
public double getDouble(@NonNull String key) {
throwIfAbsent(doubles, key);
return doubles.get(key);
}
public double getDoubleOrDefault(@NonNull String key, double defaultValue) {
if (hasDouble(key)) return getDouble(key);
else return defaultValue;
}
public boolean hasDoubleArray(@NonNull String key) {
return floatArrays.containsKey(key);
}
public double[] getDoubleArray(@NonNull String key) {
throwIfAbsent(doubleArrays, key);
return doubleArrays.get(key);
}
public boolean hasBoolean(@NonNull String key) {
return booleans.containsKey(key);
}
public boolean getBoolean(@NonNull String key) {
throwIfAbsent(booleans, key);
return booleans.get(key);
}
public boolean getBooleanOrDefault(@NonNull String key, boolean defaultValue) {
if (hasBoolean(key)) return getBoolean(key);
else return defaultValue;
}
public boolean hasBooleanArray(@NonNull String key) {
return booleanArrays.containsKey(key);
}
public boolean[] getBooleanArray(@NonNull String key) {
throwIfAbsent(booleanArrays, key);
return booleanArrays.get(key);
}
public boolean hasByteArray(@NonNull String key) {
return byteArrays.containsKey(key);
}
public byte[] getByteArray(@NonNull String key) {
throwIfAbsent(byteArrays, key);
return byteArrays.get(key);
}
public boolean hasParcelable(@NonNull String key) {
return byteArrays.containsKey(key);
}
public <T extends Parcelable> T getParcelable(@NonNull String key, @NonNull Parcelable.Creator<T> creator) {
throwIfAbsent(byteArrays, key);
byte[] bytes = byteArrays.get(key);
return ParcelableUtil.unmarshall(bytes, creator);
}
private void throwIfAbsent(@NonNull Map map, @NonNull String key) {
if (!map.containsKey(key)) {
throw new IllegalStateException("Tried to retrieve a value with key '" + key + "', but it wasn't present.");
}
}
public static class Builder {
private final Map<String, String> strings = new HashMap<>();
private final Map<String, String[]> stringArrays = new HashMap<>();
private final Map<String, Integer> integers = new HashMap<>();
private final Map<String, int[]> integerArrays = new HashMap<>();
private final Map<String, Long> longs = new HashMap<>();
private final Map<String, long[]> longArrays = new HashMap<>();
private final Map<String, Float> floats = new HashMap<>();
private final Map<String, float[]> floatArrays = new HashMap<>();
private final Map<String, Double> doubles = new HashMap<>();
private final Map<String, double[]> doubleArrays = new HashMap<>();
private final Map<String, Boolean> booleans = new HashMap<>();
private final Map<String, boolean[]> booleanArrays = new HashMap<>();
private final Map<String, byte[]> byteArrays = new HashMap<>();
public Builder putString(@NonNull String key, @Nullable String value) {
strings.put(key, value);
return this;
}
public Builder putStringArray(@NonNull String key, @NonNull String[] value) {
stringArrays.put(key, value);
return this;
}
public Builder putInt(@NonNull String key, int value) {
integers.put(key, value);
return this;
}
public Builder putIntArray(@NonNull String key, @NonNull int[] value) {
integerArrays.put(key, value);
return this;
}
public Builder putLong(@NonNull String key, long value) {
longs.put(key, value);
return this;
}
public Builder putLongArray(@NonNull String key, @NonNull long[] value) {
longArrays.put(key, value);
return this;
}
public Builder putFloat(@NonNull String key, float value) {
floats.put(key, value);
return this;
}
public Builder putFloatArray(@NonNull String key, @NonNull float[] value) {
floatArrays.put(key, value);
return this;
}
public Builder putDouble(@NonNull String key, double value) {
doubles.put(key, value);
return this;
}
public Builder putDoubleArray(@NonNull String key, @NonNull double[] value) {
doubleArrays.put(key, value);
return this;
}
public Builder putBoolean(@NonNull String key, boolean value) {
booleans.put(key, value);
return this;
}
public Builder putBooleanArray(@NonNull String key, @NonNull boolean[] value) {
booleanArrays.put(key, value);
return this;
}
public Builder putByteArray(@NonNull String key, @NonNull byte[] value) {
byteArrays.put(key, value);
return this;
}
public Builder putParcelable(@NonNull String key, @NonNull Parcelable value) {
byte[] bytes = ParcelableUtil.marshall(value);
byteArrays.put(key, bytes);
return this;
}
public Data build() {
return new Data(strings,
stringArrays,
integers,
integerArrays,
longs,
longArrays,
floats,
floatArrays,
doubles,
doubleArrays,
booleans,
booleanArrays,
byteArrays);
}
}
public interface Serializer {
@NonNull String serialize(@NonNull Data data);
@NonNull Data deserialize(@NonNull String serialized);
}
}

View File

@@ -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(): Data
fun initJob(data: Data) {
id = data.getString(KEY_ID)
failureCount = data.getInt(KEY_FAILURE_COUNT)
}
fun createJobDataBuilder(): Data.Builder {
return Data.Builder().putString(KEY_ID, id)
.putInt(KEY_FAILURE_COUNT, failureCount)
}
interface Factory<T : Job> {
fun create(data: Data): T
}
}

View File

@@ -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(): Data {
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<MessageReceiveJob> {
override fun create(data: Data): 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
}
}
}

View File

@@ -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(): Data {
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<MessageSendJob> {
override fun create(data: Data): 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
}
}
}

View File

@@ -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(): Data {
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<NotifyPNServerJob> {
override fun create(data: Data): 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
}
}
}

View File

@@ -0,0 +1,31 @@
package org.session.libsession.utilities
import android.os.Parcel
import android.os.Parcelable
object ParcelableUtil {
@JvmStatic
fun marshall(parcelable: Parcelable): ByteArray {
val parcel = Parcel.obtain()
parcelable.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.recycle()
return bytes
}
@JvmStatic
fun unmarshall(bytes: ByteArray): Parcel {
val parcel = Parcel.obtain()
parcel.unmarshall(bytes, 0, bytes.size)
parcel.setDataPosition(0) // This is extremely important!
return parcel
}
@JvmStatic
fun <T> unmarshall(bytes: ByteArray, creator: Parcelable.Creator<T>): T {
val parcel: Parcel = ParcelableUtil.unmarshall(bytes)
val result = creator.createFromParcel(parcel)
parcel.recycle()
return result
}
}