feat: adding a force new configs flag and logic for timestamp handling / forced configs, fix issue with handling legacy messages

This commit is contained in:
0x330a 2023-05-22 14:59:03 +10:00
parent 371fb20b6e
commit 9c206bad64
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
10 changed files with 59 additions and 27 deletions

View File

@ -94,14 +94,8 @@ import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol, open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol,
ThreadDatabase.ConversationThreadUpdateListener { ThreadDatabase.ConversationThreadUpdateListener {
// TODO: maybe add time here from formation / creation message
override fun threadCreated(address: Address, threadId: Long) { override fun threadCreated(address: Address, threadId: Long) {
if (!getRecipientApproved(address)) return // don't store unapproved / message requests if (!getRecipientApproved(address)) return // don't store unapproved / message requests
if (getUserPublicKey() == address.serialize()) {
Log.d("Loki-DBG", "NTS created, context:\n${Thread.currentThread().stackTrace.joinToString("\n")}")
} else {
Log.d("Loki-DBG", "Thread created ${address.serialize()}")
}
val volatile = configFactory.convoVolatile ?: return val volatile = configFactory.convoVolatile ?: return
if (address.isGroup) { if (address.isGroup) {
@ -491,7 +485,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
private fun updateUserGroups(userGroups: UserGroupsConfig) { private fun updateUserGroups(userGroups: UserGroupsConfig) {
val threadDb = DatabaseComponent.get(context).threadDatabase() val threadDb = DatabaseComponent.get(context).threadDatabase()
val localUserPublicKey = getUserPublicKey() ?: return Log.w( val localUserPublicKey = getUserPublicKey() ?: return Log.w(
"Loki-DBG", "Loki",
"No user public key when trying to update user groups from config" "No user public key when trying to update user groups from config"
) )
val communities = userGroups.allCommunityInfo() val communities = userGroups.allCommunityInfo()

View File

@ -596,6 +596,8 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND); db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND);
db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_GROUPS); db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_GROUPS);
db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_ONE_TO_ONES); db.execSQL(ConfigurationMessageUtilities.DELETE_INACTIVE_ONE_TO_ONES);
// TODO: remove this for release
TextSecurePreferences.setForceNewConfig(context);
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();

View File

@ -6,13 +6,14 @@ import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.UserGroupsConfig import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile import network.loki.messenger.libsession_util.UserProfile
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.ConfigFactoryUpdateListener import org.session.libsession.utilities.ConfigFactoryUpdateListener
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.ConfigDatabase
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import java.util.concurrent.Executors
class ConfigFactory( class ConfigFactory(
private val context: Context, private val context: Context,
@ -39,6 +40,8 @@ class ConfigFactory(
private val userGroupsLock = Object() private val userGroupsLock = Object()
private var _userGroups: UserGroupsConfig? = null private var _userGroups: UserGroupsConfig? = null
private val isConfigForcedOn = TextSecurePreferences.hasForcedNewConfig(context)
private val listeners: MutableList<ConfigFactoryUpdateListener> = mutableListOf() private val listeners: MutableList<ConfigFactoryUpdateListener> = mutableListOf()
fun registerListener(listener: ConfigFactoryUpdateListener) { fun registerListener(listener: ConfigFactoryUpdateListener) {
listeners += listener listeners += listener
@ -50,7 +53,7 @@ class ConfigFactory(
override val user: UserProfile? override val user: UserProfile?
get() = synchronized(userLock) { get() = synchronized(userLock) {
if (!ConfigBase.isNewConfigEnabled) return null if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_userConfig == null) { if (_userConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
val userDump = configDatabase.retrieveConfigAndHashes( val userDump = configDatabase.retrieveConfigAndHashes(
@ -70,7 +73,7 @@ class ConfigFactory(
override val contacts: Contacts? override val contacts: Contacts?
get() = synchronized(contactsLock) { get() = synchronized(contactsLock) {
if (!ConfigBase.isNewConfigEnabled) return null if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_contacts == null) { if (_contacts == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
val contactsDump = configDatabase.retrieveConfigAndHashes( val contactsDump = configDatabase.retrieveConfigAndHashes(
@ -90,7 +93,7 @@ class ConfigFactory(
override val convoVolatile: ConversationVolatileConfig? override val convoVolatile: ConversationVolatileConfig?
get() = synchronized(convoVolatileLock) { get() = synchronized(convoVolatileLock) {
if (!ConfigBase.isNewConfigEnabled) return null if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_convoVolatileConfig == null) { if (_convoVolatileConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
val convoDump = configDatabase.retrieveConfigAndHashes( val convoDump = configDatabase.retrieveConfigAndHashes(
@ -111,7 +114,7 @@ class ConfigFactory(
override val userGroups: UserGroupsConfig? override val userGroups: UserGroupsConfig?
get() = synchronized(userGroupsLock) { get() = synchronized(userGroupsLock) {
if (!ConfigBase.isNewConfigEnabled) return null if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (_userGroups == null) { if (_userGroups == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null val (secretKey, publicKey) = maybeGetUserInfo() ?: return@synchronized null
val userGroupsDump = configDatabase.retrieveConfigAndHashes( val userGroupsDump = configDatabase.retrieveConfigAndHashes(
@ -174,7 +177,7 @@ class ConfigFactory(
else -> throw UnsupportedOperationException("Can't support type of ${forConfigObject::class.simpleName} yet") else -> throw UnsupportedOperationException("Can't support type of ${forConfigObject::class.simpleName} yet")
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Loki-DBG", e) Log.e("Loki", "failed to persist ${forConfigObject.javaClass.simpleName}", e)
} }
} }
} }

View File

@ -354,7 +354,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
} }
private fun updateLegacyConfigView() { private fun updateLegacyConfigView() {
binding.configOutdatedView.isVisible = ConfigBase.isNewConfigEnabled && textSecurePreferences.getHasLegacyConfig() binding.configOutdatedView.isVisible = ConfigBase.isNewConfigEnabled(textSecurePreferences.hasForcedNewConfig(), SnodeAPI.nowWithOffset)
&& textSecurePreferences.getHasLegacyConfig()
} }
override fun onResume() { override fun onResume() {

View File

@ -18,6 +18,7 @@ import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -25,7 +26,6 @@ import org.session.libsession.utilities.WindowDebouncer
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.GroupMemberDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import java.util.Timer import java.util.Timer
@ -53,7 +53,9 @@ object ConfigurationMessageUtilities {
fun syncConfigurationIfNeeded(context: Context) { fun syncConfigurationIfNeeded(context: Context) {
// add if check here to schedule new config job process and return early // add if check here to schedule new config job process and return early
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
if (ConfigBase.isNewConfigEnabled) { val forcedConfig = TextSecurePreferences.hasForcedNewConfig(context)
val currentTime = SnodeAPI.nowWithOffset
if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) {
scheduleConfigSync(userPublicKey) scheduleConfigSync(userPublicKey)
return return
} }
@ -81,7 +83,9 @@ object ConfigurationMessageUtilities {
fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> { fun forceSyncConfigurationNowIfNeeded(context: Context): Promise<Unit, Exception> {
// add if check here to schedule new config job process and return early // add if check here to schedule new config job process and return early
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null")) val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return Promise.ofFail(NullPointerException("User Public Key is null"))
if (ConfigBase.isNewConfigEnabled) { val forcedConfig = TextSecurePreferences.hasForcedNewConfig(context)
val currentTime = SnodeAPI.nowWithOffset
if (ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)) {
// schedule job if none exist // schedule job if none exist
// don't schedule job if we already have one // don't schedule job if we already have one
scheduleConfigSync(userPublicKey) scheduleConfigSync(userPublicKey)

View File

@ -25,7 +25,11 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) {
is UserGroupsConfig -> Kind.GROUPS is UserGroupsConfig -> Kind.GROUPS
} }
const val isNewConfigEnabled = true // TODO: time in future to activate (hardcoded to 1st jan 2024 for testing, change before release)
private const val ACTIVATE_TIME = 1704027600
fun isNewConfigEnabled(forced: Boolean, currentTime: Long) =
forced || currentTime >= ACTIVATE_TIME
const val PRIORITY_HIDDEN = -1 const val PRIORITY_HIDDEN = -1
const val PRIORITY_VISIBLE = 0 const val PRIORITY_VISIBLE = 0

View File

@ -2,14 +2,13 @@ package org.session.libsession.messaging.jobs
import network.loki.messenger.libsession_util.ConfigBase import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor import network.loki.messenger.libsession_util.ConfigBase.Companion.protoKindFor
import nl.komponents.kovenant.functional.bind
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.SharedConfigurationMessage import org.session.libsession.messaging.messages.control.SharedConfigurationMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.Data import org.session.libsession.messaging.utilities.Data
import org.session.libsession.snode.RawResponse
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -25,12 +24,14 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
override suspend fun execute(dispatcherName: String) { override suspend fun execute(dispatcherName: String) {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val forcedConfig = TextSecurePreferences.hasForcedNewConfig(MessagingModuleConfiguration.shared.context)
val currentTime = SnodeAPI.nowWithOffset
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()
val userPublicKey = storage.getUserPublicKey() val userPublicKey = storage.getUserPublicKey()
val delegate = delegate val delegate = delegate
if (destination is Destination.ClosedGroup // TODO: closed group configs will be handled in closed group feature 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 // if we haven't enabled the new configs don't run
|| !ConfigBase.isNewConfigEnabled || !ConfigBase.isNewConfigEnabled(forcedConfig, currentTime)
// if we don't have a user ed key pair for signing updates // if we don't have a user ed key pair for signing updates
|| userEdKeyPair == null || userEdKeyPair == null
// this will be useful to not handle null delegate cases // this will be useful to not handle null delegate cases

View File

@ -149,8 +149,11 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) {
TextSecurePreferences.setConfigurationMessageSynced(context, true) TextSecurePreferences.setConfigurationMessageSynced(context, true)
TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!) TextSecurePreferences.setLastProfileUpdateTime(context, message.sentTimestamp!!)
if (ConfigBase.isNewConfigEnabled) { val isForceSync = TextSecurePreferences.hasForcedNewConfig(context)
val currentTime = SnodeAPI.nowWithOffset
if (ConfigBase.isNewConfigEnabled(isForceSync, currentTime)) {
TextSecurePreferences.setHasLegacyConfig(context, true) TextSecurePreferences.setHasLegacyConfig(context, true)
if (!firstTimeSync) return
} }
val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys() val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys()
for (closedGroup in message.closedGroups) { for (closedGroup in message.closedGroups) {

View File

@ -151,7 +151,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(), openGroupServerID = null) val (message, _) = MessageReceiver.parse(data = envelope.toByteArray(), openGroupServerID = null)
// sanity checks // sanity checks
if (message !is SharedConfigurationMessage) { if (message !is SharedConfigurationMessage) {
Log.w("Loki-DBG", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}") Log.w("Loki", "shared config message handled in configs wasn't SharedConfigurationMessage but was ${message.javaClass.simpleName}")
return@forEach return@forEach
} }
forConfigObject.merge(hash!! to message.data) forConfigObject.merge(hash!! to message.data)
@ -213,11 +213,11 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
if (personalResponseIndex >= 0) { if (personalResponseIndex >= 0) {
responseList.getOrNull(personalResponseIndex)?.let { rawResponse -> responseList.getOrNull(personalResponseIndex)?.let { rawResponse ->
if (rawResponse["code"] as? Int != 200) { if (rawResponse["code"] as? Int != 200) {
Log.e("Loki-DBG", "Batch sub-request for personal messages had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}") Log.e("Loki", "Batch sub-request for personal messages had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}")
} else { } else {
val body = rawResponse["body"] as? RawResponse val body = rawResponse["body"] as? RawResponse
if (body == null) { if (body == null) {
Log.e("Loki-DBG", "Batch sub-request for personal messages didn't contain a body") Log.e("Loki", "Batch sub-request for personal messages didn't contain a body")
} else { } else {
processPersonalMessages(snode, body) processPersonalMessages(snode, body)
} }
@ -230,12 +230,12 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) -> requestSparseArray.keyIterator().withIndex().forEach { (requestIndex, key) ->
responseList.getOrNull(requestIndex)?.let { rawResponse -> responseList.getOrNull(requestIndex)?.let { rawResponse ->
if (rawResponse["code"] as? Int != 200) { 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]"}") Log.e("Loki", "Batch sub-request had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}")
return@forEach return@forEach
} }
val body = rawResponse["body"] as? RawResponse val body = rawResponse["body"] as? RawResponse
if (body == null) { if (body == null) {
Log.e("Loki-DBG", "Batch sub-request didn't contain a body") Log.e("Loki", "Batch sub-request didn't contain a body")
return@forEach return@forEach
} }
if (key == Namespace.DEFAULT) { if (key == Namespace.DEFAULT) {

View File

@ -180,6 +180,8 @@ interface TextSecurePreferences {
fun setThemeStyle(themeStyle: String) fun setThemeStyle(themeStyle: String)
fun setFollowSystemSettings(followSystemSettings: Boolean) fun setFollowSystemSettings(followSystemSettings: Boolean)
fun autoplayAudioMessages(): Boolean fun autoplayAudioMessages(): Boolean
fun setForceNewConfig()
fun hasForcedNewConfig(): Boolean
fun hasPreference(key: String): Boolean fun hasPreference(key: String): Boolean
fun clearAll() fun clearAll()
@ -268,6 +270,7 @@ interface TextSecurePreferences {
const val SELECTED_ACCENT_COLOR = "selected_accent_color" const val SELECTED_ACCENT_COLOR = "selected_accent_color"
const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config" const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_config"
const val GREEN_ACCENT = "accent_green" const val GREEN_ACCENT = "accent_green"
const val BLUE_ACCENT = "accent_blue" const val BLUE_ACCENT = "accent_blue"
@ -811,6 +814,16 @@ interface TextSecurePreferences {
setIntegerPreference(context, NOTIFICATION_MESSAGES_CHANNEL_VERSION, version) setIntegerPreference(context, NOTIFICATION_MESSAGES_CHANNEL_VERSION, version)
} }
@JvmStatic
fun setForceNewConfig(context: Context) {
setBooleanPreference(context, HAS_FORCED_NEW_CONFIG, true)
}
@JvmStatic
fun hasForcedNewConfig(context: Context): Boolean {
return getBooleanPreference(context, HAS_FORCED_NEW_CONFIG, false)
}
@JvmStatic @JvmStatic
fun getBooleanPreference(context: Context, key: String?, defaultValue: Boolean): Boolean { fun getBooleanPreference(context: Context, key: String?, defaultValue: Boolean): Boolean {
return getDefaultSharedPreferences(context).getBoolean(key, defaultValue) return getDefaultSharedPreferences(context).getBoolean(key, defaultValue)
@ -1447,6 +1460,13 @@ class AppTextSecurePreferences @Inject constructor(
setIntegerPreference(TextSecurePreferences.NOTIFICATION_MESSAGES_CHANNEL_VERSION, version) setIntegerPreference(TextSecurePreferences.NOTIFICATION_MESSAGES_CHANNEL_VERSION, version)
} }
override fun setForceNewConfig() {
setBooleanPreference(TextSecurePreferences.HAS_FORCED_NEW_CONFIG, true)
}
override fun hasForcedNewConfig(): Boolean =
getBooleanPreference(TextSecurePreferences.HAS_FORCED_NEW_CONFIG, false)
override fun getBooleanPreference(key: String?, defaultValue: Boolean): Boolean { override fun getBooleanPreference(key: String?, defaultValue: Boolean): Boolean {
return getDefaultSharedPreferences(context).getBoolean(key, defaultValue) return getDefaultSharedPreferences(context).getBoolean(key, defaultValue)
} }