diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index ef47269107..6a342eacda 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -41,11 +41,13 @@ object OpenGroupManager { isPolling = true val storage = MessagingModuleConfiguration.shared.storage val servers = storage.getAllOpenGroups().values.map { it.server }.toSet() - servers.forEach { server -> - pollers[server]?.stop() // Shouldn't be necessary - val poller = OpenGroupPoller(server, executorService) - poller.startIfNeeded() - pollers[server] = poller + synchronized(pollUpdaterLock) { + servers.forEach { server -> + pollers[server]?.stop() // Shouldn't be necessary + val poller = OpenGroupPoller(server, executorService) + poller.startIfNeeded() + pollers[server] = poller + } } } @@ -60,7 +62,7 @@ object OpenGroupManager { @WorkerThread fun add(server: String, room: String, publicKey: String, context: Context): OpenGroupApi.RoomInfo? { val openGroupID = "$server.$room" - var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) + val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) val storage = MessagingModuleConfiguration.shared.storage val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() // Check it it's added already @@ -76,13 +78,16 @@ object OpenGroupManager { // Get capabilities & room info val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get() storage.setServerCapabilities(server, capabilities.capabilities) - storage.setUserCount(room, server, info.activeUsers) // Create the group locally if not available already if (threadID < 0) { - threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId + GroupManager.createOpenGroup(openGroupID, context, null, info.name) } - val openGroup = OpenGroup(server = server, room = room, publicKey = publicKey, name = info.name, imageId = info.imageId, canWrite = info.write, infoUpdates = info.infoUpdates) - threadDB.setOpenGroupChat(openGroup, threadID) + OpenGroupPoller.handleRoomPollInfo( + server = server, + roomToken = room, + pollInfo = info.toPollInfo(), + createGroupIfMissingWithPublicKey = publicKey + ) return info } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt index 5154101328..c5ec1bc74e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt @@ -39,13 +39,7 @@ class BackgroundGroupAddJob(val joinUrl: String): Job { delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException()) return } - // get image - storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey) - val info = storage.addOpenGroup(openGroup.joinUrl()) - val imageId = info?.imageId - if (imageId != null && storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId) == null) { - JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId)) - } + storage.addOpenGroup(openGroup.joinUrl()) Log.d(KEY, "onOpenGroupAdded(${openGroup.server})") storage.onOpenGroupAdded(openGroup.server) } catch (e: Exception) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index ca60fd3cbf..a05addcee2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -109,7 +109,26 @@ object OpenGroupApi { val defaultWrite: Boolean = false, val upload: Boolean = false, val defaultUpload: Boolean = false, - ) + ) { + fun toPollInfo(): RoomPollInfo { + return RoomPollInfo( + token = token, + activeUsers = activeUsers, + admin = admin, + globalAdmin = globalAdmin, + moderator = moderator, + globalModerator = globalModerator, + read = read, + defaultRead = defaultRead, + defaultAccessible = defaultAccessible, + write = write, + defaultWrite = defaultWrite, + upload = upload, + defaultUpload = defaultUpload, + details = this + ) + } + } @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) data class PinnedMessage( 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 562ddda699..fb7b3b6c8a 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 @@ -30,6 +30,7 @@ import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.successBackground +import java.util.UUID import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit @@ -39,15 +40,97 @@ class OpenGroupPoller(private val server: String, private val executorService: S var isCaughtUp = false var secondToLastJob: MessageReceiveJob? = null private var future: ScheduledFuture<*>? = null + private var runId: UUID = UUID.randomUUID() companion object { private const val pollInterval: Long = 4000L const val maxInactivityPeriod = 14 * 24 * 60 * 60 * 1000 + + public fun handleRoomPollInfo( + server: String, + roomToken: String, + pollInfo: OpenGroupApi.RoomPollInfo, + createGroupIfMissingWithPublicKey: String? = null + ) { + val storage = MessagingModuleConfiguration.shared.storage + val groupId = "$server.$roomToken" + val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray()) + val existingOpenGroup = storage.getOpenGroup(roomToken, server) + + // If we don't have an existing group and don't have a 'createGroupIfMissingWithPublicKey' + // value then don't process the poll info + val publicKey = ((existingOpenGroup?.publicKey ?: createGroupIfMissingWithPublicKey) ?: return) + + val openGroup = OpenGroup( + server = server, + room = pollInfo.token, + name = ((pollInfo.details?.name ?: existingOpenGroup?.name) ?: ""), + publicKey = publicKey, + imageId = (pollInfo.details?.imageId ?: existingOpenGroup?.imageId), + canWrite = pollInfo.write, + infoUpdates = ((pollInfo.details?.infoUpdates ?: existingOpenGroup?.infoUpdates) ?: 0) + ) + // - Open Group changes + storage.updateOpenGroup(openGroup) + + // - User Count + storage.setUserCount(roomToken, server, pollInfo.activeUsers) + + // - Moderators + pollInfo.details?.moderators?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.MODERATOR) + }) + } + pollInfo.details?.hiddenModerators?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR) + }) + } + // - Admins + pollInfo.details?.admins?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.ADMIN) + }) + } + pollInfo.details?.hiddenAdmins?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) + }) + } + + // Update the group avatar + if ( + ( + pollInfo.details != null && + pollInfo.details.imageId != null && ( + pollInfo.details.imageId != existingOpenGroup?.imageId || + !storage.hasDownloadedProfilePicture(dbGroupId) + ) && + storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null + ) || ( + pollInfo.details == null && + existingOpenGroup?.imageId != null && + !storage.hasDownloadedProfilePicture(dbGroupId) && + storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null + ) + ) { + JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, openGroup.imageId)) + } + else if ( + pollInfo.details != null && + pollInfo.details.imageId == null && + existingOpenGroup?.imageId != null + ) { + storage.removeProfilePicture(dbGroupId) + } + } } fun startIfNeeded() { if (hasStarted) { return } hasStarted = true + runId = UUID.randomUUID() future = executorService?.schedule(::poll, 0, TimeUnit.MILLISECONDS) } @@ -57,6 +140,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S } fun poll(isPostCapabilitiesRetry: Boolean = false): Promise { + val currentRunId = runId val storage = MessagingModuleConfiguration.shared.storage val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room } @@ -86,22 +170,30 @@ class OpenGroupPoller(private val server: String, private val executorService: S isCaughtUp = true } } - executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) + + // Only poll again if it's the same poller run + if (currentRunId == runId) { + future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) + } }.fail { - updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, it) + updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, currentRunId, it) }.map { } } - private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, exception: Exception) { + private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, currentRunId: UUID, exception: Exception) { if (exception is OnionRequestAPI.HTTPRequestFailedBlindingRequiredException) { if (!isPostCapabilitiesRetry) { OpenGroupApi.getCapabilities(server).map { handleCapabilities(server, it) } - executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS) + + // Only poll again if it's the same poller run + if (currentRunId == runId) { + future = executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS) + } } - } else { - executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) + } else if (currentRunId == runId) { + future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) } } @@ -110,82 +202,6 @@ class OpenGroupPoller(private val server: String, private val executorService: S storage.setServerCapabilities(server, capabilities.capabilities) } - private fun handleRoomPollInfo( - server: String, - roomToken: String, - pollInfo: OpenGroupApi.RoomPollInfo - ) { - val storage = MessagingModuleConfiguration.shared.storage - val groupId = "$server.$roomToken" - val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray()) - - val existingOpenGroup = storage.getOpenGroup(roomToken, server) - val publicKey = existingOpenGroup?.publicKey ?: return - val openGroup = OpenGroup( - server = server, - room = pollInfo.token, - name = if (pollInfo.details != null) { pollInfo.details.name } else { existingOpenGroup.name }, - publicKey = publicKey, - imageId = if (pollInfo.details != null) { pollInfo.details.imageId } else { existingOpenGroup.imageId }, - canWrite = pollInfo.write, - infoUpdates = if (pollInfo.details != null) { pollInfo.details.infoUpdates } else { existingOpenGroup.infoUpdates } - ) - // - Open Group changes - storage.updateOpenGroup(openGroup) - - // - User Count - storage.setUserCount(roomToken, server, pollInfo.activeUsers) - - // - Moderators - pollInfo.details?.moderators?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.MODERATOR) - }) - } - pollInfo.details?.hiddenModerators?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR) - }) - } - // - Admins - pollInfo.details?.admins?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.ADMIN) - }) - } - pollInfo.details?.hiddenAdmins?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) - }) - } - - // Update the group avatar - if ( - ( - pollInfo.details != null && - pollInfo.details.imageId != null && ( - pollInfo.details.imageId != existingOpenGroup.imageId || - !storage.hasDownloadedProfilePicture(dbGroupId) - ) && - storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null - ) || ( - pollInfo.details == null && - existingOpenGroup.imageId != null && - !storage.hasDownloadedProfilePicture(dbGroupId) && - storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null - ) - ) { - JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, existingOpenGroup.imageId)) - } - else if ( - pollInfo.details != null && - pollInfo.details.imageId == null && - existingOpenGroup.imageId != null - ) { - storage.removeProfilePicture(dbGroupId) - } - } - private fun handleMessages( server: String, roomToken: String,