feat: syncs the user profile stuff for now, and errors back to placeholder instead of unknown recipient

This commit is contained in:
0x330a 2023-02-13 16:43:45 +11:00
parent fb21f58cbd
commit 03a343d832
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
8 changed files with 77 additions and 33 deletions

View File

@ -108,12 +108,14 @@ class ProfilePictureView @JvmOverloads constructor(
val signalProfilePicture = recipient.contactPhoto val signalProfilePicture = recipient.contactPhoto
val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject
val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
if (signalProfilePicture != null && avatar != "0" && avatar != "") { if (signalProfilePicture != null && avatar != "0" && avatar != "") {
glide.clear(imageView) glide.clear(imageView)
glide.load(signalProfilePicture) glide.load(signalProfilePicture)
.placeholder(unknownRecipientDrawable) .placeholder(unknownRecipientDrawable)
.centerCrop() .centerCrop()
.error(unknownRecipientDrawable) .error(glide.load(placeholder))
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop() .circleCrop()
.into(imageView) .into(imageView)
@ -121,8 +123,6 @@ class ProfilePictureView @JvmOverloads constructor(
glide.clear(imageView) glide.clear(imageView)
imageView.setImageDrawable(unknownOpenGroupDrawable) imageView.setImageDrawable(unknownOpenGroupDrawable)
} else { } else {
val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}")
glide.clear(imageView) glide.clear(imageView)
glide.load(placeholder) glide.load(placeholder)
.placeholder(unknownRecipientDrawable) .placeholder(unknownRecipientDrawable)

View File

@ -126,8 +126,6 @@ class ConfigFactory(private val context: Context,
} }
override fun notifyUpdates(forConfigObject: ConfigBase) { override fun notifyUpdates(forConfigObject: ConfigBase) {
if (!forConfigObject.needsDump()) return
when (forConfigObject) { when (forConfigObject) {
is UserProfile -> updateUser(forConfigObject) is UserProfile -> updateUser(forConfigObject)
is Contacts -> updateContacts(forConfigObject) is Contacts -> updateContacts(forConfigObject)

View File

@ -20,9 +20,11 @@ import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.BuildConfig import network.loki.messenger.BuildConfig
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivitySettingsBinding import network.loki.messenger.databinding.ActivitySettingsBinding
import network.loki.messenger.libsession_util.util.UserPic
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all import nl.komponents.kovenant.all
import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.alwaysUi
@ -35,6 +37,7 @@ import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.avatar.AvatarSelection import org.thoughtcrime.securesms.avatar.AvatarSelection
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.home.PathActivity import org.thoughtcrime.securesms.home.PathActivity
import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
@ -50,9 +53,14 @@ import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.show import org.thoughtcrime.securesms.util.show
import java.io.File import java.io.File
import java.security.SecureRandom import java.security.SecureRandom
import java.util.Date import javax.inject.Inject
@AndroidEntryPoint
class SettingsActivity : PassphraseRequiredActionBarActivity() { class SettingsActivity : PassphraseRequiredActionBarActivity() {
@Inject
lateinit var configFactory: ConfigFactory
private lateinit var binding: ActivitySettingsBinding private lateinit var binding: ActivitySettingsBinding
private var displayNameEditActionMode: ActionMode? = null private var displayNameEditActionMode: ActionMode? = null
set(value) { field = value; handleDisplayNameEditActionModeChanged() } set(value) { field = value; handleDisplayNameEditActionModeChanged() }
@ -196,6 +204,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
val displayName = displayNameToBeUploaded val displayName = displayNameToBeUploaded
if (displayName != null) { if (displayName != null) {
TextSecurePreferences.setProfileName(this, displayName) TextSecurePreferences.setProfileName(this, displayName)
configFactory.user?.setName(displayName)
} }
val profilePicture = profilePictureToBeUploaded val profilePicture = profilePictureToBeUploaded
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this) val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
@ -207,8 +216,13 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
if (isUpdatingProfilePicture && profilePicture != null) { if (isUpdatingProfilePicture && profilePicture != null) {
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture) AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt()) TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
TextSecurePreferences.setLastProfilePictureUpload(this, Date().time)
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey) ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
// new config
val url = TextSecurePreferences.getProfilePictureURL(this)
val profileKey = ProfileKeyUtil.getProfileKey(this)
if (!url.isNullOrEmpty() && !profileKey.isEmpty()) {
configFactory.user?.setPic(UserPic(url, profileKey))
}
} }
if (profilePicture != null || displayName != null) { if (profilePicture != null || displayName != null) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
@ -218,10 +232,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
if (displayName != null) { if (displayName != null) {
binding.btnGroupNameDisplay.text = displayName binding.btnGroupNameDisplay.text = displayName
} }
if (isUpdatingProfilePicture && profilePicture != null) { binding.profilePictureView.root.recycle() // Clear the cached image before updating
binding.profilePictureView.root.recycle() // Clear the cached image before updating binding.profilePictureView.root.update()
binding.profilePictureView.root.update()
}
displayNameToBeUploaded = null displayNameToBeUploaded = null
profilePictureToBeUploaded = null profilePictureToBeUploaded = null
binding.loader.isVisible = false binding.loader.isVisible = false

View File

@ -27,7 +27,10 @@ object ConfigurationMessageUtilities {
if (ConfigBase.isNewConfigEnabled) { if (ConfigBase.isNewConfigEnabled) {
// 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)
if (storage.getConfigSyncJob(ourDestination) != null) return if (storage.getConfigSyncJob(ourDestination) != null) {
Log.d("Loki", "ConfigSyncJob is already running for our destination")
return
}
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

@ -85,7 +85,10 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
val allRequests = mutableListOf<SnodeAPI.SnodeBatchRequestInfo>() val allRequests = mutableListOf<SnodeAPI.SnodeBatchRequestInfo>()
allRequests += batchObjects.requireNoNulls().map { (_, request) -> request } allRequests += batchObjects.requireNoNulls().map { (_, request) -> request }
// add in the deletion if we have any hashes // add in the deletion if we have any hashes
if (toDeleteRequest != null) allRequests += toDeleteRequest if (toDeleteRequest != null) {
allRequests += toDeleteRequest
Log.d(TAG, "Including delete request for current hashes")
}
val batchResponse = SnodeAPI.getSingleTargetSnode(destination.destinationPublicKey()).bind { snode -> val batchResponse = SnodeAPI.getSingleTargetSnode(destination.destinationPublicKey()).bind { snode ->
SnodeAPI.getRawBatchResponse( SnodeAPI.getRawBatchResponse(
@ -104,17 +107,29 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
val deletionResponse = if (toDeleteRequest != null) responseList.last() else null val deletionResponse = if (toDeleteRequest != null) responseList.last() else null
val deletedHashes = deletionResponse?.let { val deletedHashes = deletionResponse?.let {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
deletionResponse["deleted"] as? List<String> // get the sub-request body
}?.toSet() ?: emptySet() (deletionResponse["body"] as? RawResponse)?.let { body ->
// get the swarm dict
body["swarm"] as? RawResponse
}?.mapValues { (_, swarmDict) ->
// get the deleted values from dict
((swarmDict as? RawResponse)?.get("deleted") as? List<String>)?.toSet() ?: emptySet()
}?.values?.reduce { acc, strings ->
// create an intersection of all deleted hashes (common between all swarm nodes)
acc intersect strings
}
} ?: emptySet()
// at this point responseList index should line up with configsRequiringPush index // at this point responseList index should line up with configsRequiringPush index
configsRequiringPush.forEachIndexed { index, config -> configsRequiringPush.forEachIndexed { index, config ->
val (toPushMessage, _) = batchObjects[index]!! val (toPushMessage, _) = batchObjects[index]!!
val response = responseList[index] val response = responseList[index]
val insertHash = response["hash"] as? String ?: run { val responseBody = response["body"] as? RawResponse
val insertHash = responseBody?.get("hash") as? String ?: run {
Log.w(TAG, "No hash returned for the configuration in namespace ${config.configNamespace()}") Log.w(TAG, "No hash returned for the configuration in namespace ${config.configNamespace()}")
return@forEachIndexed return@forEachIndexed
} }
Log.d(TAG, "Hash $insertHash returned from store request for new config")
// confirm pushed seqno // confirm pushed seqno
val thisSeqNo = toPushMessage.seqNo val thisSeqNo = toPushMessage.seqNo
@ -129,7 +144,8 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
configFactory.persist(config) configFactory.persist(config)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error performing batch request") Log.e(TAG, "Error performing batch request", e)
return delegate.handleJobFailedPermanently(this, e)
} }
delegate.handleJobSucceeded(this) delegate.handleJobSucceeded(this)
} }

View File

@ -115,7 +115,7 @@ class JobQueue : JobDelegate {
while (isActive) { while (isActive) {
when (val job = queue.receive()) { when (val job = queue.receive()) {
is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> { is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob, is ConfigurationSyncJob -> {
txQueue.send(job) txQueue.send(job)
} }
is AttachmentDownloadJob -> { is AttachmentDownloadJob -> {

View File

@ -26,7 +26,6 @@ import org.session.libsession.snode.RawResponse
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeModule import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.Snode import org.session.libsignal.utilities.Snode
@ -133,8 +132,18 @@ class Poller(private val configFactory: ConfigFactoryProtocol) {
snode, snode,
userPublicKey, userPublicKey,
namespace, namespace,
updateLatestHash = false updateLatestHash = false,
) updateStoredHashes = false,
).filter { (_, hash) -> !configFactory.getHashesFor(forConfigObject).contains(hash) }
if (messages.isEmpty()) {
// no new messages to process
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)
@ -143,7 +152,6 @@ class Poller(private val configFactory: ConfigFactoryProtocol) {
Log.w("Loki-DBG", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}") Log.w("Loki-DBG", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}")
return@forEach return@forEach
} }
// maybe do something with seqNo ?
Log.d("Loki-DBG", "Merging config of kind ${message.kind} into ${forConfigObject.javaClass.simpleName}") Log.d("Loki-DBG", "Merging config of kind ${message.kind} into ${forConfigObject.javaClass.simpleName}")
forConfigObject.merge(message.data) forConfigObject.merge(message.data)
configFactory.appendHash(forConfigObject, hash!!) configFactory.appendHash(forConfigObject, hash!!)
@ -184,20 +192,27 @@ class Poller(private val configFactory: ConfigFactoryProtocol) {
if (deferred.promise.isDone()) { if (deferred.promise.isDone()) {
return@bind Promise.ofSuccess(Unit) return@bind Promise.ofSuccess(Unit)
} else { } else {
// TODO: remove log after testing responses
Log.d("Loki-DBG", JsonUtil.toJson(rawResponses))
val requestList = (rawResponses["results"] as List<RawResponse>) val requestList = (rawResponses["results"] as List<RawResponse>)
// in case we had null configs, the array won't be fully populated // in case we had null configs, the array won't be fully populated
// index of the sparse array key iterator should be the request index, with the key being the namespace // index of the sparse array key iterator should be the request index, with the key being the namespace
requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) -> requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) ->
requestList.getOrNull(requestIndex)?.let { rawResponse -> requestList.getOrNull(requestIndex)?.let { rawResponse ->
if (rawResponse["code"] as? Int != 200) {
Log.e("Loki-DBG", "Batch sub-request had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}")
return@forEach
}
val body = rawResponse["body"] as? RawResponse
if (body == null) {
Log.e("Loki-DBG", "Batch sub-request didn't contain a body")
return@forEach
}
if (key == Namespace.DEFAULT) { if (key == Namespace.DEFAULT) {
processPersonalMessages(snode, rawResponse) processPersonalMessages(snode, body)
} else { } else {
when (ConfigBase.kindFor(key)) { when (ConfigBase.kindFor(key)) {
UserProfile::class.java -> processConfig(snode, rawResponse, key, configFactory.user) UserProfile::class.java -> processConfig(snode, body, key, configFactory.user)
Contacts::class.java -> processConfig(snode, rawResponse, key, configFactory.contacts) Contacts::class.java -> processConfig(snode, body, key, configFactory.contacts)
ConversationVolatileConfig::class.java -> processConfig(snode, rawResponse, key, configFactory.convoVolatile) ConversationVolatileConfig::class.java -> processConfig(snode, body, key, configFactory.convoVolatile)
} }
} }
} }

View File

@ -442,7 +442,7 @@ object SnodeAPI {
params["pubkey_ed25519"] = ed25519PublicKey params["pubkey_ed25519"] = ed25519PublicKey
params["signature"] = Base64.encodeBytes(signature) params["signature"] = Base64.encodeBytes(signature)
return SnodeBatchRequestInfo( return SnodeBatchRequestInfo(
Snode.Method.Retrieve.rawValue, Snode.Method.DeleteMessage.rawValue,
params, params,
null null
) )
@ -711,13 +711,13 @@ object SnodeAPI {
} }
} }
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0, updateLatestHash: Boolean = true): List<Pair<SignalServiceProtos.Envelope, String?>> { fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0, updateLatestHash: Boolean = true, updateStoredHashes: Boolean = true): List<Pair<SignalServiceProtos.Envelope, String?>> {
val messages = rawResponse["messages"] as? List<*> val messages = rawResponse["messages"] as? List<*>
return if (messages != null) { return if (messages != null) {
if (updateLatestHash) { if (updateLatestHash) {
updateLastMessageHashValueIfPossible(snode, publicKey, messages, namespace) updateLastMessageHashValueIfPossible(snode, publicKey, messages, namespace)
} }
val newRawMessages = removeDuplicates(publicKey, messages, namespace) val newRawMessages = removeDuplicates(publicKey, messages, namespace, updateStoredHashes)
return parseEnvelopes(newRawMessages) return parseEnvelopes(newRawMessages)
} else { } else {
listOf() listOf()
@ -734,7 +734,7 @@ object SnodeAPI {
} }
} }
private fun removeDuplicates(publicKey: String, rawMessages: List<*>, namespace: Int): List<*> { private fun removeDuplicates(publicKey: String, rawMessages: List<*>, namespace: Int, updateStoredHashes: Boolean): List<*> {
val originalMessageHashValues = database.getReceivedMessageHashValues(publicKey, namespace)?.toMutableSet() ?: mutableSetOf() val originalMessageHashValues = database.getReceivedMessageHashValues(publicKey, namespace)?.toMutableSet() ?: mutableSetOf()
val receivedMessageHashValues = originalMessageHashValues.toMutableSet() val receivedMessageHashValues = originalMessageHashValues.toMutableSet()
val result = rawMessages.filter { rawMessage -> val result = rawMessages.filter { rawMessage ->
@ -749,7 +749,7 @@ object SnodeAPI {
false false
} }
} }
if (originalMessageHashValues != receivedMessageHashValues) { if (originalMessageHashValues != receivedMessageHashValues && updateStoredHashes) {
database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues, namespace) database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues, namespace)
} }
return result return result