session-android/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt

166 lines
7.2 KiB
Kotlin

package org.thoughtcrime.securesms.groups
import android.content.Context
import androidx.annotation.WorkerThread
import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.GroupMemberRole
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import java.util.concurrent.Executors
object OpenGroupManager {
private val executorService = Executors.newScheduledThreadPool(4)
private var pollers = mutableMapOf<String, OpenGroupPoller>() // One for each server
private var isPolling = false
private val pollUpdaterLock = Any()
val isAllCaughtUp: Boolean
get() {
pollers.values.forEach { poller ->
val jobID = poller.secondToLastJob?.id
jobID?.let {
val storage = MessagingModuleConfiguration.shared.storage
if (storage.getMessageReceiveJob(jobID) == null) {
// If the second to last job is done, it means we are now handling the last job
poller.isCaughtUp = true
poller.secondToLastJob = null
}
}
if (!poller.isCaughtUp) { return false }
}
return true
}
fun startPolling() {
if (isPolling) { return }
isPolling = true
val storage = MessagingModuleConfiguration.shared.storage
val servers = storage.getAllOpenGroups().values.map { it.server }.toSet()
synchronized(pollUpdaterLock) {
servers.forEach { server ->
pollers[server]?.stop() // Shouldn't be necessary
val poller = OpenGroupPoller(server, executorService)
poller.startIfNeeded()
pollers[server] = poller
}
}
}
fun stopPolling() {
synchronized(pollUpdaterLock) {
pollers.forEach { it.value.stop() }
pollers.clear()
isPolling = false
}
}
@WorkerThread
fun add(server: String, room: String, publicKey: String, context: Context): OpenGroupApi.RoomInfo? {
val openGroupID = "$server.$room"
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
val storage = MessagingModuleConfiguration.shared.storage
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
// Check it it's added already
val existingOpenGroup = threadDB.getOpenGroupChat(threadID)
if (existingOpenGroup != null) { return null }
// Clear any existing data if needed
storage.removeLastDeletionServerID(room, server)
storage.removeLastMessageServerID(room, server)
storage.removeLastInboxMessageId(server)
storage.removeLastOutboxMessageId(server)
// Store the public key
storage.setOpenGroupPublicKey(server, publicKey)
// Get capabilities & room info
val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get()
storage.setServerCapabilities(server, capabilities.capabilities)
// Create the group locally if not available already
if (threadID < 0) {
GroupManager.createOpenGroup(openGroupID, context, null, info.name)
}
OpenGroupPoller.handleRoomPollInfo(
server = server,
roomToken = room,
pollInfo = info.toPollInfo(),
createGroupIfMissingWithPublicKey = publicKey
)
return info
}
fun restartPollerForServer(server: String) {
// Start the poller if needed
synchronized(pollUpdaterLock) {
pollers[server]?.stop()
pollers[server]?.startIfNeeded() ?: run {
val poller = OpenGroupPoller(server, executorService)
Log.d("Loki", "Starting poller for open group: $server")
pollers[server] = poller
poller.startIfNeeded()
}
}
}
fun delete(server: String, room: String, context: Context) {
val storage = MessagingModuleConfiguration.shared.storage
val threadDB = DatabaseComponent.get(context).threadDatabase()
val openGroupID = "$server.$room"
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
val recipient = threadDB.getRecipientForThreadId(threadID) ?: return
threadDB.setThreadArchived(threadID)
val groupID = recipient.address.serialize()
// Stop the poller if needed
val openGroups = storage.getAllOpenGroups().filter { it.value.server == server }
if (openGroups.count() == 1) {
synchronized(pollUpdaterLock) {
val poller = pollers[server]
poller?.stop()
pollers.remove(server)
}
}
// Delete
storage.removeLastDeletionServerID(room, server)
storage.removeLastMessageServerID(room, server)
storage.removeLastInboxMessageId(server)
storage.removeLastOutboxMessageId(server)
val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase()
lokiThreadDB.removeOpenGroupChat(threadID)
ThreadUtils.queue {
threadDB.deleteConversation(threadID) // Must be invoked on a background thread
GroupManager.deleteGroup(groupID, context) // Must be invoked on a background thread
}
}
fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? {
val url = HttpUrl.parse(urlAsString) ?: return null
val server = OpenGroup.getServer(urlAsString)
val room = url.pathSegments().firstOrNull() ?: return null
val publicKey = url.queryParameter("public_key") ?: return null
return add(server.toString().removeSuffix("/"), room, publicKey, context) // assume migrated from calling function
}
fun updateOpenGroup(openGroup: OpenGroup, context: Context) {
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
val openGroupID = "${openGroup.server}.${openGroup.room}"
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
threadDB.setOpenGroupChat(openGroup, threadID)
}
fun isUserModerator(context: Context, groupId: String, standardPublicKey: String, blindedPublicKey: String? = null): Boolean {
val memberDatabase = DatabaseComponent.get(context).groupMemberDatabase()
val standardRoles = memberDatabase.getGroupMemberRoles(groupId, standardPublicKey)
val blindedRoles = blindedPublicKey?.let { memberDatabase.getGroupMemberRoles(groupId, it) } ?: emptyList()
// roles to check against
val moderatorRoles = listOf(
GroupMemberRole.MODERATOR, GroupMemberRole.ADMIN,
GroupMemberRole.HIDDEN_MODERATOR, GroupMemberRole.HIDDEN_ADMIN
)
return standardRoles.any { it in moderatorRoles } || blindedRoles.any { it in moderatorRoles }
}
}