diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt index 72c76cd128..572cad0f46 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance @@ -33,6 +34,7 @@ import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigPushResult import org.session.libsession.utilities.ConfigUpdateNotification +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.UserConfigType import org.session.libsession.utilities.getGroup import org.session.libsignal.utilities.AccountId @@ -43,6 +45,7 @@ import org.session.libsignal.utilities.Snode import org.session.libsignal.utilities.retryWithUniformInterval import org.thoughtcrime.securesms.util.InternetConnectivity import javax.inject.Inject +import kotlin.math.log private const val TAG = "ConfigUploader" @@ -62,6 +65,7 @@ class ConfigUploader @Inject constructor( private val storageProtocol: StorageProtocol, private val clock: SnodeClock, private val internetConnectivity: InternetConnectivity, + private val textSecurePreferences: TextSecurePreferences, ) { private var job: Job? = null @@ -82,6 +86,11 @@ class ConfigUploader @Inject constructor( } } + // A flow that emits true when there's a logged in user + private fun hasLoggedInUser(): Flow = textSecurePreferences.watchLocalNumber() + .map { it != null } + .distinctUntilChanged() + @OptIn(DelicateCoroutinesApi::class, FlowPreview::class, ExperimentalCoroutinesApi::class) fun start() { @@ -92,41 +101,58 @@ class ConfigUploader @Inject constructor( // For any of these events, we need to push the user configs: // - The onion path has just become available to use // - The user configs have been modified + // Also, these events are only relevant when there's a logged in user val job1 = launch { - merge( - pathBecomesAvailable(), - configFactory.configUpdateNotifications - .filterIsInstance() - .debounce(1000L) - ).collect { - try { - retryWithUniformInterval { - pushUserConfigChangesIfNeeded() + hasLoggedInUser() + .flatMapLatest { loggedIn -> + if (loggedIn) { + merge( + pathBecomesAvailable(), + configFactory.configUpdateNotifications + .filterIsInstance() + .debounce(1000L) + ) + } else { + emptyFlow() + } + } + .collect { + try { + retryWithUniformInterval { + pushUserConfigChangesIfNeeded() + } + } catch (e: Exception) { + Log.e(TAG, "Failed to push user configs", e) } - } catch (e: Exception) { - Log.e(TAG, "Failed to push user configs", e) } - } } val job2 = launch { - merge( - // When the onion request path changes, we need to examine all the groups - // and push the pending configs for them - pathBecomesAvailable().flatMapLatest { - configFactory.withUserConfigs { configs -> configs.userGroups.allClosedGroupInfo() } - .asSequence() - .filter { !it.destroyed && !it.kicked } - .map { it.groupAccountId } - .asFlow() - }, - - // Or, when a group config is updated, we need to push the changes for that group - configFactory.configUpdateNotifications - .filterIsInstance() - .map { it.groupId } - .debounce(1000L) - ).collect { groupId -> + hasLoggedInUser() + .flatMapLatest { loggedIn -> + if (loggedIn) { + merge( + // When the onion request path changes, we need to examine all the groups + // and push the pending configs for them + pathBecomesAvailable().flatMapLatest { + configFactory.withUserConfigs { configs -> configs.userGroups.allClosedGroupInfo() } + .asSequence() + .filter { !it.destroyed && !it.kicked } + .map { it.groupAccountId } + .asFlow() + }, + + // Or, when a group config is updated, we need to push the changes for that group + configFactory.configUpdateNotifications + .filterIsInstance() + .map { it.groupId } + .debounce(1000L) + ) + } else { + emptyFlow() + } + } + .collect { groupId -> try { retryWithUniformInterval { pushGroupConfigsChangesIfNeeded(groupId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt index 8215eecd9b..7aa9924957 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt @@ -56,7 +56,7 @@ object ResendMessageUtilities { if (sentTimestamp != null && sender != null) { if (isResync) { MessagingModuleConfiguration.shared.storage.markAsResyncing(sentTimestamp, sender) - MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = true) + MessageSender.sendNonDurably(message, Destination.from(recipient.address), isSyncMessage = true) } else { MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender) MessageSender.send(message, recipient.address) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt index 9a04364494..5acbba02e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt @@ -101,10 +101,38 @@ abstract class BaseGroupMembersViewModel ( status = status.takeIf { !isMyself }, // Status is only meant for other members highlightStatus = highlightStatus, showAsAdmin = member.isAdminOrBeingPromoted(status), - clickable = !isMyself + clickable = !isMyself, + statusLabel = getMemberLabel(status, context, amIAdmin), ) } + private fun getMemberLabel(status: GroupMember.Status, context: Context, amIAdmin: Boolean): String { + return when (status) { + GroupMember.Status.INVITE_FAILED -> context.getString(R.string.groupInviteFailed) + GroupMember.Status.INVITE_SENDING -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1) + GroupMember.Status.INVITE_SENT -> context.getString(R.string.groupInviteSent) + GroupMember.Status.PROMOTION_FAILED -> context.getString(R.string.adminPromotionFailed) + GroupMember.Status.PROMOTION_SENDING -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1) + GroupMember.Status.PROMOTION_SENT -> context.getString(R.string.adminPromotionSent) + GroupMember.Status.REMOVED, + GroupMember.Status.REMOVED_UNKNOWN, + GroupMember.Status.REMOVED_INCLUDING_MESSAGES -> { + if (amIAdmin) { + context.getString(R.string.groupPendingRemoval) + } else { + "" + } + } + + GroupMember.Status.INVITE_UNKNOWN, + GroupMember.Status.INVITE_ACCEPTED, + GroupMember.Status.INVITE_NOT_SENT, + GroupMember.Status.PROMOTION_NOT_SENT, + GroupMember.Status.PROMOTION_UNKNOWN, + GroupMember.Status.PROMOTION_ACCEPTED -> "" + } + } + // Refer to notion doc for the sorting logic private fun sortMembers(members: List, currentUserId: AccountId) = members.sortedWith( @@ -150,37 +178,8 @@ data class GroupMemberState( val canResendPromotion: Boolean, val canRemove: Boolean, val canPromote: Boolean, - val clickable: Boolean + val clickable: Boolean, + val statusLabel: String, ) { val canEdit: Boolean get() = canRemove || canPromote || canResendInvite || canResendPromotion } - -// Function to get the label dynamically using the context -fun GroupMemberState.getLabel(context: Context): String { - return when (this.status) { - GroupMember.Status.INVITE_FAILED -> context.getString(R.string.groupInviteFailed) - GroupMember.Status.INVITE_SENDING -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1) - GroupMember.Status.INVITE_SENT -> context.getString(R.string.groupInviteSent) - GroupMember.Status.PROMOTION_FAILED -> context.getString(R.string.adminPromotionFailed) - GroupMember.Status.PROMOTION_SENDING -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1) - GroupMember.Status.PROMOTION_SENT -> context.getString(R.string.adminPromotionSent) - GroupMember.Status.REMOVED, - GroupMember.Status.REMOVED_UNKNOWN, - GroupMember.Status.REMOVED_INCLUDING_MESSAGES -> { - if (this.showAsAdmin) { - context.getString(R.string.groupPendingRemoval) - } else { - "" - } - } - - GroupMember.Status.INVITE_UNKNOWN, - GroupMember.Status.INVITE_ACCEPTED, - GroupMember.Status.INVITE_NOT_SENT, - GroupMember.Status.PROMOTION_NOT_SENT, - GroupMember.Status.PROMOTION_UNKNOWN, - GroupMember.Status.PROMOTION_ACCEPTED -> "" - - null -> "" - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index 30a0959d0c..652d1bb431 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -3,11 +3,9 @@ package org.thoughtcrime.securesms.groups import android.content.Context import com.google.protobuf.ByteString import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first @@ -19,7 +17,6 @@ import network.loki.messenger.libsession_util.util.Conversation import network.loki.messenger.libsession_util.util.ExpiryMode import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.GroupMember -import network.loki.messenger.libsession_util.util.Sodium import network.loki.messenger.libsession_util.util.UserPic import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol @@ -269,8 +266,8 @@ class GroupManagerV2Impl @Inject constructor( subAccountTokens = subAccountTokens ) - // Before we send the invitation, we need to make sure the configs are pushed - configFactory.waitUntilGroupConfigsPushed(group) + // Send a group update message to the group telling members someone has been invited + sendGroupUpdateForAddingMembers(group, adminKey, newMembers) // Call the API try { @@ -307,9 +304,6 @@ class GroupManagerV2Impl @Inject constructor( newMembers.map { it.hexString }.toTypedArray() ) ) - - // Send a group update message to the group telling members someone has been invited - sendGroupUpdateForAddingMembers(group, adminKey, newMembers) } /** @@ -336,7 +330,8 @@ class GroupManagerV2Impl @Inject constructor( ) .build() ).apply { this.sentTimestamp = timestamp } - MessageSender.send(updatedMessage, Destination.ClosedGroup(group.hexString), false) + + MessageSender.send(updatedMessage, Address.fromSerialized(group.hexString)) storage.insertGroupInfoChange(updatedMessage, group) } @@ -377,7 +372,7 @@ class GroupManagerV2Impl @Inject constructor( updateMessage ).apply { sentTimestamp = timestamp } - MessageSender.send(message, Destination.ClosedGroup(groupAccountId.hexString), false).await() + MessageSender.send(message, Address.fromSerialized(groupAccountId.hexString)) storage.insertGroupInfoChange(message, groupAccountId) } @@ -444,37 +439,27 @@ class GroupManagerV2Impl @Inject constructor( } if (group != null && !group.kicked && !weAreTheOnlyAdmin) { - val destination = Destination.ClosedGroup(groupId.hexString) - val sendMessageTasks = mutableListOf>() + val address = Address.fromSerialized(groupId.hexString) // Always send a "XXX left" message to the group if we can - sendMessageTasks += async { - MessageSender.send( - GroupUpdated( - GroupUpdateMessage.newBuilder() - .setMemberLeftNotificationMessage(DataMessage.GroupUpdateMemberLeftNotificationMessage.getDefaultInstance()) - .build() - ), - destination, - isSyncMessage = false - ).await() - } - + MessageSender.send( + GroupUpdated( + GroupUpdateMessage.newBuilder() + .setMemberLeftNotificationMessage(DataMessage.GroupUpdateMemberLeftNotificationMessage.getDefaultInstance()) + .build() + ), + address + ) // If we are not the only admin, send a left message for other admin to handle the member removal - sendMessageTasks += async { - MessageSender.send( - GroupUpdated( - GroupUpdateMessage.newBuilder() - .setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance()) - .build() - ), - destination, - isSyncMessage = false - ).await() - } - - sendMessageTasks.awaitAll() + MessageSender.send( + GroupUpdated( + GroupUpdateMessage.newBuilder() + .setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance()) + .build() + ), + address, + ) } // If we are the only admin, leaving this group will destroy the group @@ -537,11 +522,10 @@ class GroupManagerV2Impl @Inject constructor( val promotionDeferred = members.associateWith { member -> async { - MessageSender.sendNonDurably( + MessageSender.sendAndAwait( message = promoteMessage, address = Address.fromSerialized(member.hexString), - isSyncMessage = false - ).await() + ) } } @@ -585,7 +569,7 @@ class GroupManagerV2Impl @Inject constructor( sentTimestamp = timestamp } - MessageSender.send(message, Destination.ClosedGroup(group.hexString), false).await() + MessageSender.sendAndAwait(message, Address.fromSerialized(group.hexString)) storage.insertGroupInfoChange(message, group) } } @@ -683,7 +667,7 @@ class GroupManagerV2Impl @Inject constructor( .setInviteResponse(inviteResponse) val responseMessage = GroupUpdated(responseData.build(), profile = storage.getUserProfile()) // this will fail the first couple of times :) - MessageSender.send( + MessageSender.sendNonDurably( responseMessage, Destination.ClosedGroup(group.groupAccountId.hexString), isSyncMessage = false @@ -962,7 +946,7 @@ class GroupManagerV2Impl @Inject constructor( sentTimestamp = timestamp } - MessageSender.send(message, Address.fromSerialized(groupId.hexString)) + MessageSender.sendAndAwait(message, Address.fromSerialized(groupId.hexString)) storage.insertGroupInfoChange(message, groupId) } @@ -1034,7 +1018,7 @@ class GroupManagerV2Impl @Inject constructor( sentTimestamp = timestamp } - MessageSender.send(message, Destination.ClosedGroup(groupId.hexString), false).await() + MessageSender.sendAndAwait(message, Address.fromSerialized(groupId.hexString)) } override suspend fun handleDeleteMemberContent( diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt index ba0e2d814d..86f4c7ee46 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupScreen.kt @@ -50,7 +50,6 @@ import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.groups.EditGroupViewModel import org.thoughtcrime.securesms.groups.GroupMemberState -import org.thoughtcrime.securesms.groups.getLabel import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.DialogButtonModel import org.thoughtcrime.securesms.ui.GetString @@ -442,7 +441,7 @@ fun EditMemberItem( MemberItem( accountId = member.accountId, title = member.name, - subtitle = member.getLabel(LocalContext.current), + subtitle = member.statusLabel, subtitleColor = if (member.highlightStatus) { LocalColors.current.danger } else { @@ -476,7 +475,8 @@ private fun EditGroupPreview3() { canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "Invited" ) val twoMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), @@ -488,7 +488,8 @@ private fun EditGroupPreview3() { canResendInvite = false, canResendPromotion = false, showAsAdmin = true, - clickable = true + clickable = true, + statusLabel = "Promotion failed" ) val threeMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), @@ -500,7 +501,8 @@ private fun EditGroupPreview3() { canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "" ) val (editingName, setEditingName) = remember { mutableStateOf(null) } @@ -551,7 +553,8 @@ private fun EditGroupPreview() { canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "Invited" ) val twoMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), @@ -563,7 +566,8 @@ private fun EditGroupPreview() { canResendInvite = false, canResendPromotion = false, showAsAdmin = true, - clickable = true + clickable = true, + statusLabel = "Promotion failed" ) val threeMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), @@ -575,7 +579,8 @@ private fun EditGroupPreview() { canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "" ) val (editingName, setEditingName) = remember { mutableStateOf(null) } @@ -628,7 +633,8 @@ private fun EditGroupEditNamePreview( canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "Invited" ) val twoMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), @@ -640,7 +646,8 @@ private fun EditGroupEditNamePreview( canResendInvite = false, canResendPromotion = false, showAsAdmin = true, - clickable = true + clickable = true, + statusLabel = "Promotion failed" ) val threeMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), @@ -652,7 +659,8 @@ private fun EditGroupEditNamePreview( canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "" ) EditGroup( diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/GroupMembersScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/GroupMembersScreen.kt index c2c8b41ea5..2f203d8673 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/GroupMembersScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/GroupMembersScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel @@ -18,7 +17,6 @@ import network.loki.messenger.libsession_util.util.GroupMember import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.groups.GroupMemberState import org.thoughtcrime.securesms.groups.GroupMembersViewModel -import org.thoughtcrime.securesms.groups.getLabel import org.thoughtcrime.securesms.ui.components.BackAppBar import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.PreviewTheme @@ -43,7 +41,7 @@ fun GroupMembersScreen( @Composable fun GroupMembers( onBack: () -> Unit, - members: List + members: List, ) { Scaffold( @@ -62,7 +60,7 @@ fun GroupMembers( MemberItem( accountId = member.accountId, title = member.name, - subtitle = member.getLabel(LocalContext.current), + subtitle = member.statusLabel, subtitleColor = if (member.highlightStatus) { LocalColors.current.danger } else { @@ -91,7 +89,8 @@ private fun EditGroupPreview() { canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "Invited" ) val twoMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), @@ -103,7 +102,8 @@ private fun EditGroupPreview() { canResendInvite = false, canResendPromotion = false, showAsAdmin = true, - clickable = true + clickable = true, + statusLabel = "Promotion failed" ) val threeMember = GroupMemberState( accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), @@ -115,7 +115,8 @@ private fun EditGroupPreview() { canResendInvite = false, canResendPromotion = false, showAsAdmin = false, - clickable = true + clickable = true, + statusLabel = "" ) GroupMembers( diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index b73131297a..157247a776 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -416,7 +416,7 @@ class DefaultConversationRepository @Inject constructor( ) } else { val message = MessageRequestResponse(true) - MessageSender.send( + MessageSender.sendNonDurably( message = message, destination = Destination.from(recipient.address), isSyncMessage = recipient.isLocalNumber diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index 8a91a854c9..c08b15e260 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -139,7 +139,8 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess destination.whisperTo, destination.whisperMods, destination.fileIds + uploadResult.id.toString() - ) + ), + statusCallback = it.statusCallback ) updatedJob.id = it.id updatedJob.delegate = it.delegate diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt index 10f04c15df..a76101450e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/InviteContactsJob.kt @@ -7,7 +7,6 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext -import org.session.libsession.R import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.groups.GroupInviteException import org.session.libsession.messaging.messages.Destination @@ -18,11 +17,7 @@ import org.session.libsession.messaging.utilities.MessageAuthentication.buildGro import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.utilities.await -import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY -import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY -import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY import org.session.libsession.utilities.getGroup -import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteMessage import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage import org.session.libsignal.utilities.AccountId @@ -81,7 +76,7 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array< sentTimestamp = timestamp } - MessageSender.send(update, Destination.Contact(memberSessionId), false) + MessageSender.sendNonDurably(update, Destination.Contact(memberSessionId), false) .await() } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt index 9549d0c735..77ef4446b8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt @@ -3,10 +3,10 @@ package org.session.libsession.messaging.jobs import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output +import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withTimeout import org.session.libsession.messaging.MessagingModuleConfiguration @@ -23,7 +23,7 @@ import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log -class MessageSendJob(val message: Message, val destination: Destination) : Job { +class MessageSendJob(val message: Message, val destination: Destination, val statusCallback: SendChannel>?) : Job { object AwaitingAttachmentUploadException : Exception("Awaiting attachment upload.") @@ -91,18 +91,25 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { .waitForGroupEncryptionKeys(AccountId(destination.publicKey)) } - MessageSender.send(this@MessageSendJob.message, destination, isSync).await() + MessageSender.sendNonDurably(this@MessageSendJob.message, destination, isSync).await() } this.handleSuccess(dispatcherName) + statusCallback?.trySend(Result.success(Unit)) } catch (e: HTTP.HTTPRequestFailedException) { if (e.statusCode == 429) { this.handlePermanentFailure(dispatcherName, e) } else { this.handleFailure(dispatcherName, e) } + + statusCallback?.trySend(Result.failure(e)) } catch (e: MessageSender.Error) { if (!e.isRetryable) { this.handlePermanentFailure(dispatcherName, e) } else { this.handleFailure(dispatcherName, e) } + + statusCallback?.trySend(Result.failure(e)) } catch (e: Exception) { this.handleFailure(dispatcherName, e) + + statusCallback?.trySend(Result.failure(e)) } } @@ -191,7 +198,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { } destinationInput.close() // Return - return MessageSendJob(message, destination) + return MessageSendJob(message, destination, statusCallback = null) } } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 96c66054c3..efa79e2243 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -2,9 +2,10 @@ package org.session.libsession.messaging.sending_receiving import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_HIDDEN import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.util.ExpiryMode import nl.komponents.kovenant.Promise @@ -30,24 +31,20 @@ import org.session.libsession.messaging.open_groups.OpenGroupApi.Capability import org.session.libsession.messaging.open_groups.OpenGroupMessage import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.messaging.utilities.SodiumUtilities -import org.session.libsession.snode.RawResponsePromise import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI.nowWithOffset import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SnodeModule import org.session.libsession.snode.utilities.asyncPromise -import org.session.libsession.snode.utilities.await import org.session.libsession.utilities.Address import org.session.libsession.utilities.Device import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.SSKEnvironment -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.IdPrefix -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace import org.session.libsignal.utilities.defaultRequiresAuth import org.session.libsignal.utilities.hasNamespaces @@ -80,7 +77,7 @@ object MessageSender { } // Convenience - fun send(message: Message, destination: Destination, isSyncMessage: Boolean): Promise { + fun sendNonDurably(message: Message, destination: Destination, isSyncMessage: Boolean): Promise { if (message is VisibleMessage) MessagingModuleConfiguration.shared.lastSentTimestampCache.submitTimestamp(message.threadID!!, message.sentTimestamp!!) return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) { sendToOpenGroupDestination(destination, message) @@ -548,12 +545,13 @@ object MessageSender { } @JvmStatic - fun send(message: Message, address: Address) { + @JvmOverloads + fun send(message: Message, address: Address, statusCallback: SendChannel>? = null) { val threadID = MessagingModuleConfiguration.shared.storage.getThreadId(address) threadID?.let(message::applyExpiryMode) message.threadID = threadID val destination = Destination.from(address) - val job = MessageSendJob(message, destination) + val job = MessageSendJob(message, destination, statusCallback) JobQueue.shared.add(job) // if we are sending a 'Note to Self' make sure it is not hidden @@ -565,6 +563,12 @@ object MessageSender { } } + suspend fun sendAndAwait(message: Message, address: Address) { + val resultChannel = Channel>() + send(message, address, resultChannel) + resultChannel.receive().getOrThrow() + } + fun sendNonDurably(message: VisibleMessage, attachments: List, address: Address, isSyncMessage: Boolean): Promise { val attachmentIDs = MessagingModuleConfiguration.shared.messageDataProvider.getAttachmentIDsFor(message.id!!) message.attachmentIDs.addAll(attachmentIDs) @@ -575,7 +579,7 @@ object MessageSender { val threadID = MessagingModuleConfiguration.shared.storage.getThreadId(address) message.threadID = threadID val destination = Destination.from(address) - return send(message, destination, isSyncMessage) + return sendNonDurably(message, destination, isSyncMessage) } // Closed groups diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index a7045d00c6..fb9ffd1c92 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -6,11 +6,9 @@ import com.google.protobuf.ByteString import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.Channel import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.deferred import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.jobs.GroupLeavingJob import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.sending_receiving.MessageSender.Error import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 @@ -22,14 +20,12 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Device import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.ecc.Curve import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.removingIdPrefixIfNeeded @@ -85,7 +81,8 @@ fun MessageSender.create( val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupUpdateKind, groupID) closedGroupControlMessage.sentTimestamp = sentTime try { - sendNonDurably(closedGroupControlMessage, Address.fromSerialized(member), member == ourPubKey).await() + sendNonDurably(closedGroupControlMessage, fromSerialized(member), member == ourPubKey) + .await() } catch (e: Exception) { // We failed to properly create the group so delete it's associated data (in the past // we didn't create this data until the messages successfully sent but this resulted