From 457faae5a50e5c610e09f6db7c56de72980a23d7 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 18 Nov 2019 11:08:23 +1100 Subject: [PATCH 1/4] Added public chat server profile name batch polling. --- .../securesms/loki/LokiPublicChatPoller.kt | 81 ++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 28bca2fea1..47825577df 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -28,6 +28,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki // region Convenience private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) + private var displayNameUpdatees = setOf() private val api: LokiPublicChatAPI get() = { @@ -62,6 +63,13 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki handler.postDelayed(this, pollForModeratorsInterval) } } + + private val pollForDisplayNamesTask = object : Runnable { + override fun run() { + pollForDisplayNames() + handler.postDelayed(this, pollForDisplayNamesInterval) + } + } // endregion // region Settings @@ -69,6 +77,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private val pollForNewMessagesInterval: Long = 4 * 1000 private val pollForDeletedMessagesInterval: Long = 20 * 1000 private val pollForModeratorsInterval: Long = 10 * 60 * 1000 + private val pollForDisplayNamesInterval: Long = 1 * 60 * 1000 } // endregion @@ -78,6 +87,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki pollForNewMessagesTask.run() pollForDeletedMessagesTask.run() pollForModeratorsTask.run() + pollForDisplayNamesTask.run() hasStarted = true } @@ -85,6 +95,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki handler.removeCallbacks(pollForNewMessagesTask) handler.removeCallbacks(pollForDeletedMessagesTask) handler.removeCallbacks(pollForModeratorsTask) + handler.removeCallbacks(pollForDisplayNamesTask) hasStarted = false } // endregion @@ -136,10 +147,15 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private fun pollForNewMessages() { fun processIncomingMessage(message: LokiPublicChatMessage) { + // If the sender of the current message is not a secondary device, we need to set the display name in the database + val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(message.hexEncodedPublicKey).get() + if (primaryDevice == null) { + val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})" + DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName) + } + val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey val serviceDataMessage = getDataMessage(message) - val serviceContent = SignalServiceContent(serviceDataMessage, message.hexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) - val senderDisplayName = "${message.displayName} (...${message.hexEncodedPublicKey.takeLast(8)})" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.hexEncodedPublicKey, senderDisplayName) + val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) } else { @@ -161,23 +177,70 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) } } + api.getMessages(group.channel, group.server).successBackground { messages -> if (messages.isNotEmpty()) { val ourDevices = LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).get(setOf()) - // Process messages in the background - messages.forEach { message -> - if (ourDevices.contains(message.hexEncodedPublicKey)) { - processOutgoingMessage(message) - } else { - processIncomingMessage(message) + val uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() + val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } + + fun proceed() { + // Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices + val newDisplayNameUpdatees = uniqueDevices.mapNotNull { + // This will return null if current device is primary + // So if it's non-null then we know the device is a secondary device + val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() + primaryDevice + }.toSet() + + // Fetch the display names of the primary devices + displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) + + // Process messages in the background + messages.forEach { message -> + if (ourDevices.contains(message.hexEncodedPublicKey)) { + processOutgoingMessage(message) + } else { + processIncomingMessage(message) + } } } + + // We need to fetch device mappings for all the devices we don't have + if (devicesToUpdate.isEmpty()) { + proceed() + } else { + // Fetch the device mappings first + try { + LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).get() + } finally { + proceed() + } + } + } }.fail { Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.") } } + private fun pollForDisplayNames() { + if (displayNameUpdatees.isEmpty()) { return } + + val devices = displayNameUpdatees + displayNameUpdatees = setOf() + + api.getDisplayNames(devices, group.server).successBackground { mapping -> + for (pair in mapping.entries) { + val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" + DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName) + } + }.fail { + // Retry next time + displayNameUpdatees = displayNameUpdatees.union(devices) + } + } + private fun pollForDeletedMessages() { api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs -> val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context) From 8eedff81eb382c22c5cd9cedd7e21d907534bf0f Mon Sep 17 00:00:00 2001 From: Mikunj Date: Mon, 18 Nov 2019 11:55:16 +1100 Subject: [PATCH 2/4] Async everything! --- .../securesms/loki/LokiPublicChatPoller.kt | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 47825577df..230a618b14 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -1,8 +1,12 @@ package org.thoughtcrime.securesms.loki import android.content.Context +import android.os.AsyncTask import android.os.Handler import android.util.Log +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.bind +import nl.komponents.kovenant.then import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.PushDecryptJob @@ -178,46 +182,42 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki } } - api.getMessages(group.channel, group.server).successBackground { messages -> + var ourDevices = setOf() + var uniqueDevices = setOf() + LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).bind { devices -> + ourDevices = devices + api.getMessages(group.channel, group.server) + }.bind { messages -> if (messages.isNotEmpty()) { - val ourDevices = LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).get(setOf()) - val uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() - val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } - - fun proceed() { - // Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices - val newDisplayNameUpdatees = uniqueDevices.mapNotNull { - // This will return null if current device is primary - // So if it's non-null then we know the device is a secondary device - val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() - primaryDevice - }.toSet() - - // Fetch the display names of the primary devices - displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) - - // Process messages in the background - messages.forEach { message -> - if (ourDevices.contains(message.hexEncodedPublicKey)) { - processOutgoingMessage(message) - } else { - processIncomingMessage(message) - } - } - } - // We need to fetch device mappings for all the devices we don't have - if (devicesToUpdate.isEmpty()) { - proceed() - } else { - // Fetch the device mappings first - try { - LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).get() - } finally { - proceed() + uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() + val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } + if (devicesToUpdate.isNotEmpty()) { + return@bind LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).then { messages } + } + } + Promise.of(messages) + }.successBackground { + // Get the set of primary device pubKeys FROM the secondary devices in uniqueDevices + val newDisplayNameUpdatees = uniqueDevices.mapNotNull { + // This will return null if current device is primary + // So if it's non-null then we know the device is a secondary device + val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() + primaryDevice + }.toSet() + + // Fetch the display names of the primary devices + displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) + }.success { messages -> + // Process messages in the background + messages.forEach { message -> + AsyncTask.execute { + if (ourDevices.contains(message.hexEncodedPublicKey)) { + processOutgoingMessage(message) + } else { + processIncomingMessage(message) } } - } }.fail { Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.") From 638d693e11c3b60254e3ab4228894afa251798bb Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 13:12:57 +1100 Subject: [PATCH 3/4] Clean --- .../securesms/loki/LokiPublicChatPoller.kt | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 230a618b14..9db5c22bdb 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -22,7 +22,6 @@ import org.whispersystems.signalservice.loki.api.LokiPublicChat import org.whispersystems.signalservice.loki.api.LokiPublicChatAPI import org.whispersystems.signalservice.loki.api.LokiPublicChatMessage import org.whispersystems.signalservice.loki.api.LokiStorageAPI -import org.whispersystems.signalservice.loki.utilities.get import org.whispersystems.signalservice.loki.utilities.successBackground import java.util.* @@ -81,7 +80,7 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private val pollForNewMessagesInterval: Long = 4 * 1000 private val pollForDeletedMessagesInterval: Long = 20 * 1000 private val pollForModeratorsInterval: Long = 10 * 60 * 1000 - private val pollForDisplayNamesInterval: Long = 1 * 60 * 1000 + private val pollForDisplayNamesInterval: Long = 60 * 1000 } // endregion @@ -181,17 +180,16 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) } } - - var ourDevices = setOf() + var userDevices = setOf() var uniqueDevices = setOf() LokiStorageAPI.shared.getAllDevicePublicKeys(userHexEncodedPublicKey).bind { devices -> - ourDevices = devices + userDevices = devices api.getMessages(group.channel, group.server) }.bind { messages -> if (messages.isNotEmpty()) { // We need to fetch device mappings for all the devices we don't have uniqueDevices = messages.map { it.hexEncodedPublicKey }.toSet() - val devicesToUpdate = uniqueDevices.filter { !ourDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } + val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && LokiStorageAPI.shared.hasCacheExpired(it) } if (devicesToUpdate.isNotEmpty()) { return@bind LokiStorageAPI.shared.getDeviceMappings(devicesToUpdate.toSet()).then { messages } } @@ -205,14 +203,13 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val primaryDevice = LokiStorageAPI.shared.getPrimaryDevicePublicKey(it).get() primaryDevice }.toSet() - // Fetch the display names of the primary devices displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees) }.success { messages -> // Process messages in the background messages.forEach { message -> AsyncTask.execute { - if (ourDevices.contains(message.hexEncodedPublicKey)) { + if (userDevices.contains(message.hexEncodedPublicKey)) { processOutgoingMessage(message) } else { processIncomingMessage(message) @@ -226,18 +223,15 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki private fun pollForDisplayNames() { if (displayNameUpdatees.isEmpty()) { return } - - val devices = displayNameUpdatees + val hexEncodedPublicKeys = displayNameUpdatees displayNameUpdatees = setOf() - - api.getDisplayNames(devices, group.server).successBackground { mapping -> + api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping -> for (pair in mapping.entries) { val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})" DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName) } }.fail { - // Retry next time - displayNameUpdatees = displayNameUpdatees.union(devices) + displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys) } } From 5da4892d9d16faae2a04a35201b5b7dd8c7f3725 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 18 Nov 2019 13:19:25 +1100 Subject: [PATCH 4/4] Ensure UI updates happen on the main thread --- .../securesms/loki/LokiPublicChatPoller.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt index 9db5c22bdb..16a50d67df 100644 --- a/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/LokiPublicChatPoller.kt @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.jobs.PushDecryptJob import org.thoughtcrime.securesms.util.TextSecurePreferences +import org.thoughtcrime.securesms.util.Util import org.whispersystems.libsignal.util.guava.Optional import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.messages.SignalServiceContent @@ -159,10 +160,12 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val senderPublicKey = primaryDevice ?: message.hexEncodedPublicKey val serviceDataMessage = getDataMessage(message) val serviceContent = SignalServiceContent(serviceDataMessage, senderPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.timestamp, false) - if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { - PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) - } else { - PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + Util.runOnMain { + if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { + PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + } else { + PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) + } } } fun processOutgoingMessage(message: LokiPublicChatMessage) { @@ -174,10 +177,12 @@ class LokiPublicChatPoller(private val context: Context, private val group: Loki val dataMessage = getDataMessage(message) val transcript = SentTranscriptMessage(localNumber, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(localNumber, false)) transcript.messageServerID = messageServerID - if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { - PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) - } else { - PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) + Util.runOnMain { + if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { + PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) + } else { + PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript) + } } } var userDevices = setOf()