From a8839af112aca42de4ba6d16ea5c828d98a29311 Mon Sep 17 00:00:00 2001 From: SessionHero01 <180888785+SessionHero01@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:32:05 +1100 Subject: [PATCH] Config update details and config syncing issue --- .../securesms/configs/ConfigToDatabaseSync.kt | 202 +++++++++++------- .../securesms/configs/ConfigUploader.kt | 70 +++--- .../securesms/database/Storage.kt | 18 +- .../securesms/dependencies/ConfigFactory.kt | 32 ++- .../onboarding/loading/LoadingViewModel.kt | 5 +- .../libsession/database/StorageProtocol.kt | 2 +- .../utilities/ConfigFactoryProtocol.kt | 10 +- 7 files changed, 200 insertions(+), 139 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt index cd05b15948..fc73b2cfe4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigToDatabaseSync.kt @@ -4,18 +4,21 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.supervisorScope import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_PINNED -import network.loki.messenger.libsession_util.ReadableContacts -import network.loki.messenger.libsession_util.ReadableConversationVolatileConfig import network.loki.messenger.libsession_util.ReadableGroupInfoConfig import network.loki.messenger.libsession_util.ReadableUserGroupsConfig import network.loki.messenger.libsession_util.ReadableUserProfile 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.ExpiryMode +import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.UserPic import network.loki.messenger.libsession_util.util.afterSend import org.session.libsession.database.StorageProtocol @@ -30,7 +33,6 @@ import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol.Companion.NAME_PADDED_LENGTH import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient @@ -45,7 +47,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.PollerFactory import org.thoughtcrime.securesms.groups.ClosedGroupManager import org.thoughtcrime.securesms.groups.OpenGroupManager -import org.thoughtcrime.securesms.util.asSequence +import org.thoughtcrime.securesms.sskenvironment.ProfileManager import javax.inject.Inject private const val TAG = "ConfigToDatabaseSync" @@ -66,6 +68,8 @@ class ConfigToDatabaseSync @Inject constructor( private val mmsDatabase: MmsDatabase, private val pollerFactory: PollerFactory, private val clock: SnodeClock, + private val profileManager: ProfileManager, + private val preferences: TextSecurePreferences, ) { private var job: Job? = null @@ -74,79 +78,98 @@ class ConfigToDatabaseSync @Inject constructor( @Suppress("OPT_IN_USAGE") job = GlobalScope.launch { - val groupMutex = hashMapOf() - val userMutex = Mutex() - - configFactory.configUpdateNotifications.collect { notification -> - when (notification) { - is ConfigUpdateNotification.UserConfigs -> { - launch { - userMutex.withLock { - syncUserConfigs() + supervisorScope { + val job1 = async { + configFactory.configUpdateNotifications + .filterIsInstance() + .debounce(800L) + .collect { config -> + try { + Log.i(TAG, "Start syncing user configs") + syncUserConfigs(config.timestamp) + Log.i(TAG, "Finished syncing user configs") + } catch (e: Exception) { + Log.e(TAG, "Error syncing user configs", e) } } - } - - is ConfigUpdateNotification.GroupConfigsUpdated -> { - val groupId = notification.groupId - val mutex = groupMutex.getOrPut(groupId) { Mutex() } - - launch { - mutex.withLock { - syncGroupConfigs(groupId) - } - } - } - - is ConfigUpdateNotification.GroupConfigsDeleted -> { - groupMutex.remove(notification.groupId) - } } + + val job2 = async { + configFactory.configUpdateNotifications + .filterIsInstance() + .collect { + syncGroupConfigs(it.groupId) + } + } + + job1.await() + job2.await() } } } private fun syncGroupConfigs(groupId: AccountId) { - configFactory.withGroupConfigs(groupId) { - updateGroup(it.groupInfo) + val info = configFactory.withGroupConfigs(groupId) { + UpdateGroupInfo(it.groupInfo) } + + updateGroup(info) } - private fun syncUserConfigs() { - val messageTimestamp = clock.currentTimeMills() + private fun syncUserConfigs(updateTimestamp: Long) { + lateinit var updateUserInfo: UpdateUserInfo + lateinit var updateUserGroupsInfo: UpdateUserGroupsInfo + lateinit var updateContacts: List + lateinit var updateConvoVolatile: List + configFactory.withUserConfigs { configs -> - updateUser(configs.userProfile, messageTimestamp) - updateUserGroups(configs.userGroups, messageTimestamp) - updateContacts(configs.contacts, messageTimestamp) - updateConvoVolatile(configs.convoInfoVolatile) + updateUserInfo = UpdateUserInfo(configs.userProfile) + updateUserGroupsInfo = UpdateUserGroupsInfo(configs.userGroups) + updateContacts = configs.contacts.all() + updateConvoVolatile = configs.convoInfoVolatile.all() } + + updateUser(updateUserInfo, updateTimestamp) + updateContacts(updateContacts, updateTimestamp) + updateUserGroups(updateUserGroupsInfo, updateTimestamp) + updateConvoVolatile(updateConvoVolatile) } - private fun updateUser(userProfile: ReadableUserProfile, messageTimestamp: Long) { + private data class UpdateUserInfo( + val name: String?, + val userPic: UserPic, + val ntsPriority: Long, + val ntsExpiry: ExpiryMode + ) { + constructor(profile: ReadableUserProfile) : this( + name = profile.getName(), + userPic = profile.getPic(), + ntsPriority = profile.getNtsPriority(), + ntsExpiry = profile.getNtsExpiry() + ) + } + + private fun updateUser(userProfile: UpdateUserInfo, messageTimestamp: Long) { val userPublicKey = storage.getUserPublicKey() ?: return // would love to get rid of recipient and context from this val recipient = Recipient.from(context, fromSerialized(userPublicKey), false) // Update profile name - val name = userProfile.getName() ?: return - val userPic = userProfile.getPic() - val profileManager = SSKEnvironment.shared.profileManager - - name.takeUnless { it.isEmpty() }?.truncate(NAME_PADDED_LENGTH)?.let { - TextSecurePreferences.setProfileName(context, it) + userProfile.name?.takeUnless { it.isEmpty() }?.truncate(NAME_PADDED_LENGTH)?.let { + preferences.setProfileName(it) profileManager.setName(context, recipient, it) } // Update profile picture - if (userPic == UserPic.DEFAULT) { - storage.clearUserPic() - } else if (userPic.key.isNotEmpty() && userPic.url.isNotEmpty() - && TextSecurePreferences.getProfilePictureURL(context) != userPic.url + if (userProfile.userPic == UserPic.DEFAULT) { + storage.clearUserPic(clearConfig = false) + } else if (userProfile.userPic.key.isNotEmpty() && userProfile.userPic.url.isNotEmpty() + && preferences.getProfilePictureURL() != userProfile.userPic.url ) { - storage.setUserProfilePicture(userPic.url, userPic.key) + storage.setUserProfilePicture(userProfile.userPic.url, userProfile.userPic.key) } - if (userProfile.getNtsPriority() == PRIORITY_HIDDEN) { + if (userProfile.ntsPriority == PRIORITY_HIDDEN) { // delete nts thread if needed val ourThread = storage.getThreadId(recipient) ?: return storage.deleteConversation(ourThread) @@ -157,53 +180,77 @@ class ConfigToDatabaseSync @Inject constructor( storage.setThreadDate(it, 0) } threadDatabase.setHasSent(ourThread, true) - storage.setPinned(ourThread, userProfile.getNtsPriority() > 0) + storage.setPinned(ourThread, userProfile.ntsPriority > 0) } // Set or reset the shared library to use latest expiration config storage.getThreadId(recipient)?.let { storage.setExpirationConfiguration( storage.getExpirationConfiguration(it)?.takeIf { it.updatedTimestampMs > messageTimestamp } ?: - ExpirationConfiguration(it, userProfile.getNtsExpiry(), messageTimestamp) + ExpirationConfiguration(it, userProfile.ntsExpiry, messageTimestamp) ) } } - private fun updateGroup(groupInfoConfig: ReadableGroupInfoConfig) { - val threadId = storage.getThreadId(fromSerialized(groupInfoConfig.id().hexString)) ?: return + private data class UpdateGroupInfo( + val id: AccountId, + val name: String?, + val deleteBefore: Long?, + val deleteAttachmentsBefore: Long? + ) { + constructor(groupInfoConfig: ReadableGroupInfoConfig) : this( + id = groupInfoConfig.id(), + name = groupInfoConfig.getName(), + deleteBefore = groupInfoConfig.getDeleteBefore(), + deleteAttachmentsBefore = groupInfoConfig.getDeleteAttachmentsBefore() + ) + } + + private fun updateGroup(groupInfoConfig: UpdateGroupInfo) { + val threadId = storage.getThreadId(fromSerialized(groupInfoConfig.id.hexString)) ?: return val recipient = storage.getRecipientForThread(threadId) ?: return - recipientDatabase.setProfileName(recipient, groupInfoConfig.getName()) - groupInfoConfig.getDeleteBefore()?.let { removeBefore -> + recipientDatabase.setProfileName(recipient, groupInfoConfig.name) + profileManager.setName(context, recipient, groupInfoConfig.name ?: "") + groupInfoConfig.deleteBefore?.let { removeBefore -> storage.trimThreadBefore(threadId, removeBefore) } - groupInfoConfig.getDeleteAttachmentsBefore()?.let { removeAttachmentsBefore -> + groupInfoConfig.deleteAttachmentsBefore?.let { removeAttachmentsBefore -> mmsDatabase.deleteMessagesInThreadBeforeDate(threadId, removeAttachmentsBefore, onlyMedia = true) } } - private fun updateContacts(contacts: ReadableContacts, messageTimestamp: Long) { - val extracted = contacts.all().toList() - storage.addLibSessionContacts(extracted, messageTimestamp) + private fun updateContacts(contacts: List, messageTimestamp: Long) { + storage.addLibSessionContacts(contacts, messageTimestamp) } - private fun updateUserGroups(userGroups: ReadableUserGroupsConfig, messageTimestamp: Long) { + private data class UpdateUserGroupsInfo( + val communityInfo: List, + val legacyGroupInfo: List, + val closedGroupInfo: List + ) { + constructor(userGroups: ReadableUserGroupsConfig) : this( + communityInfo = userGroups.allCommunityInfo(), + legacyGroupInfo = userGroups.allLegacyGroupInfo(), + closedGroupInfo = userGroups.allClosedGroupInfo() + ) + } + + private fun updateUserGroups(userGroups: UpdateUserGroupsInfo, messageTimestamp: Long) { val localUserPublicKey = storage.getUserPublicKey() ?: return Log.w( - "Loki", + TAG, "No user public key when trying to update user groups from config" ) - val communities = userGroups.allCommunityInfo() - val lgc = userGroups.allLegacyGroupInfo() val allOpenGroups = storage.getAllOpenGroups() val toDeleteCommunities = allOpenGroups.filter { - Conversation.Community(BaseCommunityInfo(it.value.server, it.value.room, it.value.publicKey), 0, false).baseCommunityInfo.fullUrl() !in communities.map { it.community.fullUrl() } + Conversation.Community(BaseCommunityInfo(it.value.server, it.value.room, it.value.publicKey), 0, false).baseCommunityInfo.fullUrl() !in userGroups.communityInfo.map { it.community.fullUrl() } } val existingCommunities: Map = allOpenGroups.filterKeys { it !in toDeleteCommunities.keys } - val toAddCommunities = communities.filter { it.community.fullUrl() !in existingCommunities.map { it.value.joinURL } } + val toAddCommunities = userGroups.communityInfo.filter { it.community.fullUrl() !in existingCommunities.map { it.value.joinURL } } val existingJoinUrls = existingCommunities.values.map { it.joinURL } val existingLegacyClosedGroups = storage.getAllGroups(includeInactive = true).filter { it.isLegacyClosedGroup } - val lgcIds = lgc.map { it.accountId } + val lgcIds = userGroups.legacyGroupInfo.map { it.accountId } val toDeleteLegacyClosedGroups = existingLegacyClosedGroups.filter { group -> GroupUtil.doubleDecodeGroupId(group.encodedId) !in lgcIds } @@ -228,7 +275,7 @@ class ConfigToDatabaseSync @Inject constructor( } } - for (groupInfo in communities) { + for (groupInfo in userGroups.communityInfo) { val groupBaseCommunity = groupInfo.community if (groupBaseCommunity.fullUrl() in existingJoinUrls) { // add it @@ -237,7 +284,6 @@ class ConfigToDatabaseSync @Inject constructor( } } - val newClosedGroups = userGroups.allClosedGroupInfo() val existingClosedGroupThreads: Map = threadDatabase.readerFor(threadDatabase.conversationList).use { reader -> buildMap(reader.count) { var current = reader.next @@ -253,7 +299,7 @@ class ConfigToDatabaseSync @Inject constructor( val groupThreadsToKeep = hashMapOf() - for (closedGroup in newClosedGroups) { + for (closedGroup in userGroups.closedGroupInfo) { val recipient = Recipient.from(context, fromSerialized(closedGroup.groupAccountId.hexString), false) storage.setRecipientApprovedMe(recipient, true) storage.setRecipientApproved(recipient, !closedGroup.invited) @@ -273,7 +319,7 @@ class ConfigToDatabaseSync @Inject constructor( storage.removeClosedGroupThread(threadId) } - for (group in lgc) { + for (group in userGroups.legacyGroupInfo) { val groupId = GroupUtil.doubleEncodeGroupID(group.accountId) val existingGroup = existingLegacyClosedGroups.firstOrNull { GroupUtil.doubleDecodeGroupId(it.encodedId) == group.accountId } val existingThread = existingGroup?.let { storage.getThreadId(existingGroup.encodedId) } @@ -282,9 +328,9 @@ class ConfigToDatabaseSync @Inject constructor( ClosedGroupManager.silentlyRemoveGroup(context,existingThread, GroupUtil.doubleDecodeGroupId(existingGroup.encodedId), existingGroup.encodedId, localUserPublicKey, delete = true) } else if (existingThread == null) { - Log.w("Loki-DBG", "Existing group had no thread to hide") + Log.w(TAG, "Existing group had no thread to hide") } else { - Log.d("Loki-DBG", "Setting existing group pinned status to ${group.priority}") + Log.d(TAG, "Setting existing group pinned status to ${group.priority}") threadDatabase.setPinned(existingThread, group.priority == PRIORITY_PINNED) } } else { @@ -322,8 +368,8 @@ class ConfigToDatabaseSync @Inject constructor( } } - private fun updateConvoVolatile(convos: ReadableConversationVolatileConfig) { - val extracted = convos.all().filterNotNull() + private fun updateConvoVolatile(convos: List) { + val extracted = convos.filterNotNull() for (conversation in extracted) { val threadId = when (conversation) { is Conversation.OneToOne -> storage.getThreadIdFor(conversation.accountId, null, null, createThread = false) @@ -334,8 +380,8 @@ class ConfigToDatabaseSync @Inject constructor( if (threadId != null) { if (conversation.lastRead > storage.getLastSeen(threadId)) { storage.markConversationAsRead(threadId, conversation.lastRead, force = true) + storage.updateThread(threadId, false) } - storage.updateThread(threadId, false) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt index e794bb7f1d..26b16c031e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt @@ -1,19 +1,22 @@ package org.thoughtcrime.securesms.configs import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.supervisorScope import network.loki.messenger.libsession_util.util.ConfigPush import org.session.libsession.database.StorageProtocol import org.session.libsession.database.userAuth import org.session.libsession.snode.OwnedSwarmAuth import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.SnodeClock import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SwarmAuth import org.session.libsession.snode.model.StoreMessageResponse @@ -47,52 +50,49 @@ private const val TAG = "ConfigUploader" class ConfigUploader @Inject constructor( private val configFactory: ConfigFactoryProtocol, private val storageProtocol: StorageProtocol, + private val clock: SnodeClock, ) { private var job: Job? = null - @OptIn(DelicateCoroutinesApi::class) + @OptIn(DelicateCoroutinesApi::class, FlowPreview::class) fun start() { require(job == null) { "Already started" } job = GlobalScope.launch { - val groupMutex = hashMapOf() - val userMutex = Mutex() - - configFactory.configUpdateNotifications - .collect { changes -> - when (changes) { - is ConfigUpdateNotification.GroupConfigsDeleted -> { - groupMutex.remove(changes.groupId) - } - - is ConfigUpdateNotification.GroupConfigsUpdated -> { - // Group config pushing is limited to its own dispatcher - launch { - try { - retryWithUniformInterval { - groupMutex.getOrPut(changes.groupId) { Mutex() }.withLock { - pushGroupConfigsChangesIfNeeded(changes.groupId) - } - } - } catch (e: Exception) { - Log.e(TAG, "Failed to push group configs", e) - } - } - } - - ConfigUpdateNotification.UserConfigs -> launch { + supervisorScope { + val job1 = launch { + configFactory.configUpdateNotifications + .filterIsInstance() + .debounce(1000L) + .collect { try { retryWithUniformInterval { - userMutex.withLock { - pushUserConfigChangesIfNeeded() - } + pushUserConfigChangesIfNeeded() } } catch (e: Exception) { Log.e(TAG, "Failed to push user configs", e) } } - } } + + val job2 = launch { + configFactory.configUpdateNotifications + .filterIsInstance() + .debounce(1000L) + .collect { changes -> + try { + retryWithUniformInterval { + pushGroupConfigsChangesIfNeeded(changes.groupId) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to push group configs", e) + } + } + } + + job1.join() + job2.join() + } } } @@ -160,7 +160,7 @@ class ConfigUploader @Inject constructor( auth.accountId.hexString, Base64.encodeBytes(keysPush), SnodeMessage.CONFIG_TTL, - SnodeAPI.nowWithOffset, + clock.currentTimeMills(), ), auth ), @@ -203,7 +203,7 @@ class ConfigUploader @Inject constructor( auth.accountId.hexString, Base64.encodeBytes(push.config), SnodeMessage.CONFIG_TTL, - SnodeAPI.nowWithOffset, + clock.currentTimeMills(), ), auth, ), 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 581b436e5b..ba980a23cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -257,8 +257,8 @@ open class Storage @Inject constructor( Recipient.from(context, it, false) } ourRecipient.resolve().profileKey = newProfileKey - TextSecurePreferences.setProfileKey(context, newProfileKey?.let { Base64.encodeBytes(it) }) - TextSecurePreferences.setProfilePictureURL(context, newProfilePicture) + preferences.setProfileKey(newProfileKey?.let { Base64.encodeBytes(it) }) + preferences.setProfilePictureURL(newProfilePicture) if (newProfileKey != null) { JobQueue.shared.add(RetrieveProfileAvatarJob(newProfilePicture, ourRecipient.address)) @@ -533,20 +533,22 @@ open class Storage @Inject constructor( return configFactory.withUserConfigs { it.userProfile.getCommunityMessageRequests() } } - override fun clearUserPic() { + override fun clearUserPic(clearConfig: Boolean) { val userPublicKey = getUserPublicKey() ?: return Log.w(TAG, "No user public key when trying to clear user pic") val recipient = Recipient.from(context, fromSerialized(userPublicKey), false) // Clear details related to the user's profile picture - TextSecurePreferences.setProfileKey(context, null) + preferences.setProfileKey(null) ProfileKeyUtil.setEncodedProfileKey(context, null) recipientDatabase.setProfileAvatar(recipient, null) - TextSecurePreferences.setProfileAvatarId(context, 0) - TextSecurePreferences.setProfilePictureURL(context, null) + preferences.setProfileAvatarId(0) + preferences.setProfilePictureURL(null) Recipient.removeCached(fromSerialized(userPublicKey)) - configFactory.withMutableUserConfigs { - it.userProfile.setPic(UserPic.DEFAULT) + if (clearConfig) { + configFactory.withMutableUserConfigs { + it.userProfile.setPic(UserPic.DEFAULT) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt index 98179f43f5..27a53b0f2d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -142,14 +142,14 @@ class ConfigFactory @Inject constructor( * * @param cb A function that takes a [UserConfigsImpl] and returns a pair of the result of the operation and a boolean indicating if the configs were changed. */ - private fun doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair): T { + private fun doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair): T { val (lock, configs) = ensureUserConfigsInitialized() val (result, changed) = lock.write { cb(configs) } - if (changed) { - _configUpdateNotifications.tryEmit(ConfigUpdateNotification.UserConfigs) + if (changed != null) { + _configUpdateNotifications.tryEmit(changed) } return result @@ -171,13 +171,25 @@ class ConfigFactory @Inject constructor( UserConfigType.USER_GROUPS -> configs.userGroups } - Unit to config.merge(messages.map { it.hash to it.data }.toTypedArray()).isNotEmpty() + val maxTimestamp = config.merge(messages.map { it.hash to it.data }.toTypedArray()) + .asSequence() + .mapNotNull { hash -> messages.firstOrNull { it.hash == hash } } + .maxOfOrNull { it.timestamp } + + Unit to maxTimestamp?.let(ConfigUpdateNotification::UserConfigsMerged) } } override fun withMutableUserConfigs(cb: (MutableUserConfigs) -> T): T { return doWithMutableUserConfigs { - cb(it) to it.persistIfDirty(clock) + val result = cb(it) + val changed = if (it.persistIfDirty(clock)) { + ConfigUpdateNotification.UserConfigsModified + } else { + null + } + + result to changed } } @@ -229,10 +241,6 @@ class ConfigFactory @Inject constructor( it.convoInfoVolatile.eraseClosedGroup(groupId.hexString) } - if (groupConfigs.remove(groupId) != null) { - _configUpdateNotifications.tryEmit(ConfigUpdateNotification.GroupConfigsDeleted(groupId)) - } - configDatabase.deleteGroupConfigs(groupId) } @@ -291,7 +299,9 @@ class ConfigFactory @Inject constructor( convoInfoVolatile?.let { (push, result) -> configs.convoInfoVolatile.confirmPushed(push.seqNo, result.hash) } userGroups?.let { (push, result) -> configs.userGroups.confirmPushed(push.seqNo, result.hash) } - Unit to configs.persistIfDirty(clock) + configs.persistIfDirty(clock) + + Unit to null } } @@ -385,7 +395,7 @@ class ConfigFactory @Inject constructor( } fun clearAll() { - //TODO: clear all configsr + //TODO: clear all configs } private class GroupSubAccountSwarmAuth( diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loading/LoadingViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loading/LoadingViewModel.kt index c612a50240..f5aed46bb9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/loading/LoadingViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/loading/LoadingViewModel.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow @@ -73,8 +74,8 @@ internal class LoadingViewModel @Inject constructor( viewModelScope.launch { try { configFactory.configUpdateNotifications - .filter { it == ConfigUpdateNotification.UserConfigs } - .onStart { emit(ConfigUpdateNotification.UserConfigs) } + .filterIsInstance() + .onStart { emit(ConfigUpdateNotification.UserConfigsModified) } .filter { configFactory.withUserConfigs { configs -> !configs.userProfile.getName().isNullOrEmpty() 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 87e2a2bf8c..a12869c016 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -55,7 +55,7 @@ interface StorageProtocol { fun setProfilePicture(recipient: Recipient, newProfilePicture: String?, newProfileKey: ByteArray?) fun setBlocksCommunityMessageRequests(recipient: Recipient, blocksMessageRequests: Boolean) fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) - fun clearUserPic() + fun clearUserPic(clearConfig: Boolean = true) // Signal fun getOrGenerateRegistrationID(): Int diff --git a/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt b/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt index ff4cf45f53..d2a54f30d6 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/ConfigFactoryProtocol.kt @@ -115,8 +115,8 @@ suspend fun ConfigFactoryProtocol.waitUntilUserConfigsPushed(timeoutMills: Long return withTimeoutOrNull(timeoutMills){ configUpdateNotifications - .onStart { emit(ConfigUpdateNotification.UserConfigs) } // Trigger the filtering immediately - .filter { it == ConfigUpdateNotification.UserConfigs && !needsPush() } + .onStart { emit(ConfigUpdateNotification.UserConfigsModified) } // Trigger the filtering immediately + .filter { it == ConfigUpdateNotification.UserConfigsModified && !needsPush() } .first() } != null } @@ -190,8 +190,10 @@ interface MutableGroupConfigs : GroupConfigs { fun rekey() } + sealed interface ConfigUpdateNotification { - data object UserConfigs : ConfigUpdateNotification + data object UserConfigsModified : ConfigUpdateNotification + data class UserConfigsMerged(val timestamp: Long) : ConfigUpdateNotification + data class GroupConfigsUpdated(val groupId: AccountId) : ConfigUpdateNotification - data class GroupConfigsDeleted(val groupId: AccountId) : ConfigUpdateNotification }