fix: add some more contact syncing: nicknames, approved statuses, blocked statuses

This commit is contained in:
0x330a 2023-02-24 17:05:33 +11:00
parent b3ae2e967f
commit c36387175d
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
9 changed files with 52 additions and 37 deletions

View File

@ -7,6 +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.UserProfile import network.loki.messenger.libsession_util.UserProfile
import network.loki.messenger.libsession_util.util.Conversation import network.loki.messenger.libsession_util.util.Conversation
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
@ -318,7 +319,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
} }
// update pfp // update pfp
if (userPic == null) { if (userPic == UserPic.DEFAULT) {
// clear picture if userPic is null // clear picture if userPic is null
TextSecurePreferences.setProfileKey(context, null) TextSecurePreferences.setProfileKey(context, null)
ProfileKeyUtil.setEncodedProfileKey(context, null) ProfileKeyUtil.setEncodedProfileKey(context, null)
@ -335,10 +336,23 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
private fun updateContacts(contacts: Contacts) { private fun updateContacts(contacts: Contacts) {
val extracted = contacts.all().toList() val extracted = contacts.all().toList()
val profileManager = SSKEnvironment.shared.profileManager
extracted.forEach { contact -> extracted.forEach { contact ->
val address = fromSerialized(contact.id) val address = fromSerialized(contact.id)
val recipient = Recipient.from(context, address, false) val recipient = Recipient.from(context, address, false)
setBlocked(listOf(recipient), contact.blocked) setBlocked(listOf(recipient), contact.blocked, fromConfigUpdate = true)
setRecipientApproved(recipient, contact.approved)
setRecipientApprovedMe(recipient, contact.approvedMe)
profileManager.setName(context, recipient, contact.name)
profileManager.setNickname(context, recipient, contact.nickname)
if (contact.profilePicture != UserPic.DEFAULT) {
val (url, key) = contact.profilePicture
if (key.size != ProfileKeyUtil.PROFILE_KEY_BYTES) return@forEach
profileManager.setProfileKey(context, recipient, key)
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
profileManager.setProfilePictureURL(context, recipient, url)
}
Log.d("Loki-DBG", "Updated contact $contact")
} }
} }
@ -356,7 +370,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
getOrCreateThreadIdFor("",null, it) getOrCreateThreadIdFor("",null, it)
} }
} }
Log.d("Loki-DBG", "Should update thread $threadId")
if (threadId >= 0) { if (threadId >= 0) {
markConversationAsRead(threadId, conversation.lastRead) markConversationAsRead(threadId, conversation.lastRead)
updateThread(threadId, false) updateThread(threadId, false)
@ -761,6 +774,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setContact(contact: Contact) { override fun setContact(contact: Contact) {
DatabaseComponent.get(context).sessionContactDatabase().setContact(contact) DatabaseComponent.get(context).sessionContactDatabase().setContact(contact)
SSKEnvironment.shared.profileManager.contactUpdatedInternal(contact)
} }
override fun getRecipientForThread(threadId: Long): Recipient? { override fun getRecipientForThread(threadId: Long): Recipient? {
@ -823,7 +837,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
threadDatabase.setHasSent(threadId, true) threadDatabase.setHasSent(threadId, true)
} }
if (contact.isBlocked == true) { if (contact.isBlocked == true) {
setBlocked(listOf(recipient), true) setBlocked(listOf(recipient), true, fromConfigUpdate = true)
threadDatabase.deleteConversation(threadId) threadDatabase.deleteConversation(threadId)
} }
} }
@ -1125,16 +1139,17 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms)) DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms))
} }
override fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean) { override fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean, fromConfigUpdate: Boolean) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase() val recipientDb = DatabaseComponent.get(context).recipientDatabase()
recipientDb.setBlocked(recipients, isBlocked) recipientDb.setBlocked(recipients, isBlocked)
recipients.filter { it.isContactRecipient }.forEach { recipient -> recipients.filter { it.isContactRecipient }.forEach { recipient ->
configFactory.contacts?.upsertContact(recipient.address.serialize()) { configFactory.contacts?.upsertContact(recipient.address.serialize()) {
this.blocked = true this.blocked = isBlocked
} }
} }
val contactsConfig = configFactory.contacts ?: return val contactsConfig = configFactory.contacts ?: return
if (contactsConfig.needsDump()) { if (contactsConfig.needsPush() && !fromConfigUpdate) {
Log.d("Loki-DBG", "Needs to push contacts after blocking ${recipients.map { it.toShortString() }}")
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
} }
} }

View File

@ -25,7 +25,6 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.UiModeUtilities import org.thoughtcrime.securesms.util.UiModeUtilities
import javax.inject.Inject import javax.inject.Inject
@ -131,10 +130,10 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() {
newNickName = nicknameEditText.text.toString() newNickName = nicknameEditText.text.toString()
} }
val publicKey = recipient.address.serialize() val publicKey = recipient.address.serialize()
val contactDB = DatabaseComponent.get(requireContext()).sessionContactDatabase() val storage = MessagingModuleConfiguration.shared.storage
val contact = contactDB.getContactWithSessionID(publicKey) ?: Contact(publicKey) val contact = storage.getContactWithSessionID(publicKey) ?: Contact(publicKey)
contact.nickname = newNickName contact.nickname = newNickName
contactDB.setContact(contact) storage.setContact(contact)
nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally
} }

View File

@ -82,7 +82,7 @@ class ProfileManager(private val context: Context, private val configFactory: Co
database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode) database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode)
} }
private fun contactUpdatedInternal(contact: Contact) { override fun contactUpdatedInternal(contact: Contact) {
val contactConfig = configFactory.contacts ?: return val contactConfig = configFactory.contacts ?: return
contactConfig.upsertContact(contact.sessionID) { contactConfig.upsertContact(contact.sessionID) {
this.name = contact.name.orEmpty() this.name = contact.name.orEmpty()
@ -93,7 +93,7 @@ class ProfileManager(private val context: Context, private val configFactory: Co
this.profilePicture = UserPic(url, key) this.profilePicture = UserPic(url, key)
} }
} }
if (contactConfig.needsDump()) { if (contactConfig.needsPush()) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
} }
} }

View File

@ -70,7 +70,10 @@ object ConfigurationMessageUtilities {
// don't schedule job if we already have one // don't schedule job if we already have one
val ourDestination = Destination.Contact(userPublicKey) val ourDestination = Destination.Contact(userPublicKey)
val currentStorageJob = storage.getConfigSyncJob(ourDestination) val currentStorageJob = storage.getConfigSyncJob(ourDestination)
if (currentStorageJob != null && !(currentStorageJob as ConfigurationSyncJob).isRunning.get()) return Promise.ofFail(NullPointerException("A job is already pending or in progress, don't schedule another job")) if (currentStorageJob != null) {
(currentStorageJob as ConfigurationSyncJob).shouldRunAgain.set(true)
return Promise.ofFail(NullPointerException("A job is already pending or in progress, don't schedule another job"))
}
val newConfigSync = ConfigurationSyncJob(ourDestination) val newConfigSync = ConfigurationSyncJob(ourDestination)
Log.d("Loki", "Scheduling new ConfigurationSyncJob") Log.d("Loki", "Scheduling new ConfigurationSyncJob")
JobQueue.shared.add(newConfigSync) JobQueue.shared.add(newConfigSync)

View File

@ -204,7 +204,7 @@ interface StorageProtocol {
fun removeReaction(emoji: String, messageTimestamp: Long, author: String, notifyUnread: Boolean) fun removeReaction(emoji: String, messageTimestamp: Long, author: String, notifyUnread: Boolean)
fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long) fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long)
fun deleteReactions(messageId: Long, mms: Boolean) fun deleteReactions(messageId: Long, mms: Boolean)
fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean) fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean, fromConfigUpdate: Boolean = false)
fun blockedContacts(): List<Recipient> fun blockedContacts(): List<Recipient>
// Shared configs // Shared configs

View File

@ -21,18 +21,12 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
override var failureCount: Int = 0 override var failureCount: Int = 0
override val maxFailureCount: Int = 1 override val maxFailureCount: Int = 1
val isRunning = AtomicBoolean(false) val shouldRunAgain = AtomicBoolean(false)
suspend fun wrap(body: suspend ()->Unit) { override suspend fun execute(dispatcherName: String) {
isRunning.set(true) val storage = MessagingModuleConfiguration.shared.storage
body()
isRunning.set(false)
}
override suspend fun execute(dispatcherName: String) = wrap {
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() val userPublicKey = storage.getUserPublicKey()
val delegate = delegate val delegate = delegate
if (destination is Destination.ClosedGroup // TODO: closed group configs will be handled in closed group feature if (destination is Destination.ClosedGroup // TODO: closed group configs will be handled in closed group feature
// if we haven't enabled the new configs don't run // if we haven't enabled the new configs don't run
@ -47,7 +41,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
|| (destination is Destination.Contact && destination.publicKey != userPublicKey) || (destination is Destination.Contact && destination.publicKey != userPublicKey)
) { ) {
Log.w(TAG, "No need to run config sync job, TODO") Log.w(TAG, "No need to run config sync job, TODO")
return@wrap delegate?.handleJobSucceeded(this, dispatcherName) ?: Unit return delegate?.handleJobSucceeded(this, dispatcherName) ?: Unit
} }
// configFactory singleton instance will come in handy for modifying hashes and fetching configs for namespace etc // configFactory singleton instance will come in handy for modifying hashes and fetching configs for namespace etc
@ -61,7 +55,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
).filter { config -> config.needsPush() } ).filter { config -> config.needsPush() }
// don't run anything if we don't need to push anything // don't run anything if we don't need to push anything
if (configsRequiringPush.isEmpty()) return@wrap delegate.handleJobSucceeded(this, dispatcherName) if (configsRequiringPush.isEmpty()) return delegate.handleJobSucceeded(this, dispatcherName)
// allow null results here so the list index matches configsRequiringPush // allow null results here so the list index matches configsRequiringPush
val batchObjects: List<Pair<SharedConfigurationMessage, SnodeAPI.SnodeBatchRequestInfo>?> = configsRequiringPush.map { config -> val batchObjects: List<Pair<SharedConfigurationMessage, SnodeAPI.SnodeBatchRequestInfo>?> = configsRequiringPush.map { config ->
@ -88,7 +82,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
if (batchObjects.any { it == null }) { if (batchObjects.any { it == null }) {
// stop running here, something like a signing error occurred // stop running here, something like a signing error occurred
return@wrap delegate.handleJobFailedPermanently(this, dispatcherName, NullPointerException("One or more requests had a null batch request info")) return delegate.handleJobFailedPermanently(this, dispatcherName, NullPointerException("One or more requests had a null batch request info"))
} }
val allRequests = mutableListOf<SnodeAPI.SnodeBatchRequestInfo>() val allRequests = mutableListOf<SnodeAPI.SnodeBatchRequestInfo>()
@ -156,9 +150,13 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error performing batch request", e) Log.e(TAG, "Error performing batch request", e)
return@wrap delegate.handleJobFailedPermanently(this, dispatcherName, e) return delegate.handleJobFailedPermanently(this, dispatcherName, e)
} }
delegate.handleJobSucceeded(this, dispatcherName) delegate.handleJobSucceeded(this, dispatcherName)
if (shouldRunAgain.get() && storage.getConfigSyncJob(destination) == null) {
// reschedule if something has updated since we started this job
JobQueue.shared.add(ConfigurationSyncJob(destination))
}
} }
fun Destination.destinationPublicKey(): String = when (this) { fun Destination.destinationPublicKey(): String = when (this) {

View File

@ -43,7 +43,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
var isCaughtUp = false var isCaughtUp = false
var configPollingJob: Job? = null var configPollingJob: Job? = null
val configDebouncer = WindowDebouncer(3000, debounceTimer) private val configDebouncer = WindowDebouncer(3000, debounceTimer)
// region Settings // region Settings
companion object { companion object {
@ -144,9 +144,6 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
return return
} }
Log.d("Loki-DBG", "Received configs with hashes: ${messages.map { it.second }}")
Log.d("Loki-DBG", "Hashes we have for config: ${configFactory.getHashesFor(forConfigObject)}")
messages.forEach { (envelope, hash) -> messages.forEach { (envelope, hash) ->
try { try {
val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(), openGroupServerID = null) val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(), openGroupServerID = null)

View File

@ -1,23 +1,24 @@
package org.session.libsession.utilities; package org.session.libsession.utilities;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsignal.utilities.Base64; import org.session.libsignal.utilities.Base64;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import java.io.IOException; import java.io.IOException;
public class ProfileKeyUtil { public class ProfileKeyUtil {
public static final int PROFILE_KEY_BYTES = 32;
public static synchronized @NonNull byte[] getProfileKey(@NonNull Context context) { public static synchronized @NonNull byte[] getProfileKey(@NonNull Context context) {
try { try {
String encodedProfileKey = TextSecurePreferences.getProfileKey(context); String encodedProfileKey = TextSecurePreferences.getProfileKey(context);
if (encodedProfileKey == null) { if (encodedProfileKey == null) {
encodedProfileKey = Util.getSecret(32); encodedProfileKey = Util.getSecret(PROFILE_KEY_BYTES);
TextSecurePreferences.setProfileKey(context, encodedProfileKey); TextSecurePreferences.setProfileKey(context, encodedProfileKey);
} }
@ -36,7 +37,7 @@ public class ProfileKeyUtil {
} }
public static synchronized @NonNull String generateEncodedProfileKey(@NonNull Context context) { public static synchronized @NonNull String generateEncodedProfileKey(@NonNull Context context) {
return Util.getSecret(32); return Util.getSecret(PROFILE_KEY_BYTES);
} }
public static synchronized void setEncodedProfileKey(@NonNull Context context, @Nullable String key) { public static synchronized void setEncodedProfileKey(@NonNull Context context, @Nullable String key) {

View File

@ -1,6 +1,7 @@
package org.session.libsession.utilities package org.session.libsession.utilities
import android.content.Context import android.content.Context
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
@ -33,6 +34,7 @@ class SSKEnvironment(
fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String)
fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray?) fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray?)
fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
fun contactUpdatedInternal(contact: Contact)
} }
interface MessageExpirationManagerProtocol { interface MessageExpirationManagerProtocol {