diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index a827a7d260..4590c86892 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -108,12 +108,14 @@ class ProfilePictureView @JvmOverloads constructor( val signalProfilePicture = recipient.contactPhoto val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject + val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}") + if (signalProfilePicture != null && avatar != "0" && avatar != "") { glide.clear(imageView) glide.load(signalProfilePicture) .placeholder(unknownRecipientDrawable) .centerCrop() - .error(unknownRecipientDrawable) + .error(glide.load(placeholder)) .diskCacheStrategy(DiskCacheStrategy.NONE) .circleCrop() .into(imageView) @@ -121,8 +123,6 @@ class ProfilePictureView @JvmOverloads constructor( glide.clear(imageView) imageView.setImageDrawable(unknownOpenGroupDrawable) } else { - val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}") - glide.clear(imageView) glide.load(placeholder) .placeholder(unknownRecipientDrawable) 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 7679c97bc7..afff87aaae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt @@ -126,8 +126,6 @@ class ConfigFactory(private val context: Context, } override fun notifyUpdates(forConfigObject: ConfigBase) { - if (!forConfigObject.needsDump()) return - when (forConfigObject) { is UserProfile -> updateUser(forConfigObject) is Contacts -> updateContacts(forConfigObject) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 0a56c5058d..4839180265 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -20,9 +20,11 @@ import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.core.view.isVisible +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.BuildConfig import network.loki.messenger.R import network.loki.messenger.databinding.ActivitySettingsBinding +import network.loki.messenger.libsession_util.util.UserPic import nl.komponents.kovenant.Promise import nl.komponents.kovenant.all 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.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.avatar.AvatarSelection +import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.home.PathActivity import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.mms.GlideApp @@ -50,9 +53,14 @@ import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.show import java.io.File import java.security.SecureRandom -import java.util.Date +import javax.inject.Inject +@AndroidEntryPoint class SettingsActivity : PassphraseRequiredActionBarActivity() { + + @Inject + lateinit var configFactory: ConfigFactory + private lateinit var binding: ActivitySettingsBinding private var displayNameEditActionMode: ActionMode? = null set(value) { field = value; handleDisplayNameEditActionModeChanged() } @@ -196,6 +204,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { val displayName = displayNameToBeUploaded if (displayName != null) { TextSecurePreferences.setProfileName(this, displayName) + configFactory.user?.setName(displayName) } val profilePicture = profilePictureToBeUploaded val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this) @@ -207,8 +216,13 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { if (isUpdatingProfilePicture && profilePicture != null) { AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture) TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt()) - TextSecurePreferences.setLastProfilePictureUpload(this, Date().time) 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) { ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) @@ -218,10 +232,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { if (displayName != null) { binding.btnGroupNameDisplay.text = displayName } - if (isUpdatingProfilePicture && profilePicture != null) { - binding.profilePictureView.root.recycle() // Clear the cached image before updating - binding.profilePictureView.root.update() - } + binding.profilePictureView.root.recycle() // Clear the cached image before updating + binding.profilePictureView.root.update() displayNameToBeUploaded = null profilePictureToBeUploaded = null binding.loader.isVisible = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt index 2d43992c9c..32d2d026ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt @@ -27,7 +27,10 @@ object ConfigurationMessageUtilities { if (ConfigBase.isNewConfigEnabled) { // don't schedule job if we already have one 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) Log.d("Loki", "Scheduling new ConfigurationSyncJob") JobQueue.shared.add(newConfigSync) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt index 4e66cb4eaa..4175bea5d6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt @@ -85,7 +85,10 @@ data class ConfigurationSyncJob(val destination: Destination): Job { val allRequests = mutableListOf() allRequests += batchObjects.requireNoNulls().map { (_, request) -> request } // 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 -> SnodeAPI.getRawBatchResponse( @@ -104,17 +107,29 @@ data class ConfigurationSyncJob(val destination: Destination): Job { val deletionResponse = if (toDeleteRequest != null) responseList.last() else null val deletedHashes = deletionResponse?.let { @Suppress("UNCHECKED_CAST") - deletionResponse["deleted"] as? List - }?.toSet() ?: emptySet() + // get the sub-request body + (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)?.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 configsRequiringPush.forEachIndexed { index, config -> val (toPushMessage, _) = batchObjects[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()}") return@forEachIndexed } + Log.d(TAG, "Hash $insertHash returned from store request for new config") // confirm pushed seqno val thisSeqNo = toPushMessage.seqNo @@ -129,7 +144,8 @@ data class ConfigurationSyncJob(val destination: Destination): Job { configFactory.persist(config) } } 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) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index ab8f3212ae..666f7b2742 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -115,7 +115,7 @@ class JobQueue : JobDelegate { while (isActive) { when (val job = queue.receive()) { - is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> { + is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob, is ConfigurationSyncJob -> { txQueue.send(job) } is AttachmentDownloadJob -> { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt index 9cd7d37fe1..a699cb9218 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/Poller.kt @@ -26,7 +26,6 @@ import org.session.libsession.snode.RawResponse import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.ConfigFactoryProtocol -import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace import org.session.libsignal.utilities.Snode @@ -133,8 +132,18 @@ class Poller(private val configFactory: ConfigFactoryProtocol) { snode, userPublicKey, 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) -> try { 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}") return@forEach } - // maybe do something with seqNo ? Log.d("Loki-DBG", "Merging config of kind ${message.kind} into ${forConfigObject.javaClass.simpleName}") forConfigObject.merge(message.data) configFactory.appendHash(forConfigObject, hash!!) @@ -184,20 +192,27 @@ class Poller(private val configFactory: ConfigFactoryProtocol) { if (deferred.promise.isDone()) { return@bind Promise.ofSuccess(Unit) } else { - // TODO: remove log after testing responses - Log.d("Loki-DBG", JsonUtil.toJson(rawResponses)) val requestList = (rawResponses["results"] as List) // 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 requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) -> 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) { - processPersonalMessages(snode, rawResponse) + processPersonalMessages(snode, body) } else { when (ConfigBase.kindFor(key)) { - UserProfile::class.java -> processConfig(snode, rawResponse, key, configFactory.user) - Contacts::class.java -> processConfig(snode, rawResponse, key, configFactory.contacts) - ConversationVolatileConfig::class.java -> processConfig(snode, rawResponse, key, configFactory.convoVolatile) + UserProfile::class.java -> processConfig(snode, body, key, configFactory.user) + Contacts::class.java -> processConfig(snode, body, key, configFactory.contacts) + ConversationVolatileConfig::class.java -> processConfig(snode, body, key, configFactory.convoVolatile) } } } diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 61ebf295cd..4ee1d8aa02 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -442,7 +442,7 @@ object SnodeAPI { params["pubkey_ed25519"] = ed25519PublicKey params["signature"] = Base64.encodeBytes(signature) return SnodeBatchRequestInfo( - Snode.Method.Retrieve.rawValue, + Snode.Method.DeleteMessage.rawValue, params, null ) @@ -711,13 +711,13 @@ object SnodeAPI { } } - fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0, updateLatestHash: Boolean = true): List> { + fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0, updateLatestHash: Boolean = true, updateStoredHashes: Boolean = true): List> { val messages = rawResponse["messages"] as? List<*> return if (messages != null) { if (updateLatestHash) { updateLastMessageHashValueIfPossible(snode, publicKey, messages, namespace) } - val newRawMessages = removeDuplicates(publicKey, messages, namespace) + val newRawMessages = removeDuplicates(publicKey, messages, namespace, updateStoredHashes) return parseEnvelopes(newRawMessages) } else { 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 receivedMessageHashValues = originalMessageHashValues.toMutableSet() val result = rawMessages.filter { rawMessage -> @@ -749,7 +749,7 @@ object SnodeAPI { false } } - if (originalMessageHashValues != receivedMessageHashValues) { + if (originalMessageHashValues != receivedMessageHashValues && updateStoredHashes) { database.setReceivedMessageHashValues(publicKey, receivedMessageHashValues, namespace) } return result