diff --git a/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt b/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt index 94b5d10fe0..0917a346f5 100644 --- a/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt +++ b/libsession-util/src/androidTest/java/network/loki/messenger/libsession_util/InstrumentedTests.kt @@ -3,6 +3,7 @@ package network.loki.messenger.libsession_util import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import network.loki.messenger.libsession_util.util.Contact +import network.loki.messenger.libsession_util.util.Conversation import network.loki.messenger.libsession_util.util.KeyPair import network.loki.messenger.libsession_util.util.Sodium import network.loki.messenger.libsession_util.util.UserPic @@ -329,4 +330,26 @@ class InstrumentedTests { userProfile.free() } + @Test + fun jni_remove_all_test() { + val convos = ConversationVolatileConfig.newInstance(keyPair.secretKey) + assertEquals(0 /* number removed */, convos.eraseAll { true /* 'erase' every item */ }) + + val definitelyRealId = "050000000000000000000000000000000000000000000000000000000000000000" + val definitelyRealConvo = Conversation.OneToOne(definitelyRealId, System.currentTimeMillis(), false) + convos.set(definitelyRealConvo) + + val anotherDefinitelyReadId = "051111111111111111111111111111111111111111111111111111111111111111" + val anotherDefinitelyRealConvo = Conversation.OneToOne(anotherDefinitelyReadId, System.currentTimeMillis(), false) + convos.set(anotherDefinitelyRealConvo) + + assertEquals(2, convos.sizeOneToOnes()) + + val numErased = convos.eraseAll { convo -> + convo is Conversation.OneToOne && convo.sessionId == definitelyRealId + } + assertEquals(1, numErased) + assertEquals(1, convos.sizeOneToOnes()) + } + } \ No newline at end of file diff --git a/libsession-util/src/main/cpp/conversation.cpp b/libsession-util/src/main/cpp/conversation.cpp index 3bee143b92..3ffd240c80 100644 --- a/libsession-util/src/main/cpp/conversation.cpp +++ b/libsession-util/src/main/cpp/conversation.cpp @@ -7,13 +7,75 @@ extern "C" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_00024Companion_newInstance___3B( JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key) { + auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key); + auto* convo_info_volatile = new session::config::ConvoInfoVolatile(secret_key, std::nullopt); + jclass convoClass = env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig"); + jmethodID constructor = env->GetMethodID(convoClass, "", "(J)V"); + jobject newConfig = env->NewObject(convoClass, constructor, reinterpret_cast(convo_info_volatile)); + + return newConfig; } extern "C" #pragma ide diagnostic ignored "bugprone-reserved-identifier" JNIEXPORT jobject JNICALL Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_00024Companion_newInstance___3B_3B( JNIEnv *env, jobject thiz, jbyteArray ed25519_secret_key, jbyteArray initial_dump) { + auto secret_key = util::ustring_from_bytes(env, ed25519_secret_key); + auto initial = util::ustring_from_bytes(env, initial_dump); + auto* convo_info_volatile = new session::config::ConvoInfoVolatile(secret_key, initial); + jclass convoClass = env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig"); + jmethodID constructor = env->GetMethodID(convoClass, "", "(J)V"); + jobject newConfig = env->NewObject(convoClass, constructor, reinterpret_cast(convo_info_volatile)); + + return newConfig; } -#pragma clang diagnostic pop \ No newline at end of file + +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_set(JNIEnv *env, + jobject thiz, + jobject to_store) { + auto conversations = ptrToConvoInfo(env, thiz); + auto one_to_one = deserialize_one_to_one(env, to_store); + conversations->set(*one_to_one); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_sizeOneToOnes(JNIEnv *env, + jobject thiz) { + auto conversations = ptrToConvoInfo(env, thiz); + return conversations->size_1to1(); +} + +#pragma clang diagnostic pop +extern "C" +JNIEXPORT jint JNICALL +Java_network_loki_messenger_libsession_1util_ConversationVolatileConfig_eraseAll(JNIEnv *env, + jobject thiz, + jobject predicate) { + auto conversations = ptrToConvoInfo(env, thiz); + + jclass predicate_class = env->FindClass("kotlin/jvm/functions/Function1"); + jmethodID predicate_call = env->GetMethodID(predicate_class, "invoke", "(Ljava/lang/Object;)Ljava/lang/Object;"); + + jclass bool_class = env->FindClass("java/lang/Boolean"); + jfieldID bool_value = env->GetFieldID(bool_class, "value", "Z"); + + int removed = 0; + + for (auto it = conversations->begin(); it != conversations->end(); ) { + auto result = env->CallObjectMethod(predicate, predicate_call, serialize_any(env, *it)); + bool bool_result = env->GetBooleanField(result, bool_value); + if (bool_result) { + it = conversations->erase(it); + removed++; + } else { + ++it; + } + } + + return removed; +} \ No newline at end of file diff --git a/libsession-util/src/main/cpp/conversation.h b/libsession-util/src/main/cpp/conversation.h index a202094d2f..842ebbd3a3 100644 --- a/libsession-util/src/main/cpp/conversation.h +++ b/libsession-util/src/main/cpp/conversation.h @@ -5,28 +5,108 @@ #include "util.h" #include "session/config/convo_info_volatile.hpp" -inline session::config::ConvoInfoVolatile *ptrToContacts(JNIEnv *env, jobject obj) { +inline session::config::ConvoInfoVolatile *ptrToConvoInfo(JNIEnv *env, jobject obj) { jclass contactsClass = env->FindClass("network/loki/messenger/libsession_util/ConversationVolatileConfig"); jfieldID pointerField = env->GetFieldID(contactsClass, "pointer", "J"); return (session::config::ConvoInfoVolatile *) env->GetLongField(obj, pointerField); } -inline jobject serialize_one_on_one(JNIEnv *env, session::config::convo::one_to_one one_to_one) { - // TODO serialize function +inline jobject serialize_one_to_one(JNIEnv *env, session::config::convo::one_to_one *one_to_one) { + jclass clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OneToOne"); + jmethodID constructor = env->GetMethodID(clazz, "", "(Ljava/lang/String;JZ)V"); + auto session_id = env->NewStringUTF(one_to_one->session_id.data()); + auto last_read = one_to_one->last_read; + auto unread = one_to_one->unread; + jobject serialized = env->NewObject(clazz, constructor, session_id, last_read, unread); + return serialized; } -inline jobject serialize_open_group(JNIEnv *env, session::config::convo::open_group open_group) { - // TODO +inline jobject serialize_open_group(JNIEnv *env, session::config::convo::open_group *open_group) { + jclass clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OpenGroup"); + jmethodID constructor = env->GetMethodID(clazz, "", "(Ljava/lang/String;Ljava/lang/String;[BJZ)V"); + auto base_url = env->NewStringUTF(open_group->base_url().data()); + auto room = env->NewStringUTF(open_group->room().data()); + auto pubkey_ustring = open_group->pubkey(); + auto pubkey_jarray = util::bytes_from_ustring(env, session::ustring_view {pubkey_ustring.data(), pubkey_ustring.size()}); + auto last_read = open_group->last_read; + auto unread = open_group->unread; + jobject serialized = env->NewObject(clazz, constructor, base_url, room, pubkey_jarray, last_read, unread); + return serialized; } -inline jobject serialize_legacy_group(JNIEnv *env, session::config::convo::legacy_closed_group legacy_group) { - +inline jobject serialize_legacy_group(JNIEnv *env, session::config::convo::legacy_closed_group *legacy_group) { + jclass clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$LegacyClosedGroup"); + jmethodID constructor = env->GetMethodID(clazz, "", "(Ljava/lang/String;JZ)V"); + auto group_id = env->NewStringUTF(legacy_group->id.data()); + auto last_read = legacy_group->last_read; + auto unread = legacy_group->unread; + jobject serialized = env->NewObject(clazz, constructor, group_id, last_read, unread); + return serialized; } -inline jobject serialize_any(JNIEnv *env, session::config::convo::any) +inline jobject serialize_any(JNIEnv *env, session::config::convo::any any) { + if (auto* dm = std::get_if(&any)) { + return serialize_one_to_one(env, dm); + } else if (auto* og = std::get_if(&any)) { + return serialize_open_group(env, og); + } else if (auto* lgc = std::get_if(&any)) { + return serialize_legacy_group(env, lgc); + } + return nullptr; +} -inline session::config::ConvoInfoVolatile* deserialize_convo_info(JNIEnv *env, jobject info) { - // TODO deserialize function +inline session::config::convo::one_to_one* deserialize_one_to_one(JNIEnv *env, jobject info) { + auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OneToOne"); + auto id_getter = env->GetFieldID(clazz, "sessionId", "Ljava/lang/String;"); + auto last_read_getter = env->GetFieldID(clazz, "lastRead", "J"); + auto unread_getter = env->GetFieldID(clazz, "unread", "Z"); + jstring id = static_cast(env->GetObjectField(info, id_getter)); + auto id_chars = env->GetStringUTFChars(id, nullptr); + std::string id_string = std::string{id_chars}; + auto deserialized = new session::config::convo::one_to_one(id_string); + deserialized->last_read = env->GetLongField(info, last_read_getter); + deserialized->unread = env->GetBooleanField(info, unread_getter); + env->ReleaseStringUTFChars(id, id_chars); + return deserialized; +} + +inline session::config::convo::open_group* deserialize_open_group(JNIEnv *env, jobject info) { + auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$OpenGroup"); + auto url_getter = env->GetFieldID(clazz, "baseUrl", "Ljava/lang/String;"); + auto room_getter = env->GetFieldID(clazz, "room", "Ljava/lang/String;"); + auto pub_key_getter = env->GetFieldID(clazz, "pubKey", "[B"); + auto last_read_getter = env->GetFieldID(clazz, "lastRead", "J"); + auto unread_getter = env->GetFieldID(clazz, "unread", "Z"); + jstring base_url = static_cast(env->GetObjectField(info, url_getter)); + jstring room = static_cast(env->GetObjectField(info, room_getter)); + jbyteArray pub_key = (jbyteArray)env->GetObjectField(info, pub_key_getter); + auto base_bytes = env->GetStringUTFChars(base_url, nullptr); + auto base_string = std::string {base_bytes}; + auto room_bytes = env->GetStringUTFChars(room, nullptr); + auto room_string = std::string {room_bytes}; + auto pub_key_ustring = util::ustring_from_bytes(env, pub_key); + + auto deserialized = new session::config::convo::open_group(base_string, room_string,pub_key_ustring); + deserialized->last_read = env->GetLongField(info, last_read_getter); + deserialized->unread = env->GetBooleanField(info, unread_getter); + env->ReleaseStringUTFChars(base_url, base_bytes); + env->ReleaseStringUTFChars(room, room_bytes); + return deserialized; +} + +inline session::config::convo::legacy_closed_group* deserialize_legacy_closed_group(JNIEnv *env, jobject info) { + auto clazz = env->FindClass("network/loki/messenger/libsession_util/util/Conversation$LegacyClosedGroup"); + auto group_id_getter = env->GetFieldID(clazz, "groupId", "Ljava/lang/String;"); + auto last_read_getter = env->GetFieldID(clazz, "lastRead", "J"); + auto unread_getter = env->GetFieldID(clazz, "unread", "Z"); + auto group_id = static_cast(env->GetObjectField(info, group_id_getter)); + auto group_id_bytes = env->GetStringUTFChars(group_id, nullptr); + auto group_id_string = std::string{group_id_bytes}; + auto deserialized = new session::config::convo::legacy_closed_group(group_id_string); + deserialized->last_read = env->GetLongField(info, last_read_getter); + deserialized->unread = env->GetBooleanField(info, unread_getter); + env->ReleaseStringUTFChars(group_id, group_id_bytes); + return deserialized; } #endif //SESSION_ANDROID_CONVERSATION_H \ No newline at end of file diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt index 742145026e..1f670a22fb 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/Config.kt @@ -2,6 +2,7 @@ package network.loki.messenger.libsession_util import network.loki.messenger.libsession_util.util.ConfigWithSeqNo import network.loki.messenger.libsession_util.util.Contact +import network.loki.messenger.libsession_util.util.Conversation import network.loki.messenger.libsession_util.util.UserPic @@ -56,7 +57,6 @@ class UserProfile(pointer: Long) : ConfigBase(pointer) { init { System.loadLibrary("session_util") } - external fun newInstance(ed25519SecretKey: ByteArray): UserProfile external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): UserProfile } @@ -72,11 +72,31 @@ class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) { init { System.loadLibrary("session_util") } - external fun newInstance(ed25519SecretKey: ByteArray): ConversationVolatileConfig - external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): ConversationVolatileConfig - } + external fun getOneToOne(pubKeyHex: String): Conversation.OneToOne? + external fun getOrConstructOneToOne(pubKeyHex: String): Conversation.OneToOne + external fun set(toStore: Conversation.OneToOne) + + external fun getOpenGroup(baseUrl: String, room: String, pubKeyHex: String): Conversation.OpenGroup? + external fun getOrConstructOpenGroup(baseUrl: String, room: String, pubKey: ByteArray): Conversation.OpenGroup + + external fun getLegacyClosedGroup(groupId: String): Conversation.LegacyClosedGroup? + external fun getOrConstructLegacyClosedGroup(groupId: String): Conversation.LegacyClosedGroup + + external fun erase(conversation: Conversation): Boolean + + /** + * Erase all conversations that do not satisfy the `predicate`, similar to [MutableList.removeAll] + */ + external fun eraseAll(predicate: (Conversation) -> Boolean): Int + + external fun sizeOneToOnes(): Int + external fun sizeOpenGroups(): Int + external fun sizeLegacyClosedGroups(): Int + + external fun all(): List + } \ No newline at end of file diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Conversations.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt similarity index 77% rename from libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Conversations.kt rename to libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt index 54f92d5727..02cdd647b4 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Conversations.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/Conversation.kt @@ -1,17 +1,22 @@ package network.loki.messenger.libsession_util.util sealed class Conversation { + + abstract val lastRead: Long + abstract val unread: Boolean + data class OneToOne( val sessionId: String, - val lastRead: Long, - val expiryMode: ExpiryMode, + override val lastRead: Long, + override val unread: Boolean ): Conversation() data class OpenGroup( val baseUrl: String, - val room: String, + val room: String, // lowercase val pubKey: ByteArray, - val lastRead: Long, + override val lastRead: Long, + override val unread: Boolean ) : Conversation() { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -38,7 +43,7 @@ sealed class Conversation { data class LegacyClosedGroup( val groupId: String, - val lastRead: Long, - val expiryMode: ExpiryMode + override val lastRead: Long, + override val unread: Boolean ): Conversation() } \ No newline at end of file