Tweaked some open group handling and a couple of onboarding issues

Updated the OpenGroup adding and polling logic to reduce duplicate API calls
Updated the BackgroundGroupAddJob to start a GroupAvatarDownloadJob instead of running the download itself (to appear to run faster)
Defaulted OpenGroups to use blinded auth when no server capabilities are present
Fixed an issue where the background poller could be started even though the onboarding hadn't been completed
Fixed an issue where the database could get into an invalid state if the app was restarted during onboarding
This commit is contained in:
Morgan Pretty 2023-01-09 15:22:29 +11:00
parent d0a4bac83e
commit 5afd647686
13 changed files with 89 additions and 55 deletions

View File

@ -245,6 +245,12 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
Log.i(TAG, "App is now visible."); Log.i(TAG, "App is now visible.");
KeyCachingService.onAppForegrounded(this); KeyCachingService.onAppForegrounded(this);
// If the user account hasn't been created or onboarding wasn't finished then don't start
// the pollers
if (TextSecurePreferences.getLocalNumber(this) == null || !TextSecurePreferences.hasSeenWelcomeScreen(this)) {
return;
}
ThreadUtils.queue(()->{ ThreadUtils.queue(()->{
if (poller != null) { if (poller != null) {
poller.setCaughtUp(false); poller.setCaughtUp(false);

View File

@ -300,6 +300,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val lastHash = database.insertOrUpdate(lastMessageHashValueTable2, row, query, arrayOf( snode.toString(), publicKey, namespace.toString() )) val lastHash = database.insertOrUpdate(lastMessageHashValueTable2, row, query, arrayOf( snode.toString(), publicKey, namespace.toString() ))
} }
override fun clearAllLastMessageHashes() {
val database = databaseHelper.writableDatabase
database.delete(lastMessageHashValueTable2, null, null)
}
override fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set<String>? { override fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set<String>? {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
val query = "${Companion.publicKey} = ? AND ${Companion.receivedMessageHashNamespace} = ?" val query = "${Companion.publicKey} = ? AND ${Companion.receivedMessageHashNamespace} = ?"
@ -321,6 +326,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
database.insertOrUpdate(receivedMessageHashValuesTable, row, query, arrayOf( publicKey, namespace.toString() )) database.insertOrUpdate(receivedMessageHashValuesTable, row, query, arrayOf( publicKey, namespace.toString() ))
} }
override fun clearReceivedMessageHashValues() {
val database = databaseHelper.writableDatabase
database.delete(receivedMessageHashValuesTable, null, null)
}
override fun getAuthToken(server: String): String? { override fun getAuthToken(server: String): String? {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
return database.get(openGroupAuthTokenTable, "${Companion.server} = ?", wrap(server)) { cursor -> return database.get(openGroupAuthTokenTable, "${Companion.server} = ?", wrap(server)) { cursor ->

View File

@ -58,14 +58,14 @@ object OpenGroupManager {
} }
@WorkerThread @WorkerThread
fun add(server: String, room: String, publicKey: String, context: Context) { fun add(server: String, room: String, publicKey: String, context: Context): OpenGroupApi.RoomInfo? {
val openGroupID = "$server.$room" val openGroupID = "$server.$room"
var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
// Check it it's added already // Check it it's added already
val existingOpenGroup = threadDB.getOpenGroupChat(threadID) val existingOpenGroup = threadDB.getOpenGroupChat(threadID)
if (existingOpenGroup != null) { return } if (existingOpenGroup != null) { return null }
// Clear any existing data if needed // Clear any existing data if needed
storage.removeLastDeletionServerID(room, server) storage.removeLastDeletionServerID(room, server)
storage.removeLastMessageServerID(room, server) storage.removeLastMessageServerID(room, server)
@ -73,18 +73,17 @@ object OpenGroupManager {
storage.removeLastOutboxMessageId(server) storage.removeLastOutboxMessageId(server)
// Store the public key // Store the public key
storage.setOpenGroupPublicKey(server, publicKey) storage.setOpenGroupPublicKey(server, publicKey)
// Get capabilities // Get capabilities & room info
val capabilities = OpenGroupApi.getCapabilities(server).get() val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get()
storage.setServerCapabilities(server, capabilities.capabilities) storage.setServerCapabilities(server, capabilities.capabilities)
// Get room info
val info = OpenGroupApi.getRoomInfo(room, server).get()
storage.setUserCount(room, server, info.activeUsers) storage.setUserCount(room, server, info.activeUsers)
// Create the group locally if not available already // Create the group locally if not available already
if (threadID < 0) { if (threadID < 0) {
threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId
} }
val openGroup = OpenGroup(server, room, info.name, info.infoUpdates, publicKey) val openGroup = OpenGroup(server, room, publicKey, info.name, info.imageId, info.infoUpdates)
threadDB.setOpenGroupChat(openGroup, threadID) threadDB.setOpenGroupChat(openGroup, threadID)
return info
} }
fun restartPollerForServer(server: String) { fun restartPollerForServer(server: String) {
@ -130,12 +129,13 @@ object OpenGroupManager {
} }
} }
fun addOpenGroup(urlAsString: String, context: Context) { fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? {
val url = HttpUrl.parse(urlAsString) ?: return val url = HttpUrl.parse(urlAsString) ?: return null
val server = OpenGroup.getServer(urlAsString) val server = OpenGroup.getServer(urlAsString)
val room = url.pathSegments().firstOrNull() ?: return val room = url.pathSegments().firstOrNull() ?: return null
val publicKey = url.queryParameter("public_key") ?: return val publicKey = url.queryParameter("public_key") ?: return null
add(server.toString().removeSuffix("/"), room, publicKey, context) // assume migrated from calling function
return add(server.toString().removeSuffix("/"), room, publicKey, context) // assume migrated from calling function
} }
fun updateOpenGroup(openGroup: OpenGroup, context: Context) { fun updateOpenGroup(openGroup: OpenGroup, context: Context) {

View File

@ -44,7 +44,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
} }
override fun doWork(): Result { override fun doWork(): Result {
if (TextSecurePreferences.getLocalNumber(context) == null) { if (TextSecurePreferences.getLocalNumber(context) == null || !TextSecurePreferences.hasSeenWelcomeScreen(context)) {
Log.v(TAG, "User not registered yet.") Log.v(TAG, "User not registered yet.")
return Result.failure() return Result.failure()
} }

View File

@ -22,8 +22,10 @@ import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityLinkDeviceBinding import network.loki.messenger.databinding.ActivityLinkDeviceBinding
import network.loki.messenger.databinding.FragmentRecoveryPhraseBinding import network.loki.messenger.databinding.FragmentRecoveryPhraseBinding
import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.crypto.MnemonicCodec
import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
@ -39,6 +41,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private lateinit var binding: ActivityLinkDeviceBinding private lateinit var binding: ActivityLinkDeviceBinding
internal val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage
private val adapter = LinkDeviceActivityAdapter(this) private val adapter = LinkDeviceActivityAdapter(this)
private var restoreJob: Job? = null private var restoreJob: Job? = null
@ -99,6 +103,11 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel
if (restoreJob?.isActive == true) return if (restoreJob?.isActive == true) return
restoreJob = lifecycleScope.launch { restoreJob = lifecycleScope.launch {
// This is here to resolve a case where the app restarts before a user completes onboarding
// which can result in an invalid database state
database.clearAllLastMessageHashes()
database.clearReceivedMessageHashValues()
// RestoreActivity handles seed this way // RestoreActivity handles seed this way
val keyPairGenerationResult = KeyPairUtilities.generate(seed) val keyPairGenerationResult = KeyPairUtilities.generate(seed)
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair val x25519KeyPair = keyPairGenerationResult.x25519KeyPair

View File

@ -13,8 +13,10 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityRecoveryPhraseRestoreBinding import network.loki.messenger.databinding.ActivityRecoveryPhraseRestoreBinding
import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.crypto.MnemonicCodec
import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.hexEncodedPublicKey
@ -26,6 +28,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
private lateinit var binding: ActivityRecoveryPhraseRestoreBinding private lateinit var binding: ActivityRecoveryPhraseRestoreBinding
internal val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage
// region Lifecycle // region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -64,6 +68,11 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
private fun restore() { private fun restore() {
val mnemonic = binding.mnemonicEditText.text.toString() val mnemonic = binding.mnemonicEditText.text.toString()
try { try {
// This is here to resolve a case where the app restarts before a user completes onboarding
// which can result in an invalid database state
database.clearAllLastMessageHashes()
database.clearReceivedMessageHashValues()
val loadFileContents: (String) -> String = { fileName -> val loadFileContents: (String) -> String = { fileName ->
MnemonicUtilities.loadFileContents(this, fileName) MnemonicUtilities.loadFileContents(this, fileName)
} }

View File

@ -18,8 +18,10 @@ import android.widget.Toast
import com.goterl.lazysodium.utils.KeyPair import com.goterl.lazysodium.utils.KeyPair
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityRegisterBinding import network.loki.messenger.databinding.ActivityRegisterBinding
import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.KeyHelper
import org.session.libsignal.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.hexEncodedPublicKey
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
@ -29,6 +31,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
class RegisterActivity : BaseActionBarActivity() { class RegisterActivity : BaseActionBarActivity() {
private lateinit var binding: ActivityRegisterBinding private lateinit var binding: ActivityRegisterBinding
internal val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage
private var seed: ByteArray? = null private var seed: ByteArray? = null
private var ed25519KeyPair: KeyPair? = null private var ed25519KeyPair: KeyPair? = null
private var x25519KeyPair: ECKeyPair? = null private var x25519KeyPair: ECKeyPair? = null
@ -109,6 +113,11 @@ class RegisterActivity : BaseActionBarActivity() {
// region Interaction // region Interaction
private fun register() { private fun register() {
// This is here to resolve a case where the app restarts before a user completes onboarding
// which can result in an invalid database state
database.clearAllLastMessageHashes()
database.clearReceivedMessageHashValues()
KeyPairUtilities.store(this, seed!!, ed25519KeyPair!!, x25519KeyPair!!) KeyPairUtilities.store(this, seed!!, ed25519KeyPair!!, x25519KeyPair!!)
val userHexEncodedPublicKey = x25519KeyPair!!.hexEncodedPublicKey val userHexEncodedPublicKey = x25519KeyPair!!.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false) val registrationID = KeyHelper.generateRegistrationId(false)

View File

@ -41,15 +41,10 @@ class BackgroundGroupAddJob(val joinUrl: String): Job {
} }
// get image // get image
storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey) storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey)
val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(openGroup.room, openGroup.server, false).get() val info = storage.addOpenGroup(openGroup.joinUrl())
storage.setServerCapabilities(openGroup.server, capabilities.capabilities) val imageId = info?.imageId
val imageId = info.imageId
storage.addOpenGroup(openGroup.joinUrl())
if (imageId != null) { if (imageId != null) {
val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(openGroup.server, openGroup.room, imageId).get() JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.room, openGroup.server))
val groupId = GroupUtil.getEncodedOpenGroupID("${openGroup.server}.${openGroup.room}".toByteArray())
storage.updateProfilePicture(groupId, bytes)
storage.updateTimestampUpdated(groupId, System.currentTimeMillis())
} }
Log.d(KEY, "onOpenGroupAdded(${openGroup.server})") Log.d(KEY, "onOpenGroupAdded(${openGroup.server})")
storage.onOpenGroupAdded(openGroup.server) storage.onOpenGroupAdded(openGroup.server)

View File

@ -14,10 +14,9 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job {
override fun execute() { override fun execute() {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val imageId = storage.getOpenGroup(room, server)?.imageId ?: return
try { try {
val info = OpenGroupApi.getRoomInfo(room, server).get() val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(server, room, imageId).get()
val imageId = info.imageId ?: return
val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(server, info.token, imageId).get()
val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
storage.updateProfilePicture(groupId, bytes) storage.updateProfilePicture(groupId, bytes)
storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) storage.updateTimestampUpdated(groupId, System.currentTimeMillis())

View File

@ -11,15 +11,17 @@ data class OpenGroup(
val id: String, val id: String,
val name: String, val name: String,
val publicKey: String, val publicKey: String,
val imageId: String?,
val infoUpdates: Int, val infoUpdates: Int,
) { ) {
constructor(server: String, room: String, name: String, infoUpdates: Int, publicKey: String) : this( constructor(server: String, room: String, publicKey: String, name: String, imageId: String?, infoUpdates: Int) : this(
server = server, server = server,
room = room, room = room,
id = "$server.$room", id = "$server.$room",
name = name, name = name,
publicKey = publicKey, publicKey = publicKey,
imageId = imageId,
infoUpdates = infoUpdates, infoUpdates = infoUpdates,
) )
@ -31,11 +33,12 @@ data class OpenGroup(
if (!json.has("room")) return null if (!json.has("room")) return null
val room = json.get("room").asText().toLowerCase(Locale.US) val room = json.get("room").asText().toLowerCase(Locale.US)
val server = json.get("server").asText().toLowerCase(Locale.US) val server = json.get("server").asText().toLowerCase(Locale.US)
val displayName = json.get("displayName").asText()
val publicKey = json.get("publicKey").asText() val publicKey = json.get("publicKey").asText()
val displayName = json.get("displayName").asText()
val imageId = json.get("imageId")?.asText()
val infoUpdates = json.get("infoUpdates")?.asText()?.toIntOrNull() ?: 0 val infoUpdates = json.get("infoUpdates")?.asText()?.toIntOrNull() ?: 0
val capabilities = json.get("capabilities")?.asText()?.split(",") ?: emptyList() val capabilities = json.get("capabilities")?.asText()?.split(",") ?: emptyList()
OpenGroup(server, room, displayName, infoUpdates, publicKey) OpenGroup(server, room, displayName, publicKey, imageId, infoUpdates)
} catch (e: Exception) { } catch (e: Exception) {
Log.w("Loki", "Couldn't parse open group from JSON: $jsonAsString.", e); Log.w("Loki", "Couldn't parse open group from JSON: $jsonAsString.", e);
null null
@ -53,11 +56,12 @@ data class OpenGroup(
} }
} }
fun toJson(): Map<String,String> = mapOf( fun toJson(): Map<String,String?> = mapOf(
"room" to room, "room" to room,
"server" to server, "server" to server,
"displayName" to name,
"publicKey" to publicKey, "publicKey" to publicKey,
"displayName" to name,
"imageId" to imageId,
"infoUpdates" to infoUpdates.toString(), "infoUpdates" to infoUpdates.toString(),
) )

View File

@ -91,7 +91,7 @@ object OpenGroupApi {
val created: Long = 0, val created: Long = 0,
val activeUsers: Int = 0, val activeUsers: Int = 0,
val activeUsersCutoff: Int = 0, val activeUsersCutoff: Int = 0,
val imageId: Long? = null, val imageId: String? = null,
val pinnedMessages: List<PinnedMessage> = emptyList(), val pinnedMessages: List<PinnedMessage> = emptyList(),
val admin: Boolean = false, val admin: Boolean = false,
val globalAdmin: Boolean = false, val globalAdmin: Boolean = false,
@ -337,7 +337,7 @@ object OpenGroupApi {
.plus(request.verb.rawValue.toByteArray()) .plus(request.verb.rawValue.toByteArray())
.plus("/${request.endpoint.value}".toByteArray()) .plus("/${request.endpoint.value}".toByteArray())
.plus(bodyHash) .plus(bodyHash)
if (serverCapabilities.contains(Capability.BLIND.name.lowercase())) { if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair -> SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
pubKey = SessionId( pubKey = SessionId(
IdPrefix.BLINDED, IdPrefix.BLINDED,
@ -395,13 +395,13 @@ object OpenGroupApi {
fun downloadOpenGroupProfilePicture( fun downloadOpenGroupProfilePicture(
server: String, server: String,
roomID: String, roomID: String,
imageId: Long imageId: String
): Promise<ByteArray, Exception> { ): Promise<ByteArray, Exception> {
val request = Request( val request = Request(
verb = GET, verb = GET,
room = roomID, room = roomID,
server = server, server = server,
endpoint = Endpoint.RoomFileIndividual(roomID, imageId.toString()) endpoint = Endpoint.RoomFileIndividual(roomID, imageId)
) )
return getResponseBody(request) return getResponseBody(request)
} }
@ -794,16 +794,14 @@ object OpenGroupApi {
private fun sequentialBatch( private fun sequentialBatch(
server: String, server: String,
requests: MutableList<BatchRequestInfo<*>>, requests: MutableList<BatchRequestInfo<*>>
authRequired: Boolean = true
): Promise<List<BatchResponse<*>>, Exception> { ): Promise<List<BatchResponse<*>>, Exception> {
val request = Request( val request = Request(
verb = POST, verb = POST,
room = null, room = null,
server = server, server = server,
endpoint = Endpoint.Sequence, endpoint = Endpoint.Sequence,
parameters = requests.map { it.request }, parameters = requests.map { it.request }
isAuthRequired = authRequired
) )
return getBatchResponseJson(request, requests) return getBatchResponseJson(request, requests)
} }
@ -912,8 +910,7 @@ object OpenGroupApi {
fun getCapabilitiesAndRoomInfo( fun getCapabilitiesAndRoomInfo(
room: String, room: String,
server: String, server: String
authRequired: Boolean = true
): Promise<Pair<Capabilities, RoomInfo>, Exception> { ): Promise<Pair<Capabilities, RoomInfo>, Exception> {
val requests = mutableListOf<BatchRequestInfo<*>>( val requests = mutableListOf<BatchRequestInfo<*>>(
BatchRequestInfo( BatchRequestInfo(
@ -933,7 +930,7 @@ object OpenGroupApi {
responseType = object : TypeReference<RoomInfo>(){} responseType = object : TypeReference<RoomInfo>(){}
) )
) )
return sequentialBatch(server, requests, authRequired).map { return sequentialBatch(server, requests).map {
val capabilities = it.firstOrNull()?.body as? Capabilities ?: throw Error.ParsingFailed val capabilities = it.firstOrNull()?.body as? Capabilities ?: throw Error.ParsingFailed
val roomInfo = it.lastOrNull()?.body as? RoomInfo ?: throw Error.ParsingFailed val roomInfo = it.lastOrNull()?.body as? RoomInfo ?: throw Error.ParsingFailed
capabilities to roomInfo capabilities to roomInfo

View File

@ -59,7 +59,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S
fun poll(isPostCapabilitiesRetry: Boolean = false): Promise<Unit, Exception> { fun poll(isPostCapabilitiesRetry: Boolean = false): Promise<Unit, Exception> {
val storage = MessagingModuleConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room } val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room }
rooms.forEach { downloadGroupAvatarIfNeeded(it) }
return OpenGroupApi.poll(rooms, server).successBackground { responses -> return OpenGroupApi.poll(rooms, server).successBackground { responses ->
responses.filterNot { it.body == null }.forEach { response -> responses.filterNot { it.body == null }.forEach { response ->
when (response.endpoint) { when (response.endpoint) {
@ -123,9 +123,10 @@ class OpenGroupPoller(private val server: String, private val executorService: S
val openGroup = OpenGroup( val openGroup = OpenGroup(
server = server, server = server,
room = pollInfo.token, room = pollInfo.token,
name = pollInfo.details?.name ?: "", name = if (pollInfo.details != null) { pollInfo.details.name } else { existingOpenGroup.name },
infoUpdates = pollInfo.details?.infoUpdates ?: 0, infoUpdates = if (pollInfo.details != null) { pollInfo.details.infoUpdates } else { existingOpenGroup.infoUpdates },
publicKey = publicKey, publicKey = publicKey,
imageId = if (pollInfo.details != null) { pollInfo.details.imageId } else { existingOpenGroup.imageId }
) )
// - Open Group changes // - Open Group changes
storage.updateOpenGroup(openGroup) storage.updateOpenGroup(openGroup)
@ -155,6 +156,11 @@ class OpenGroupPoller(private val server: String, private val executorService: S
GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN)
}) })
} }
// Start downloading the room image (if we don't have one or it's been updated)
if (pollInfo.details?.imageId != null && pollInfo.details.imageId != existingOpenGroup.imageId) {
JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server))
}
} }
private fun handleMessages( private fun handleMessages(
@ -284,16 +290,4 @@ class OpenGroupPoller(private val server: String, private val executorService: S
JobQueue.shared.add(deleteJob) JobQueue.shared.add(deleteJob)
} }
} }
private fun downloadGroupAvatarIfNeeded(room: String) {
val storage = MessagingModuleConfiguration.shared.storage
if (storage.getGroupAvatarDownloadJob(server, room) != null) return
val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
storage.getGroup(groupId)?.let {
if (System.currentTimeMillis() > it.updatedTimestamp + TimeUnit.DAYS.toMillis(7)) {
JobQueue.shared.add(GroupAvatarDownloadJob(room, server))
}
}
}
} }

View File

@ -16,8 +16,10 @@ interface LokiAPIDatabaseProtocol {
fun setSwarm(publicKey: String, newValue: Set<Snode>) fun setSwarm(publicKey: String, newValue: Set<Snode>)
fun getLastMessageHashValue(snode: Snode, publicKey: String, namespace: Int): String? fun getLastMessageHashValue(snode: Snode, publicKey: String, namespace: Int): String?
fun setLastMessageHashValue(snode: Snode, publicKey: String, newValue: String, namespace: Int) fun setLastMessageHashValue(snode: Snode, publicKey: String, newValue: String, namespace: Int)
fun clearAllLastMessageHashes()
fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set<String>? fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set<String>?
fun setReceivedMessageHashValues(publicKey: String, newValue: Set<String>, namespace: Int) fun setReceivedMessageHashValues(publicKey: String, newValue: Set<String>, namespace: Int)
fun clearReceivedMessageHashValues()
fun getAuthToken(server: String): String? fun getAuthToken(server: String): String?
fun setAuthToken(server: String, newValue: String?) fun setAuthToken(server: String, newValue: String?)
fun setUserCount(room: String, server: String, newValue: Int) fun setUserCount(room: String, server: String, newValue: Int)