From 3654d1731cd87febfe00715379372ba55ec5d389 Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 30 Mar 2021 16:23:12 +1100 Subject: [PATCH] fix: various fixes wrt open groups, config messages, job queueing --- .../securesms/ApplicationContext.java | 6 +-- .../securesms/database/Storage.kt | 47 +++++++++++++++++++ .../securesms/jobs/PushReceivedJob.java | 6 +-- .../loki/api/PushNotificationService.kt | 13 +++-- .../libsession/messaging/StorageProtocol.kt | 5 ++ .../messaging/jobs/AttachmentDownloadJob.kt | 36 +++++++------- .../libsession/messaging/jobs/JobQueue.kt | 12 ++--- .../MessageReceiverHandler.kt | 27 +++++++++-- .../pollers/OpenGroupPoller.kt | 7 +-- 9 files changed, 109 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 8de298955c..c07dca4f7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -230,14 +230,14 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); } - if (publicChatManager != null) { - publicChatManager.stopPollers(); - } } @Override public void onTerminate() { stopKovenant(); // Loki + if (publicChatManager != null) { + publicChatManager.stopPollers(); + } super.onTerminate(); } 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 69c4f2e076..eaf72e6dbb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -8,6 +8,7 @@ import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.visible.Attachment @@ -30,7 +31,9 @@ import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper +import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities @@ -65,12 +68,27 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return TextSecurePreferences.getProfilePictureURL(context) } + override fun setUserProfilePictureUrl(newProfilePicture: String) { + val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let { + Recipient.from(context, it, false) + } + TextSecurePreferences.setProfilePictureURL(context, newProfilePicture) + RetrieveProfileAvatarJob(ourRecipient, newProfilePicture) + ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newProfilePicture)) + } + override fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? { val address = Address.fromSerialized(recipientPublicKey) val recipient = Recipient.from(context, address, false) return recipient.profileKey } + override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) { + val address = Address.fromSerialized(recipientPublicKey) + val recipient = Recipient.from(context, address, false) + DatabaseFactory.getRecipientDatabase(context).setProfileKey(recipient, profileKey) + } + override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { @@ -511,6 +529,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) } + override fun setDisplayName(publicKey: String, newName: String) { + DatabaseFactory.getLokiUserDatabase(context).setDisplayName(publicKey, newName) + } + override fun getServerDisplayName(serverID: String, publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(serverID, publicKey) } @@ -524,6 +546,31 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return if (recipientSettings.isPresent) { recipientSettings.get() } else null } + override fun addContacts(contacts: List) { + val recipientDatabase = DatabaseFactory.getRecipientDatabase(context) + val threadDatabase = DatabaseFactory.getThreadDatabase(context) + for (contact in contacts) { + val address = Address.fromSerialized(contact.publicKey) + val recipient = Recipient.from(context, address, true) + if (!contact.profilePicture.isNullOrEmpty()) { + recipientDatabase.setProfileAvatar(recipient, contact.profilePicture) + } + if (contact.profileKey?.isNotEmpty() == true) { + recipientDatabase.setProfileKey(recipient, contact.profileKey) + } + if (contact.name.isNotEmpty()) { + recipientDatabase.setProfileName(recipient, contact.name) + } + recipientDatabase.setProfileSharing(recipient, true) + recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED) + // create Thread if needed + threadDatabase.getOrCreateThreadIdFor(recipient) + } + if (contacts.isNotEmpty()) { + threadDatabase.notifyUpdatedFromConfig() + } + } + override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java index ac2519228d..70f9b755b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushReceivedJob.java @@ -3,11 +3,11 @@ package org.thoughtcrime.securesms.jobs; import androidx.annotation.NonNull; import org.session.libsession.messaging.threads.Address; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.session.libsignal.utilities.logging.Log; import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsignal.service.api.messages.SignalServiceEnvelope; +import org.session.libsignal.utilities.logging.Log; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.jobmanager.Job; public abstract class PushReceivedJob extends BaseJob { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt index 27f67c9a69..0f5244ae19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PushNotificationService.kt @@ -4,13 +4,13 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import org.thoughtcrime.securesms.jobs.PushContentReceiveJob -import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.logging.Log -import org.session.libsignal.service.api.messages.SignalServiceEnvelope -import org.session.libsignal.utilities.Base64 import org.session.libsignal.service.loki.api.MessageWrapper +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.logging.Log +import org.thoughtcrime.securesms.notifications.NotificationChannels class PushNotificationService : FirebaseMessagingService() { @@ -27,8 +27,7 @@ class PushNotificationService : FirebaseMessagingService() { val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { try { - val envelope = MessageWrapper.unwrap(data) - PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true) + JobQueue.shared.add(MessageReceiveJob(MessageWrapper.unwrap(data).toByteArray(),true)) } catch (e: Exception) { Log.d("Loki", "Failed to unwrap data for message due to error: $e.") } diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index 8a392953d2..64c1828d51 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -6,6 +6,7 @@ import android.net.Uri import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.opengroups.OpenGroup @@ -30,8 +31,10 @@ interface StorageProtocol { fun getUserDisplayName(): String? fun getUserProfileKey(): ByteArray? fun getUserProfilePictureURL(): String? + fun setUserProfilePictureUrl(newProfilePicture: String) fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? + fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) // Signal Protocol @@ -140,11 +143,13 @@ interface StorageProtocol { // Loki User fun getDisplayName(publicKey: String): String? + fun setDisplayName(publicKey: String, newName: String) fun getServerDisplayName(serverID: String, publicKey: String): String? fun getProfilePictureURL(publicKey: String): String? // Recipient fun getRecipientSettings(address: Address): RecipientSettings? + fun addContacts(contacts: List) // PartAuthority fun getAttachmentDataUri(attachmentId: AttachmentId): Uri diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 46dcbae37b..1a0b9f9ef3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -34,30 +34,26 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) } override fun execute() { + val handleFailure: (java.lang.Exception) -> Unit = { exception -> + if(exception is Error && exception == Error.NoAttachment) { + MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) + this.handlePermanentFailure(exception) + } else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) { + // No need to retry if the response is invalid. Most likely this means we (incorrectly) + // got a "Cannot GET ..." error from the file server. + MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) + this.handlePermanentFailure(exception) + } else { + this.handleFailure(exception) + } + } try { val messageDataProvider = MessagingConfiguration.shared.messageDataProvider val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment) messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) val tempFile = createTempFile() - val handleFailure: (java.lang.Exception) -> Unit = { exception -> - tempFile.delete() - if(exception is Error && exception == Error.NoAttachment) { - MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) - this.handlePermanentFailure(exception) - } else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) { - // No need to retry if the response is invalid. Most likely this means we (incorrectly) - // got a "Cannot GET ..." error from the file server. - MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) - this.handlePermanentFailure(exception) - } else { - this.handleFailure(exception) - } - } - try { - FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) - } catch (e: Exception) { - return handleFailure(e) - } + + FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) // DECRYPTION @@ -70,7 +66,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) tempFile.delete() handleSuccess() } catch (e: Exception) { - handleFailure(e) + return handleFailure(e) } } 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 96b00c8397..2934e8dcd7 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 @@ -16,19 +16,17 @@ import kotlin.math.roundToLong class JobQueue : JobDelegate { private var hasResumedPendingJobs = false // Just for debugging - private val dispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher() + private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val scope = GlobalScope + SupervisorJob() private val queue = Channel(UNLIMITED) init { // process jobs - scope.launch { + scope.launch(dispatcher) { while (isActive) { queue.receive().let { job -> - launch(dispatcher) { - job.delegate = this@JobQueue - job.execute() - } + job.delegate = this@JobQueue + job.execute() } } } @@ -44,7 +42,7 @@ class JobQueue : JobDelegate { queue.offer(job) // offer always called on unlimited capacity } - fun addWithoutExecuting(job: Job) { + private fun addWithoutExecuting(job: Job) { job.id = System.currentTimeMillis().toString() MessagingConfiguration.shared.storage.persistJob(job) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt index 647cb094d2..686d8fba1a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiverHandler.kt @@ -18,6 +18,7 @@ import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.ECKeyPair @@ -26,6 +27,7 @@ import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString +import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.logging.Log import java.security.MessageDigest import java.util.* @@ -91,8 +93,11 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMessage) { val context = MessagingConfiguration.shared.context val storage = MessagingConfiguration.shared.storage - if (TextSecurePreferences.getConfigurationMessageSynced(context)) return - if (message.sender != storage.getUserPublicKey()) return + if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, message.sentTimestamp!!)) return + val userPublicKey = storage.getUserPublicKey() + if (userPublicKey == null || message.sender != storage.getUserPublicKey()) return + TextSecurePreferences.setConfigurationMessageSynced(context, true) + TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!) val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() for (closeGroup in message.closedGroups) { if (allClosedGroupPublicKeys.contains(closeGroup.publicKey)) continue @@ -103,8 +108,20 @@ private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMes if (allOpenGroups.contains(openGroup)) continue storage.addOpenGroup(openGroup, 1) } - // TODO: in future handle the latest in config messages - TextSecurePreferences.setConfigurationMessageSynced(context, true) + if (message.displayName.isNotEmpty()) { + TextSecurePreferences.setProfileName(context, message.displayName) + storage.setDisplayName(userPublicKey, message.displayName) + } + if (message.profileKey.isNotEmpty()) { + val profileKey = Base64.encodeBytes(message.profileKey) + ProfileKeyUtil.setEncodedProfileKey(context, profileKey) + storage.setProfileKeyForRecipient(userPublicKey, message.profileKey) + // handle profile photo + if (!message.profilePicture.isNullOrEmpty() && TextSecurePreferences.getProfilePictureURL(context) != message.profilePicture) { + storage.setUserProfilePictureUrl(message.profilePicture!!) + } + } + storage.addContacts(message.contacts) } fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) { @@ -112,7 +129,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS val context = MessagingConfiguration.shared.context // Update profile if needed val newProfile = message.profile - if (newProfile != null) { + if (newProfile != null && openGroupID.isNullOrEmpty()) { val profileManager = SSKEnvironment.shared.profileManager val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false) val displayName = newProfile.displayName!! diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index a6d233b482..5bbae93ca6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -83,10 +83,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ messages.forEach { message -> try { val senderPublicKey = message.senderPublicKey - fun generateDisplayName(rawDisplayName: String): String { - return "${rawDisplayName} (${senderPublicKey.takeLast(8)})" - } - val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName("Anonymous") + val senderDisplayName = message.displayName val id = openGroup.id.toByteArray() // Main message val dataMessageProto = DataMessage.newBuilder() @@ -145,7 +142,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ val messageServerID = message.serverID // Profile val profileProto = DataMessage.LokiProfile.newBuilder() - profileProto.setDisplayName(message.displayName) + profileProto.setDisplayName(senderDisplayName) val profilePicture = message.profilePicture if (profilePicture != null) { profileProto.setProfilePicture(profilePicture.url)