mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 02:55:23 +00:00
feat: add almost all group editing cases, need to hook into the thread deletion for groups in the user groups
This commit is contained in:
parent
eca5712282
commit
c28fe290df
@ -7,10 +7,7 @@ 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.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.*
|
||||||
import network.loki.messenger.libsession_util.util.Conversation
|
|
||||||
import network.loki.messenger.libsession_util.util.ExpiryMode
|
|
||||||
import network.loki.messenger.libsession_util.util.UserPic
|
|
||||||
import org.session.libsession.avatars.AvatarHelper
|
import org.session.libsession.avatars.AvatarHelper
|
||||||
import org.session.libsession.database.StorageProtocol
|
import org.session.libsession.database.StorageProtocol
|
||||||
import org.session.libsession.messaging.BlindedIdMapping
|
import org.session.libsession.messaging.BlindedIdMapping
|
||||||
@ -468,6 +465,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
|
|||||||
// Notify the user
|
// Notify the user
|
||||||
val threadID = getOrCreateThreadIdFor(Address.fromSerialized(groupId))
|
val threadID = getOrCreateThreadIdFor(Address.fromSerialized(groupId))
|
||||||
insertOutgoingInfoMessage(context, groupId, SignalServiceGroup.Type.CREATION, title, members.map { it.serialize() }, admins.map { it.serialize() }, threadID, formationTimestamp)
|
insertOutgoingInfoMessage(context, groupId, SignalServiceGroup.Type.CREATION, title, members.map { it.serialize() }, admins.map { it.serialize() }, threadID, formationTimestamp)
|
||||||
|
// Don't create config group here, it's from a config update
|
||||||
// Start polling
|
// Start polling
|
||||||
ClosedGroupPollerV2.shared.startPolling(group.sessionId)
|
ClosedGroupPollerV2.shared.startPolling(group.sessionId)
|
||||||
}
|
}
|
||||||
@ -671,14 +669,55 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
|
|||||||
|
|
||||||
override fun createGroup(groupId: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long) {
|
override fun createGroup(groupId: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long) {
|
||||||
DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp)
|
DatabaseComponent.get(context).groupDatabase().create(groupId, title, members, avatar, relay, admins, formationTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map<String, Boolean>, formationTimestamp: Long, encryptionKeyPair: ECKeyPair) {
|
||||||
val volatiles = configFactory.convoVolatile ?: return
|
val volatiles = configFactory.convoVolatile ?: return
|
||||||
val groupPublicKey = GroupUtil.doubleDecodeGroupId(groupId)
|
val userGroups = configFactory.userGroups ?: return
|
||||||
val groupVolatileConfig = volatiles.getOrConstructLegacyGroup(groupPublicKey)
|
val groupVolatileConfig = volatiles.getOrConstructLegacyGroup(groupPublicKey)
|
||||||
groupVolatileConfig.lastRead = formationTimestamp
|
groupVolatileConfig.lastRead = formationTimestamp
|
||||||
volatiles.set(groupVolatileConfig)
|
volatiles.set(groupVolatileConfig)
|
||||||
|
val groupInfo = GroupInfo.LegacyGroupInfo(
|
||||||
|
sessionId = groupPublicKey,
|
||||||
|
name = name,
|
||||||
|
members = members,
|
||||||
|
hidden = false,
|
||||||
|
encPubKey = encryptionKeyPair.publicKey.serialize(),
|
||||||
|
encSecKey = encryptionKeyPair.privateKey.serialize(),
|
||||||
|
disappearingTimer = 0L
|
||||||
|
)
|
||||||
|
// shouldn't exist, don't use getOrConstruct + copy
|
||||||
|
userGroups.set(groupInfo)
|
||||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateGroupConfig(groupPublicKey: String) {
|
||||||
|
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||||
|
val groupAddress = fromSerialized(groupID)
|
||||||
|
// TODO: probably add a check in here for isActive?
|
||||||
|
// TODO: also check if local user is a member / maybe run delete otherwise?
|
||||||
|
val existingGroup = getGroup(groupID)
|
||||||
|
?: return Log.w("Loki-DBG", "No existing group for ${groupPublicKey.take(4)}...${groupPublicKey.takeLast(4)} when updating group config")
|
||||||
|
val userGroups = configFactory.userGroups ?: return
|
||||||
|
val name = existingGroup.title
|
||||||
|
val admins = existingGroup.admins.map { it.serialize() }
|
||||||
|
val members = existingGroup.members.map { it.serialize() }
|
||||||
|
val membersMap = GroupUtil.createConfigMemberMap(admins = admins, members = members)
|
||||||
|
val latestKeyPair = getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
|
||||||
|
?: return Log.w("Loki-DBG", "No latest closed group encryption key pair for ${groupPublicKey.take(4)}...${groupPublicKey.takeLast(4)} when updating group config")
|
||||||
|
val recipientSettings = getRecipientSettings(groupAddress) ?: return
|
||||||
|
val threadID = getThreadId(groupAddress) ?: return
|
||||||
|
val groupInfo = userGroups.getOrConstructLegacyGroupInfo(groupPublicKey).copy(
|
||||||
|
name = name,
|
||||||
|
members = membersMap,
|
||||||
|
encPubKey = latestKeyPair.publicKey.serialize(),
|
||||||
|
encSecKey = latestKeyPair.privateKey.serialize(),
|
||||||
|
priority = if (isPinned(threadID)) 1 else 0,
|
||||||
|
disappearingTimer = recipientSettings.expireMessages.toLong()
|
||||||
|
)
|
||||||
|
userGroups.set(groupInfo)
|
||||||
|
}
|
||||||
|
|
||||||
override fun isGroupActive(groupPublicKey: String): Boolean {
|
override fun isGroupActive(groupPublicKey: String): Boolean {
|
||||||
return DatabaseComponent.get(context).groupDatabase().getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true
|
return DatabaseComponent.get(context).groupDatabase().getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
package org.thoughtcrime.securesms.groups
|
package org.thoughtcrime.securesms.groups
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import network.loki.messenger.libsession_util.util.GroupInfo
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
||||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
|
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.GroupRecord
|
||||||
|
import org.session.libsession.utilities.GroupUtil
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.session.libsignal.utilities.toHexString
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
|
||||||
object ClosedGroupManager {
|
object ClosedGroupManager {
|
||||||
|
|
||||||
@ -28,4 +34,31 @@ object ClosedGroupManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ConfigFactory.removeLegacyGroup(group: GroupRecord): Boolean {
|
||||||
|
val groups = userGroups ?: return false
|
||||||
|
if (!group.isClosedGroup) return false
|
||||||
|
val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())
|
||||||
|
return groups.eraseLegacyGroup(groupPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ConfigFactory.updateLegacyGroup(groupRecipientSettings: Recipient.RecipientSettings, group: GroupRecord) {
|
||||||
|
val groups = userGroups ?: return
|
||||||
|
if (!group.isClosedGroup) return
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
val threadId = storage.getThreadId(group.encodedId) ?: return
|
||||||
|
val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())
|
||||||
|
val latestKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return
|
||||||
|
val legacyInfo = groups.getOrConstructLegacyGroupInfo(groupPublicKey)
|
||||||
|
val latestMemberMap = GroupUtil.createConfigMemberMap(group.members.map(Address::serialize), group.admins.map(Address::serialize))
|
||||||
|
val toSet = legacyInfo.copy(
|
||||||
|
members = latestMemberMap,
|
||||||
|
name = group.title,
|
||||||
|
disappearingTimer = groupRecipientSettings.expireMessages.toLong(),
|
||||||
|
priority = if (storage.isPinned(threadId)) 1 else 0,
|
||||||
|
encSecKey = latestKeyPair.privateKey.serialize(),
|
||||||
|
encPubKey = latestKeyPair.publicKey.serialize()
|
||||||
|
)
|
||||||
|
groups.set(toSet)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -16,6 +16,7 @@ import androidx.loader.app.LoaderManager
|
|||||||
import androidx.loader.content.Loader
|
import androidx.loader.content.Loader
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.task
|
import nl.komponents.kovenant.task
|
||||||
@ -28,16 +29,28 @@ import org.session.libsession.utilities.GroupUtil
|
|||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.ThemeUtil
|
import org.session.libsession.utilities.ThemeUtil
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.toHexString
|
import org.session.libsignal.utilities.toHexString
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
|
||||||
|
import org.thoughtcrime.securesms.database.Storage
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
|
import org.thoughtcrime.securesms.groups.ClosedGroupManager.updateLegacyGroup
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
import org.thoughtcrime.securesms.util.fadeIn
|
import org.thoughtcrime.securesms.util.fadeIn
|
||||||
import org.thoughtcrime.securesms.util.fadeOut
|
import org.thoughtcrime.securesms.util.fadeOut
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var groupConfigFactory: ConfigFactory
|
||||||
|
@Inject
|
||||||
|
lateinit var storage: Storage
|
||||||
|
|
||||||
private val originalMembers = HashSet<String>()
|
private val originalMembers = HashSet<String>()
|
||||||
private val zombies = HashSet<String>()
|
private val zombies = HashSet<String>()
|
||||||
private val members = HashSet<String>()
|
private val members = HashSet<String>()
|
||||||
@ -306,6 +319,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
promise.successUi {
|
promise.successUi {
|
||||||
loaderContainer.fadeOut()
|
loaderContainer.fadeOut()
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
updateGroupConfig()
|
||||||
finish()
|
finish()
|
||||||
}.failUi { exception ->
|
}.failUi { exception ->
|
||||||
val message = if (exception is MessageSender.Error) exception.description else "An error occurred"
|
val message = if (exception is MessageSender.Error) exception.description else "An error occurred"
|
||||||
@ -316,5 +330,13 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupMembers(val members: List<String>, val zombieMembers: List<String>) { }
|
private fun updateGroupConfig() {
|
||||||
|
val latestRecipient = storage.getRecipientSettings(Address.fromSerialized(groupID))
|
||||||
|
?: return Log.w("Loki", "No recipient settings when trying to update group config")
|
||||||
|
val latestGroup = storage.getGroup(groupID)
|
||||||
|
?: return Log.w("Loki", "No group record when trying to update group config")
|
||||||
|
groupConfigFactory.updateLegacyGroup(latestRecipient, latestGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
class GroupMembers(val members: List<String>, val zombieMembers: List<String>)
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import android.graphics.Bitmap;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
import org.session.libsession.utilities.DistributionTypes;
|
import org.session.libsession.utilities.DistributionTypes;
|
||||||
import org.session.libsession.utilities.GroupUtil;
|
import org.session.libsession.utilities.GroupUtil;
|
||||||
@ -16,11 +17,14 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
|
|||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import network.loki.messenger.libsession_util.UserGroupsConfig;
|
||||||
|
|
||||||
public class GroupManager {
|
public class GroupManager {
|
||||||
|
|
||||||
public static long getOpenGroupThreadID(String id, @NonNull Context context) {
|
public static long getOpenGroupThreadID(String id, @NonNull Context context) {
|
||||||
|
@ -625,7 +625,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
OpenGroupManager.delete(v2OpenGroup.server, v2OpenGroup.room, this@HomeActivity)
|
OpenGroupManager.delete(v2OpenGroup.server, v2OpenGroup.room, this@HomeActivity)
|
||||||
} else {
|
} else {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
threadDb.deleteConversation(threadID)
|
storage.deleteConversation(threadID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update the badge count
|
// Update the badge count
|
||||||
|
@ -257,13 +257,13 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun deleteThread(threadId: Long): ResultOf<Unit> {
|
override suspend fun deleteThread(threadId: Long): ResultOf<Unit> {
|
||||||
sessionJobDb.cancelPendingMessageSendJobs(threadId)
|
sessionJobDb.cancelPendingMessageSendJobs(threadId)
|
||||||
threadDb.deleteConversation(threadId)
|
storage.deleteConversation(threadId)
|
||||||
return ResultOf.Success(Unit)
|
return ResultOf.Success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit> {
|
override suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit> {
|
||||||
sessionJobDb.cancelPendingMessageSendJobs(thread.threadId)
|
sessionJobDb.cancelPendingMessageSendJobs(thread.threadId)
|
||||||
threadDb.deleteConversation(thread.threadId)
|
storage.deleteConversation(thread.threadId)
|
||||||
return ResultOf.Success(Unit)
|
return ResultOf.Success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
|
|
||||||
override fun declineMessageRequest(threadId: Long) {
|
override fun declineMessageRequest(threadId: Long) {
|
||||||
sessionJobDb.cancelPendingMessageSendJobs(threadId)
|
sessionJobDb.cancelPendingMessageSendJobs(threadId)
|
||||||
threadDb.deleteConversation(threadId)
|
storage.deleteConversation(threadId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hasReceived(threadId: Long): Boolean {
|
override fun hasReceived(threadId: Long): Boolean {
|
||||||
|
@ -6,11 +6,7 @@ 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.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.*
|
||||||
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 nl.komponents.kovenant.Promise
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.jobs.ConfigurationSyncJob
|
import org.session.libsession.messaging.jobs.ConfigurationSyncJob
|
||||||
@ -21,6 +17,7 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
|
|||||||
import org.session.libsession.messaging.utilities.SessionId
|
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.SSKEnvironment
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
@ -163,7 +160,8 @@ object ConfigurationMessageUtilities {
|
|||||||
approved = settings.isApproved,
|
approved = settings.isApproved,
|
||||||
approvedMe = settings.hasApprovedMe(),
|
approvedMe = settings.hasApprovedMe(),
|
||||||
profilePicture = userPic ?: UserPic.DEFAULT,
|
profilePicture = userPic ?: UserPic.DEFAULT,
|
||||||
priority = if (isPinned) 1 else 0
|
priority = if (isPinned) 1 else 0,
|
||||||
|
expiryMode = if (settings.expireMessages == 0) ExpiryMode.NONE else ExpiryMode.AfterRead(settings.expireMessages.toLong())
|
||||||
)
|
)
|
||||||
contactConfig.set(contactInfo)
|
contactConfig.set(contactInfo)
|
||||||
}
|
}
|
||||||
@ -185,7 +183,7 @@ object ConfigurationMessageUtilities {
|
|||||||
val contact = when {
|
val contact = when {
|
||||||
recipient.isOpenGroupRecipient -> {
|
recipient.isOpenGroupRecipient -> {
|
||||||
val openGroup = storage.getOpenGroup(current.threadId) ?: continue
|
val openGroup = storage.getOpenGroup(current.threadId) ?: continue
|
||||||
val (base, room, pubKey) = Conversation.Community.parseFullUrl(openGroup.joinURL) ?: continue
|
val (base, room, pubKey) = BaseCommunityInfo.parseFullUrl(openGroup.joinURL) ?: continue
|
||||||
convoConfig.getOrConstructCommunity(base, room, pubKey)
|
convoConfig.getOrConstructCommunity(base, room, pubKey)
|
||||||
}
|
}
|
||||||
recipient.isClosedGroupRecipient -> {
|
recipient.isClosedGroupRecipient -> {
|
||||||
@ -229,7 +227,9 @@ object ConfigurationMessageUtilities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val allLgc = storage.getAllGroups().filter { it.isClosedGroup }.mapNotNull { group ->
|
val allLgc = storage.getAllGroups().filter { it.isClosedGroup }.mapNotNull { group ->
|
||||||
val groupPublicKey = GroupUtil.doubleDecodeGroupID(group.encodedId).toHexString()
|
val groupAddress = Address.fromSerialized(group.encodedId)
|
||||||
|
val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupAddress.serialize()).toHexString()
|
||||||
|
val recipient = storage.getRecipientSettings(groupAddress) ?: return@mapNotNull null
|
||||||
val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return@mapNotNull null
|
val encryptionKeyPair = storage.getLatestClosedGroupEncryptionKeyPair(groupPublicKey) ?: return@mapNotNull null
|
||||||
val threadId = storage.getThreadId(group.encodedId)
|
val threadId = storage.getThreadId(group.encodedId)
|
||||||
val isPinned = threadId?.let { storage.isPinned(threadId) } ?: false
|
val isPinned = threadId?.let { storage.isPinned(threadId) } ?: false
|
||||||
@ -243,7 +243,8 @@ object ConfigurationMessageUtilities {
|
|||||||
hidden = threadId == null,
|
hidden = threadId == null,
|
||||||
priority = if (isPinned) 1 else 0,
|
priority = if (isPinned) 1 else 0,
|
||||||
encPubKey = encryptionKeyPair.publicKey.serialize(),
|
encPubKey = encryptionKeyPair.publicKey.serialize(),
|
||||||
encSecKey = encryptionKeyPair.privateKey.serialize()
|
encSecKey = encryptionKeyPair.privateKey.serialize(),
|
||||||
|
disappearingTimer = recipient.expireMessages.toLong()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(allOpenGroups + allLgc).forEach { groupInfo ->
|
(allOpenGroups + allLgc).forEach { groupInfo ->
|
||||||
|
@ -233,6 +233,7 @@ object MockDataGenerator {
|
|||||||
val encryptionKeyPair = Curve.generateKeyPair()
|
val encryptionKeyPair = Curve.generateKeyPair()
|
||||||
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, randomGroupPublicKey, System.currentTimeMillis())
|
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, randomGroupPublicKey, System.currentTimeMillis())
|
||||||
storage.setExpirationTimer(groupId, 0)
|
storage.setExpirationTimer(groupId, 0)
|
||||||
|
storage.createInitialConfigGroup(randomGroupPublicKey, groupName, GroupUtil.createConfigMemberMap(members, setOf(adminUserId)), System.currentTimeMillis(), encryptionKeyPair)
|
||||||
|
|
||||||
// Add the group created message
|
// Add the group created message
|
||||||
if (userSessionId == adminUserId) {
|
if (userSessionId == adminUserId) {
|
||||||
|
@ -233,3 +233,23 @@ Java_network_loki_messenger_libsession_1util_UserGroupsConfig_allLegacyGroupInfo
|
|||||||
jobject legacy_stack = iterator_as_java_stack(env, conf->begin_legacy_groups(), conf->end());
|
jobject legacy_stack = iterator_as_java_stack(env, conf->begin_legacy_groups(), conf->end());
|
||||||
return legacy_stack;
|
return legacy_stack;
|
||||||
}
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseCommunity(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jobject base_community_info) {
|
||||||
|
auto conf = ptrToUserGroups(env, thiz);
|
||||||
|
auto base_community = util::deserialize_base_community(env, base_community_info);
|
||||||
|
return conf->erase_community(base_community.base_url(),base_community.room());
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_network_loki_messenger_libsession_1util_UserGroupsConfig_eraseLegacyGroup(JNIEnv *env,
|
||||||
|
jobject thiz,
|
||||||
|
jstring session_id) {
|
||||||
|
auto conf = ptrToUserGroups(env, thiz);
|
||||||
|
auto session_id_bytes = env->GetStringUTFChars(session_id, nullptr);
|
||||||
|
bool return_bool = conf->erase_legacy_group(session_id_bytes);
|
||||||
|
env->ReleaseStringUTFChars(session_id, session_id_bytes);
|
||||||
|
return return_bool;
|
||||||
|
}
|
@ -1,10 +1,6 @@
|
|||||||
package network.loki.messenger.libsession_util
|
package network.loki.messenger.libsession_util
|
||||||
|
|
||||||
import network.loki.messenger.libsession_util.util.ConfigPush
|
import network.loki.messenger.libsession_util.util.*
|
||||||
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 org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind
|
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind
|
||||||
|
|
||||||
|
|
||||||
@ -160,6 +156,8 @@ class UserGroupsConfig(pointer: Long): ConfigBase(pointer) {
|
|||||||
external fun set(legacyGroupInfo: GroupInfo.LegacyGroupInfo)
|
external fun set(legacyGroupInfo: GroupInfo.LegacyGroupInfo)
|
||||||
external fun erase(communityInfo: GroupInfo.CommunityGroupInfo)
|
external fun erase(communityInfo: GroupInfo.CommunityGroupInfo)
|
||||||
external fun erase(legacyGroupInfo: GroupInfo.LegacyGroupInfo)
|
external fun erase(legacyGroupInfo: GroupInfo.LegacyGroupInfo)
|
||||||
|
external fun eraseCommunity(baseCommunityInfo: BaseCommunityInfo): Boolean
|
||||||
|
external fun eraseLegacyGroup(sessionId: String): Boolean
|
||||||
external fun sizeCommunityInfo(): Int
|
external fun sizeCommunityInfo(): Int
|
||||||
external fun sizeLegacyGroupInfo(): Int
|
external fun sizeLegacyGroupInfo(): Int
|
||||||
external fun size(): Int
|
external fun size(): Int
|
||||||
|
@ -121,6 +121,8 @@ interface StorageProtocol {
|
|||||||
// Closed Groups
|
// Closed Groups
|
||||||
fun getGroup(groupID: String): GroupRecord?
|
fun getGroup(groupID: String): GroupRecord?
|
||||||
fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long)
|
fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long)
|
||||||
|
fun createInitialConfigGroup(groupPublicKey: String, name: String, members: Map<String, Boolean>, formationTimestamp: Long, encryptionKeyPair: ECKeyPair)
|
||||||
|
fun updateGroupConfig(groupPublicKey: String)
|
||||||
fun isGroupActive(groupPublicKey: String): Boolean
|
fun isGroupActive(groupPublicKey: String): Boolean
|
||||||
fun setActive(groupID: String, value: Boolean)
|
fun setActive(groupID: String, value: Boolean)
|
||||||
fun getZombieMembers(groupID: String): Set<String>
|
fun getZombieMembers(groupID: String): Set<String>
|
||||||
|
@ -73,6 +73,7 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
|
|||||||
// Notify the user
|
// Notify the user
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTime)
|
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTime)
|
||||||
|
storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair)
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
// Start polling
|
// Start polling
|
||||||
@ -84,24 +85,6 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
|
|||||||
return deferred.promise
|
return deferred.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MessageSender.update(groupPublicKey: String, members: List<String>, name: String) {
|
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
|
||||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
|
||||||
val group = storage.getGroup(groupID) ?: run {
|
|
||||||
Log.d("Loki", "Can't update nonexistent closed group.")
|
|
||||||
throw Error.NoThread
|
|
||||||
}
|
|
||||||
// Update name if needed
|
|
||||||
if (name != group.title) { setName(groupPublicKey, name) }
|
|
||||||
// Add members if needed
|
|
||||||
val addedMembers = members - group.members.map { it.serialize() }
|
|
||||||
if (!addedMembers.isEmpty()) { addMembers(groupPublicKey, addedMembers) }
|
|
||||||
// Remove members if needed
|
|
||||||
val removedMembers = group.members.map { it.serialize() } - members
|
|
||||||
if (removedMembers.isEmpty()) { removeMembers(groupPublicKey, removedMembers) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MessageSender.setName(groupPublicKey: String, newName: String) {
|
fun MessageSender.setName(groupPublicKey: String, newName: String) {
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
val context = MessagingModuleConfiguration.shared.context
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
@ -437,8 +437,23 @@ private fun MessageReceiver.handleClosedGroupControlMessage(message: ClosedGroup
|
|||||||
is ClosedGroupControlMessage.Kind.MembersRemoved -> handleClosedGroupMembersRemoved(message)
|
is ClosedGroupControlMessage.Kind.MembersRemoved -> handleClosedGroupMembersRemoved(message)
|
||||||
is ClosedGroupControlMessage.Kind.MemberLeft -> handleClosedGroupMemberLeft(message)
|
is ClosedGroupControlMessage.Kind.MemberLeft -> handleClosedGroupMemberLeft(message)
|
||||||
}
|
}
|
||||||
|
if (message.kind !is ClosedGroupControlMessage.Kind.New) {
|
||||||
|
// update the config
|
||||||
|
val closedGroupPublicKey = message.getPublicKey()
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
storage.updateGroupConfig(closedGroupPublicKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ClosedGroupControlMessage.getPublicKey(): String = kind!!.let { when (it) {
|
||||||
|
is ClosedGroupControlMessage.Kind.New -> it.publicKey.toByteArray().toHexString()
|
||||||
|
is ClosedGroupControlMessage.Kind.EncryptionKeyPair -> it.publicKey?.toByteArray()?.toHexString() ?: groupPublicKey!!
|
||||||
|
is ClosedGroupControlMessage.Kind.MemberLeft -> groupPublicKey!!
|
||||||
|
is ClosedGroupControlMessage.Kind.MembersAdded -> groupPublicKey!!
|
||||||
|
is ClosedGroupControlMessage.Kind.MembersRemoved -> groupPublicKey!!
|
||||||
|
is ClosedGroupControlMessage.Kind.NameChange -> groupPublicKey!!
|
||||||
|
}}
|
||||||
|
|
||||||
private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMessage) {
|
private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMessage) {
|
||||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.New ?: return
|
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.New ?: return
|
||||||
val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false)
|
val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false)
|
||||||
@ -476,6 +491,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
|
|||||||
storage.addClosedGroupPublicKey(groupPublicKey)
|
storage.addClosedGroupPublicKey(groupPublicKey)
|
||||||
// Store the encryption key pair
|
// Store the encryption key pair
|
||||||
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTimestamp)
|
storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey, sentTimestamp)
|
||||||
|
storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), formationTimestamp, encryptionKeyPair)
|
||||||
// Set expiration timer
|
// Set expiration timer
|
||||||
storage.setExpirationTimer(groupID, expireTimer)
|
storage.setExpirationTimer(groupID, expireTimer)
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.session.libsession.utilities
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
|
import network.loki.messenger.libsession_util.util.GroupInfo
|
||||||
import org.session.libsignal.messages.SignalServiceGroup
|
import org.session.libsignal.messages.SignalServiceGroup
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -102,4 +103,22 @@ object GroupUtil {
|
|||||||
fun doubleDecodeGroupId(groupID: String): String {
|
fun doubleDecodeGroupId(groupID: String): String {
|
||||||
return Hex.toStringCondensed(getDecodedGroupIDAsData(getDecodedGroupID(groupID)))
|
return Hex.toStringCondensed(getDecodedGroupIDAsData(getDecodedGroupID(groupID)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createConfigMemberMap(
|
||||||
|
members: Collection<String>,
|
||||||
|
admins: Collection<String>
|
||||||
|
): Map<String, Boolean> {
|
||||||
|
// Start with admins
|
||||||
|
val memberMap = admins.associate {
|
||||||
|
it to true
|
||||||
|
}.toMutableMap()
|
||||||
|
|
||||||
|
// Add the remaining members (there may be duplicates, so only add ones that aren't already in there from admins)
|
||||||
|
for (member in members) {
|
||||||
|
if (!memberMap.contains(member)) {
|
||||||
|
memberMap[member] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return memberMap
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user