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

This commit is contained in:
0x330a 2023-03-06 15:27:06 +11:00
parent 2a701f2cc3
commit 66c997dfb2
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
9 changed files with 136 additions and 23 deletions

View File

@ -745,6 +745,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
return getThreadId(address) 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? { override fun getThreadId(address: Address): Long? {
val recipient = Recipient.from(context, address, false) val recipient = Recipient.from(context, address, false)
return getThreadId(recipient) return getThreadId(recipient)
@ -866,7 +871,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
return mmsSmsDb.getConversationCount(threadID) 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 { override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri {
return PartAuthority.getAttachmentDataUri(attachmentId) return PartAuthority.getAttachmentDataUri(attachmentId)

View File

@ -740,6 +740,19 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId); 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) { public void markAllAsRead(long threadId, boolean isGroupRecipient, long lastSeenTime) {
List<MarkedMessageInfo> messages = setRead(threadId, lastSeenTime); List<MarkedMessageInfo> messages = setRead(threadId, lastSeenTime);
if (isGroupRecipient) { if (isGroupRecipient) {

View File

@ -4,9 +4,12 @@ import android.content.Context
import network.loki.messenger.libsession_util.ConfigBase import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.Contacts import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig 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.UserProfile
import network.loki.messenger.libsession_util.util.BaseCommunityInfo
import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.Contact
import network.loki.messenger.libsession_util.util.Conversation 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 network.loki.messenger.libsession_util.util.UserPic
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import org.session.libsession.messaging.MessagingModuleConfiguration 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.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
object ConfigurationMessageUtilities { object ConfigurationMessageUtilities {
@ -103,6 +108,8 @@ object ConfigurationMessageUtilities {
private fun maybeUserSecretKey() = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes private fun maybeUserSecretKey() = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes
fun generateUserProfileConfigDump(): ByteArray? { fun generateUserProfileConfigDump(): ByteArray? {
val storage = MessagingModuleConfiguration.shared.storage
val ownPublicKey = storage.getUserPublicKey() ?: return null
val config = ConfigurationMessage.getCurrent(listOf()) ?: return null val config = ConfigurationMessage.getCurrent(listOf()) ?: return null
val secretKey = maybeUserSecretKey() ?: return null val secretKey = maybeUserSecretKey() ?: return null
val profile = UserProfile.newInstance(secretKey) val profile = UserProfile.newInstance(secretKey)
@ -112,6 +119,13 @@ object ConfigurationMessageUtilities {
if (!picUrl.isNullOrEmpty() && picKey.isNotEmpty()) { if (!picUrl.isNullOrEmpty() && picKey.isNotEmpty()) {
profile.setPic(UserPic(picUrl, picKey)) 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() val dump = profile.dump()
profile.free() profile.free()
return dump return dump
@ -124,10 +138,16 @@ object ConfigurationMessageUtilities {
val contactsWithSettings = storage.getAllContacts().filter { recipient -> val contactsWithSettings = storage.getAllContacts().filter { recipient ->
recipient.sessionID != localUserKey recipient.sessionID != localUserKey
}.map { contact -> }.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) val contactConfig = Contacts.newInstance(secretKey)
for ((contact, settings) in contactsWithSettings) { for ((contact, settings, isPinned) in contactsWithSettings) {
val url = contact.profilePictureURL val url = contact.profilePictureURL
val key = contact.profilePictureEncryptionKey val key = contact.profilePictureEncryptionKey
val userPic = if (url.isNullOrEmpty() || key?.isNotEmpty() != true) { val userPic = if (url.isNullOrEmpty() || key?.isNotEmpty() != true) {
@ -143,7 +163,7 @@ object ConfigurationMessageUtilities {
approved = settings.isApproved, approved = settings.isApproved,
approvedMe = settings.hasApprovedMe(), approvedMe = settings.hasApprovedMe(),
profilePicture = userPic ?: UserPic.DEFAULT, profilePicture = userPic ?: UserPic.DEFAULT,
priority = if () priority = if (isPinned) 1 else 0
) )
contactConfig.set(contactInfo) contactConfig.set(contactInfo)
} }
@ -196,4 +216,45 @@ object ConfigurationMessageUtilities {
return dump 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
}
} }

View File

@ -3,25 +3,6 @@
#pragma clang diagnostic push #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, "<init>", "(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" extern "C"
#pragma ide diagnostic ignored "bugprone-reserved-identifier" #pragma ide diagnostic ignored "bugprone-reserved-identifier"
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL

View File

@ -110,6 +110,22 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_getOrConstructLega
return serialize_legacy_group_info(env, group); 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" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_set__Lnetwork_loki_messenger_libsession_1util_util_GroupInfo_CommunityGroupInfo_2( Java_network_loki_messenger_libsession_1util_UserGroupsConfig_set__Lnetwork_loki_messenger_libsession_1util_util_GroupInfo_CommunityGroupInfo_2(

View File

@ -102,3 +102,21 @@ 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()}); jbyteArray curve_pk_jarray = util::bytes_from_ustring(env, session::ustring_view {curve_pk.data(), curve_pk.size()});
return curve_pk_jarray; 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, "<init>", "(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;
}

View File

@ -155,6 +155,7 @@ class UserGroupsConfig(pointer: Long): ConfigBase(pointer) {
external fun getLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo? external fun getLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo?
external fun getOrConstructCommunityInfo(baseUrl: String, room: String, pubKeyHex: String): GroupInfo.CommunityGroupInfo external fun getOrConstructCommunityInfo(baseUrl: String, room: String, pubKeyHex: String): GroupInfo.CommunityGroupInfo
external fun getOrConstructLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo external fun getOrConstructLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo
external fun set(groupInfo: GroupInfo)
external fun set(communityInfo: GroupInfo.CommunityGroupInfo) external fun set(communityInfo: GroupInfo.CommunityGroupInfo)
external fun set(legacyGroupInfo: GroupInfo.LegacyGroupInfo) external fun set(legacyGroupInfo: GroupInfo.LegacyGroupInfo)
external fun erase(communityInfo: GroupInfo.CommunityGroupInfo) external fun erase(communityInfo: GroupInfo.CommunityGroupInfo)

View File

@ -1,3 +1,10 @@
package network.loki.messenger.libsession_util.util package network.loki.messenger.libsession_util.util
data class BaseCommunityInfo(val baseUrl: String, val room: String, val pubKeyHex: String) 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<String, String, ByteArray>?
}
}

View File

@ -153,6 +153,7 @@ interface StorageProtocol {
fun getOrCreateThreadIdFor(address: Address): Long fun getOrCreateThreadIdFor(address: Address): Long
fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long
fun getThreadId(publicKeyOrOpenGroupID: String): Long? fun getThreadId(publicKeyOrOpenGroupID: String): Long?
fun getThreadId(openGroup: OpenGroup): Long?
fun getThreadId(address: Address): Long? fun getThreadId(address: Address): Long?
fun getThreadId(recipient: Recipient): Long? fun getThreadId(recipient: Recipient): Long?
fun getThreadIdForMms(mmsId: Long): Long fun getThreadIdForMms(mmsId: Long): Long
@ -160,6 +161,8 @@ interface StorageProtocol {
fun trimThread(threadID: Long, threadLimit: Int) fun trimThread(threadID: Long, threadLimit: Int)
fun trimThreadBefore(threadID: Long, timestamp: Long) fun trimThreadBefore(threadID: Long, timestamp: Long)
fun getMessageCount(threadID: Long): Long fun getMessageCount(threadID: Long): Long
fun setPinned(threadID: Long, isPinned: Boolean)
fun isPinned(threadID: Long): Boolean
// Contacts // Contacts
fun getContactWithSessionID(sessionID: String): Contact? fun getContactWithSessionID(sessionID: String): Contact?