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 3875a0fffb..74319eaabe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -7,6 +7,7 @@ import network.loki.messenger.libsession_util.Contacts import network.loki.messenger.libsession_util.ConversationVolatileConfig import network.loki.messenger.libsession_util.UserProfile 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.database.StorageProtocol import org.session.libsession.messaging.BlindedIdMapping @@ -318,7 +319,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF } // update pfp - if (userPic == null) { + if (userPic == UserPic.DEFAULT) { // clear picture if userPic is null TextSecurePreferences.setProfileKey(context, null) ProfileKeyUtil.setEncodedProfileKey(context, null) @@ -335,10 +336,23 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF private fun updateContacts(contacts: Contacts) { val extracted = contacts.all().toList() + val profileManager = SSKEnvironment.shared.profileManager extracted.forEach { contact -> val address = fromSerialized(contact.id) 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) } } - Log.d("Loki-DBG", "Should update thread $threadId") if (threadId >= 0) { markConversationAsRead(threadId, conversation.lastRead) updateThread(threadId, false) @@ -761,6 +774,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF override fun setContact(contact: Contact) { DatabaseComponent.get(context).sessionContactDatabase().setContact(contact) + SSKEnvironment.shared.profileManager.contactUpdatedInternal(contact) } override fun getRecipientForThread(threadId: Long): Recipient? { @@ -823,7 +837,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF threadDatabase.setHasSent(threadId, true) } if (contact.isBlocked == true) { - setBlocked(listOf(recipient), true) + setBlocked(listOf(recipient), true, fromConfigUpdate = true) threadDatabase.deleteConversation(threadId) } } @@ -1125,16 +1139,17 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms)) } - override fun setBlocked(recipients: List, isBlocked: Boolean) { + override fun setBlocked(recipients: List, isBlocked: Boolean, fromConfigUpdate: Boolean) { val recipientDb = DatabaseComponent.get(context).recipientDatabase() recipientDb.setBlocked(recipients, isBlocked) recipients.filter { it.isContactRecipient }.forEach { recipient -> configFactory.contacts?.upsertContact(recipient.address.serialize()) { - this.blocked = true + this.blocked = isBlocked } } 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) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index f3915abff6..86193f9619 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -25,7 +25,6 @@ import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.util.UiModeUtilities import javax.inject.Inject @@ -131,10 +130,10 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { newNickName = nicknameEditText.text.toString() } val publicKey = recipient.address.serialize() - val contactDB = DatabaseComponent.get(requireContext()).sessionContactDatabase() - val contact = contactDB.getContactWithSessionID(publicKey) ?: Contact(publicKey) + val storage = MessagingModuleConfiguration.shared.storage + val contact = storage.getContactWithSessionID(publicKey) ?: Contact(publicKey) contact.nickname = newNickName - contactDB.setContact(contact) + storage.setContact(contact) nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 8d115aa4b4..82bab07448 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -82,7 +82,7 @@ class ProfileManager(private val context: Context, private val configFactory: Co database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode) } - private fun contactUpdatedInternal(contact: Contact) { + override fun contactUpdatedInternal(contact: Contact) { val contactConfig = configFactory.contacts ?: return contactConfig.upsertContact(contact.sessionID) { this.name = contact.name.orEmpty() @@ -93,7 +93,7 @@ class ProfileManager(private val context: Context, private val configFactory: Co this.profilePicture = UserPic(url, key) } } - if (contactConfig.needsDump()) { + if (contactConfig.needsPush()) { ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) } } 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 54800d4883..c98f512b87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConfigurationMessageUtilities.kt @@ -70,7 +70,10 @@ object ConfigurationMessageUtilities { // don't schedule job if we already have one val ourDestination = Destination.Contact(userPublicKey) 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) Log.d("Loki", "Scheduling new ConfigurationSyncJob") JobQueue.shared.add(newConfigSync) 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 ef7eec8f0e..3f246af80e 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -204,7 +204,7 @@ interface StorageProtocol { fun removeReaction(emoji: String, messageTimestamp: Long, author: String, notifyUnread: Boolean) fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long) fun deleteReactions(messageId: Long, mms: Boolean) - fun setBlocked(recipients: List, isBlocked: Boolean) + fun setBlocked(recipients: List, isBlocked: Boolean, fromConfigUpdate: Boolean = false) fun blockedContacts(): List // Shared configs 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 c804ba002d..03dcbd8c05 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 @@ -21,18 +21,12 @@ data class ConfigurationSyncJob(val destination: Destination): Job { override var failureCount: Int = 0 override val maxFailureCount: Int = 1 - val isRunning = AtomicBoolean(false) + val shouldRunAgain = AtomicBoolean(false) - suspend fun wrap(body: suspend ()->Unit) { - isRunning.set(true) - body() - isRunning.set(false) - } - - - override suspend fun execute(dispatcherName: String) = wrap { + override suspend fun execute(dispatcherName: String) { + val storage = MessagingModuleConfiguration.shared.storage val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() - val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() + val userPublicKey = storage.getUserPublicKey() val delegate = delegate 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 @@ -47,7 +41,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job { || (destination is Destination.Contact && destination.publicKey != userPublicKey) ) { 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 @@ -61,7 +55,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job { ).filter { config -> config.needsPush() } // 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 val batchObjects: List?> = configsRequiringPush.map { config -> @@ -88,7 +82,7 @@ data class ConfigurationSyncJob(val destination: Destination): Job { if (batchObjects.any { it == null }) { // 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() @@ -156,9 +150,13 @@ data class ConfigurationSyncJob(val destination: Destination): Job { } } catch (e: Exception) { 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) + 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) { 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 08b531e4a2..acc58eefba 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 @@ -43,7 +43,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti var isCaughtUp = false var configPollingJob: Job? = null - val configDebouncer = WindowDebouncer(3000, debounceTimer) + private val configDebouncer = WindowDebouncer(3000, debounceTimer) // region Settings companion object { @@ -144,9 +144,6 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti 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) diff --git a/libsession/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java b/libsession/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java index 9e3842fc67..4550965ae7 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java +++ b/libsession/src/main/java/org/session/libsession/utilities/ProfileKeyUtil.java @@ -1,23 +1,24 @@ package org.session.libsession.utilities; import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.session.libsignal.utilities.Base64; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; import java.io.IOException; public class ProfileKeyUtil { + public static final int PROFILE_KEY_BYTES = 32; + public static synchronized @NonNull byte[] getProfileKey(@NonNull Context context) { try { String encodedProfileKey = TextSecurePreferences.getProfileKey(context); if (encodedProfileKey == null) { - encodedProfileKey = Util.getSecret(32); + encodedProfileKey = Util.getSecret(PROFILE_KEY_BYTES); TextSecurePreferences.setProfileKey(context, encodedProfileKey); } @@ -36,7 +37,7 @@ public class ProfileKeyUtil { } 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) { diff --git a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index eeebf17c18..e939655885 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -1,6 +1,7 @@ package org.session.libsession.utilities import android.content.Context +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier import org.session.libsession.utilities.recipients.Recipient @@ -33,6 +34,7 @@ class SSKEnvironment( fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray?) fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) + fun contactUpdatedInternal(contact: Contact) } interface MessageExpirationManagerProtocol {