Config update details and config syncing issue

This commit is contained in:
SessionHero01 2024-10-09 10:32:05 +11:00
parent db8784f0db
commit a8839af112
No known key found for this signature in database
7 changed files with 200 additions and 139 deletions

View File

@ -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<AccountId, Mutex>()
val userMutex = Mutex()
configFactory.configUpdateNotifications.collect { notification ->
when (notification) {
is ConfigUpdateNotification.UserConfigs -> {
launch {
userMutex.withLock {
syncUserConfigs()
supervisorScope {
val job1 = async {
configFactory.configUpdateNotifications
.filterIsInstance<ConfigUpdateNotification.UserConfigsMerged>()
.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)
}
val job2 = async {
configFactory.configUpdateNotifications
.filterIsInstance<ConfigUpdateNotification.GroupConfigsUpdated>()
.collect {
syncGroupConfigs(it.groupId)
}
}
is ConfigUpdateNotification.GroupConfigsDeleted -> {
groupMutex.remove(notification.groupId)
}
}
job1.await()
job2.await()
}
}
}
private fun syncGroupConfigs(groupId: AccountId) {
configFactory.withGroupConfigs(groupId) {
updateGroup(it.groupInfo)
}
val info = configFactory.withGroupConfigs(groupId) {
UpdateGroupInfo(it.groupInfo)
}
private fun syncUserConfigs() {
val messageTimestamp = clock.currentTimeMills()
updateGroup(info)
}
private fun syncUserConfigs(updateTimestamp: Long) {
lateinit var updateUserInfo: UpdateUserInfo
lateinit var updateUserGroupsInfo: UpdateUserGroupsInfo
lateinit var updateContacts: List<Contact>
lateinit var updateConvoVolatile: List<Conversation?>
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()
}
private fun updateUser(userProfile: ReadableUserProfile, messageTimestamp: Long) {
updateUser(updateUserInfo, updateTimestamp)
updateContacts(updateContacts, updateTimestamp)
updateUserGroups(updateUserGroupsInfo, updateTimestamp)
updateConvoVolatile(updateConvoVolatile)
}
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<Contact>, messageTimestamp: Long) {
storage.addLibSessionContacts(contacts, messageTimestamp)
}
private fun updateUserGroups(userGroups: ReadableUserGroupsConfig, messageTimestamp: Long) {
private data class UpdateUserGroupsInfo(
val communityInfo: List<GroupInfo.CommunityGroupInfo>,
val legacyGroupInfo: List<GroupInfo.LegacyGroupInfo>,
val closedGroupInfo: List<GroupInfo.ClosedGroupInfo>
) {
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<Long, OpenGroup> = 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<AccountId, Long> = threadDatabase.readerFor(threadDatabase.conversationList).use { reader ->
buildMap(reader.count) {
var current = reader.next
@ -253,7 +299,7 @@ class ConfigToDatabaseSync @Inject constructor(
val groupThreadsToKeep = hashMapOf<AccountId, Long>()
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<Conversation?>) {
val extracted = convos.filterNotNull()
for (conversation in extracted) {
val threadId = when (conversation) {
is Conversation.OneToOne -> storage.getThreadIdFor(conversation.accountId, null, null, createThread = false)
@ -334,12 +380,12 @@ class ConfigToDatabaseSync @Inject constructor(
if (threadId != null) {
if (conversation.lastRead > storage.getLastSeen(threadId)) {
storage.markConversationAsRead(threadId, conversation.lastRead, force = true)
}
storage.updateThread(threadId, false)
}
}
}
}
}
/**
* Truncate a string to a specified number of bytes

View File

@ -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,32 +50,39 @@ 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<AccountId, Mutex>()
val userMutex = Mutex()
supervisorScope {
val job1 = launch {
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 {
.filterIsInstance<ConfigUpdateNotification.UserConfigsModified>()
.debounce(1000L)
.collect {
try {
retryWithUniformInterval {
groupMutex.getOrPut(changes.groupId) { Mutex() }.withLock {
pushGroupConfigsChangesIfNeeded(changes.groupId)
pushUserConfigChangesIfNeeded()
}
} catch (e: Exception) {
Log.e(TAG, "Failed to push user configs", e)
}
}
}
val job2 = launch {
configFactory.configUpdateNotifications
.filterIsInstance<ConfigUpdateNotification.GroupConfigsUpdated>()
.debounce(1000L)
.collect { changes ->
try {
retryWithUniformInterval {
pushGroupConfigsChangesIfNeeded(changes.groupId)
}
} catch (e: Exception) {
Log.e(TAG, "Failed to push group configs", e)
@ -80,18 +90,8 @@ class ConfigUploader @Inject constructor(
}
}
ConfigUpdateNotification.UserConfigs -> launch {
try {
retryWithUniformInterval {
userMutex.withLock {
pushUserConfigChangesIfNeeded()
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to push user 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,
),

View File

@ -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,22 +533,24 @@ 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))
if (clearConfig) {
configFactory.withMutableUserConfigs {
it.userProfile.setPic(UserPic.DEFAULT)
}
}
}
override fun setAuthToken(room: String, server: String, newValue: String) {
val id = "$server.$room"

View File

@ -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 <T> doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair<T, Boolean>): T {
private fun <T> doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair<T, ConfigUpdateNotification?>): 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 <T> 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(

View File

@ -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<ConfigUpdateNotification.UserConfigsModified>()
.onStart { emit(ConfigUpdateNotification.UserConfigsModified) }
.filter {
configFactory.withUserConfigs { configs ->
!configs.userProfile.getName().isNullOrEmpty()

View File

@ -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

View File

@ -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
}