From 66c997dfb26e1790f89cb4092f55d7e6883f1359 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 6 Mar 2023 15:27:06 +1100 Subject: [PATCH] feat: more usergroup functionality, storage functionality for checking pinned status, adding pinned status for NTS/contacts, move community info parse full url to base community, add StorageProtocol logic for group info --- .../securesms/database/Storage.kt | 13 ++++ .../securesms/database/ThreadDatabase.java | 13 ++++ .../util/ConfigurationMessageUtilities.kt | 67 ++++++++++++++++++- libsession-util/src/main/cpp/conversation.cpp | 19 ------ libsession-util/src/main/cpp/user_groups.cpp | 16 +++++ libsession-util/src/main/cpp/util.cpp | 18 +++++ .../loki/messenger/libsession_util/Config.kt | 1 + .../libsession_util/util/BaseCommunity.kt | 9 ++- .../libsession/database/StorageProtocol.kt | 3 + 9 files changed, 136 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 9cd6641cd0..c79d9389f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -745,6 +745,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF return getThreadId(address) } + override fun getThreadId(openGroup: OpenGroup): Long? { + val address = fromSerialized("${openGroup.server}.${openGroup.room}") + return getThreadId(address) + } + override fun getThreadId(address: Address): Long? { val recipient = Recipient.from(context, address, false) return getThreadId(recipient) @@ -866,7 +871,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF return mmsSmsDb.getConversationCount(threadID) } + override fun setPinned(threadID: Long, isPinned: Boolean) { + val threadDB = DatabaseComponent.get(context).threadDatabase() + threadDB.setPinned(threadID, isPinned) + } + override fun isPinned(threadID: Long): Boolean { + val threadDB = DatabaseComponent.get(context).threadDatabase() + return threadDB.isPinned(threadID) + } override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index a3087a4175..408bf6e621 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -740,6 +740,19 @@ public class ThreadDatabase extends Database { notifyConversationListeners(threadId); } + public boolean isPinned(long threadId) { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(TABLE_NAME, new String[]{DATE}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0) == 1; + } + return false; + } finally { + if (cursor != null) cursor.close(); + } + } + public void markAllAsRead(long threadId, boolean isGroupRecipient, long lastSeenTime) { List messages = setRead(threadId, lastSeenTime); if (isGroupRecipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt index ee8fe7375c..a4a75e644d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt @@ -4,9 +4,12 @@ import android.content.Context import network.loki.messenger.libsession_util.ConfigBase import network.loki.messenger.libsession_util.Contacts import network.loki.messenger.libsession_util.ConversationVolatileConfig +import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserProfile +import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.Conversation +import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.UserPic import nl.komponents.kovenant.Promise import org.session.libsession.messaging.MessagingModuleConfiguration @@ -19,7 +22,9 @@ import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.dependencies.DatabaseComponent object ConfigurationMessageUtilities { @@ -103,6 +108,8 @@ object ConfigurationMessageUtilities { private fun maybeUserSecretKey() = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes fun generateUserProfileConfigDump(): ByteArray? { + val storage = MessagingModuleConfiguration.shared.storage + val ownPublicKey = storage.getUserPublicKey() ?: return null val config = ConfigurationMessage.getCurrent(listOf()) ?: return null val secretKey = maybeUserSecretKey() ?: return null val profile = UserProfile.newInstance(secretKey) @@ -112,6 +119,13 @@ object ConfigurationMessageUtilities { if (!picUrl.isNullOrEmpty() && picKey.isNotEmpty()) { profile.setPic(UserPic(picUrl, picKey)) } + val ownThreadId = storage.getThreadId(Address.fromSerialized(ownPublicKey)) + profile.setNtsHidden(ownThreadId != null) + if (ownThreadId != null) { + // have NTS thread + val ntsPinned = storage.isPinned(ownThreadId) + profile.setNtsPriority(if (ntsPinned) 1 else 0) // TODO: implement the pinning priority here in future + } val dump = profile.dump() profile.free() return dump @@ -124,10 +138,16 @@ object ConfigurationMessageUtilities { val contactsWithSettings = storage.getAllContacts().filter { recipient -> recipient.sessionID != localUserKey }.map { contact -> - contact to storage.getRecipientSettings(Address.fromSerialized(contact.sessionID))!! + val address = Address.fromSerialized(contact.sessionID) + val thread = storage.getThreadId(address) + val isPinned = if (thread != null) { + storage.isPinned(thread) + } else false + + Triple(contact, storage.getRecipientSettings(address)!!, isPinned) } val contactConfig = Contacts.newInstance(secretKey) - for ((contact, settings) in contactsWithSettings) { + for ((contact, settings, isPinned) in contactsWithSettings) { val url = contact.profilePictureURL val key = contact.profilePictureEncryptionKey val userPic = if (url.isNullOrEmpty() || key?.isNotEmpty() != true) { @@ -143,7 +163,7 @@ object ConfigurationMessageUtilities { approved = settings.isApproved, approvedMe = settings.hasApprovedMe(), profilePicture = userPic ?: UserPic.DEFAULT, - priority = if () + priority = if (isPinned) 1 else 0 ) contactConfig.set(contactInfo) } @@ -196,4 +216,45 @@ object ConfigurationMessageUtilities { return dump } + fun generateUserGroupDump(context: Context): ByteArray? { + val secretKey = maybeUserSecretKey() ?: return null + val storage = MessagingModuleConfiguration.shared.storage + val groupConfig = UserGroupsConfig.newInstance(secretKey) + val allOpenGroups = storage.getAllOpenGroups().values.mapNotNull { openGroup -> + val (baseUrl, room, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: return@mapNotNull null + val pubKeyHex = Hex.toStringCondensed(pubKey) + val baseInfo = BaseCommunityInfo(baseUrl, room, pubKeyHex) + val threadId = storage.getThreadId(openGroup) ?: return@mapNotNull null + val isPinned = storage.isPinned(threadId) + GroupInfo.CommunityGroupInfo(baseInfo, if (isPinned) 1 else 0) + } + + val allLgc = storage.getAllGroups().filter { it.isClosedGroup }.mapNotNull { group -> + val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString() + val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return@mapNotNull null + val threadId = storage.getThreadId(group.encodedId) + val isPinned = threadId?.let { storage.isPinned(threadId) } ?: false + val sessionId = GroupUtil.doubleEncodeGroupID(group.getId()) + val admins = group.admins.map { it.serialize() to true }.toMap() + val members = group.members.filterNot { it.serialize() !in admins.keys }.map { it.serialize() to false }.toMap() + val encPub = storage.getClosedGroupEncryptionKeyPairs(group.) + GroupInfo.LegacyGroupInfo( + sessionId = sessionId, + name = group.title, + members = admins + members, + hidden = threadId == null, + priority = if (isPinned) 1 else 0, + encPubKey = encryptionKeyPair.publicKey.serialize(), + encSecKey = encryptionKeyPair.privateKey.serialize() + ) + } + + (allOpenGroups + allLgc).forEach { groupInfo -> + groupConfig.set(groupInfo) + } + val dump = groupConfig.dump() + groupConfig.free() + return dump + } + } \ 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 0b28fc0508..d8f663581c 100644 --- a/libsession-util/src/main/cpp/conversation.cpp +++ b/libsession-util/src/main/cpp/conversation.cpp @@ -3,25 +3,6 @@ #pragma clang diagnostic push -extern "C" -JNIEXPORT jobject JNICALL -Java_network_loki_messenger_libsession_1util_util_Conversation_00024Community_00024Companion_parseFullUrl( - JNIEnv *env, jobject thiz, jstring full_url) { - auto bytes = env->GetStringUTFChars(full_url, nullptr); - auto [base, room, pk] = session::config::convo::community::parse_full_url(bytes); - env->ReleaseStringUTFChars(full_url, bytes); - - jclass clazz = env->FindClass("kotlin/Triple"); - jmethodID constructor = env->GetMethodID(clazz, "", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"); - - auto base_j = env->NewStringUTF(base.data()); - auto room_j = env->NewStringUTF(room.data()); - auto pk_jbytes = util::bytes_from_ustring(env, pk); - - jobject triple = env->NewObject(clazz, constructor, base_j, room_j, pk_jbytes); - return triple; -} - extern "C" #pragma ide diagnostic ignored "bugprone-reserved-identifier" JNIEXPORT jobject JNICALL diff --git a/libsession-util/src/main/cpp/user_groups.cpp b/libsession-util/src/main/cpp/user_groups.cpp index 07d5ee066b..8a526ec0d9 100644 --- a/libsession-util/src/main/cpp/user_groups.cpp +++ b/libsession-util/src/main/cpp/user_groups.cpp @@ -110,6 +110,22 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getOrConstructLega return serialize_legacy_group_info(env, group); } +extern "C" +JNIEXPORT void JNICALL +Java_network_loki_messenger_libsession_1util_UserGroupsConfig_set__Lnetwork_loki_messenger_libsession_1util_util_GroupInfo_2( + JNIEnv *env, jobject thiz, jobject group_info) { + auto conf = ptrToUserGroups(env, thiz); + auto communityInfo = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$CommunityGroupInfo"); + auto legacyInfo = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$LegacyGroupInfo"); + if (env->GetObjectClass(group_info) == communityInfo) { + auto deserialized = deserialize_community_info(env, group_info); + conf->set(deserialized); + } else if (env->GetObjectClass(group_info) == legacyInfo) { + auto deserialized = deserialize_legacy_group_info(env, group_info); + conf->set(deserialized); + } +} + extern "C" JNIEXPORT void JNICALL Java_network_loki_messenger_libsession_1util_UserGroupsConfig_set__Lnetwork_loki_messenger_libsession_1util_util_GroupInfo_CommunityGroupInfo_2( diff --git a/libsession-util/src/main/cpp/util.cpp b/libsession-util/src/main/cpp/util.cpp index 7b093433cc..11f1c94a1b 100644 --- a/libsession-util/src/main/cpp/util.cpp +++ b/libsession-util/src/main/cpp/util.cpp @@ -101,4 +101,22 @@ Java_network_loki_messenger_libsession_1util_util_Sodium_ed25519PkToCurve25519(J } jbyteArray curve_pk_jarray = util::bytes_from_ustring(env, session::ustring_view {curve_pk.data(), curve_pk.size()}); return curve_pk_jarray; +} +extern "C" +JNIEXPORT jobject JNICALL +Java_network_loki_messenger_libsession_1util_util_BaseCommunityInfo_00024Companion_parseFullUrl( + JNIEnv *env, jobject thiz, jstring full_url) { + auto bytes = env->GetStringUTFChars(full_url, nullptr); + auto [base, room, pk] = session::config::community::parse_full_url(bytes); + env->ReleaseStringUTFChars(full_url, bytes); + + jclass clazz = env->FindClass("kotlin/Triple"); + jmethodID constructor = env->GetMethodID(clazz, "", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"); + + auto base_j = env->NewStringUTF(base.data()); + auto room_j = env->NewStringUTF(room.data()); + auto pk_jbytes = util::bytes_from_ustring(env, pk); + + jobject triple = env->NewObject(clazz, constructor, base_j, room_j, pk_jbytes); + return triple; } \ 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 fbffa5449b..7aa5546a67 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 @@ -155,6 +155,7 @@ class UserGroupsConfig(pointer: Long): ConfigBase(pointer) { external fun getLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo? external fun getOrConstructCommunityInfo(baseUrl: String, room: String, pubKeyHex: String): GroupInfo.CommunityGroupInfo external fun getOrConstructLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo + external fun set(groupInfo: GroupInfo) external fun set(communityInfo: GroupInfo.CommunityGroupInfo) external fun set(legacyGroupInfo: GroupInfo.LegacyGroupInfo) external fun erase(communityInfo: GroupInfo.CommunityGroupInfo) diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/BaseCommunity.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/BaseCommunity.kt index aa2bbc0620..f2312ff7fa 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/BaseCommunity.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/BaseCommunity.kt @@ -1,3 +1,10 @@ package network.loki.messenger.libsession_util.util -data class BaseCommunityInfo(val baseUrl: String, val room: String, val pubKeyHex: String) \ No newline at end of file +data class BaseCommunityInfo(val baseUrl: String, val room: String, val pubKeyHex: String) { + companion object { + init { + System.loadLibrary("session_util") + } + external fun parseFullUrl(fullUrl: String): Triple? + } +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 3f246af80e..184b36197a 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -153,6 +153,7 @@ interface StorageProtocol { fun getOrCreateThreadIdFor(address: Address): Long fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long fun getThreadId(publicKeyOrOpenGroupID: String): Long? + fun getThreadId(openGroup: OpenGroup): Long? fun getThreadId(address: Address): Long? fun getThreadId(recipient: Recipient): Long? fun getThreadIdForMms(mmsId: Long): Long @@ -160,6 +161,8 @@ interface StorageProtocol { fun trimThread(threadID: Long, threadLimit: Int) fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long + fun setPinned(threadID: Long, isPinned: Boolean) + fun isPinned(threadID: Long): Boolean // Contacts fun getContactWithSessionID(sessionID: String): Contact?