mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 10:35:19 +00:00
Fix crashes on conversation screen (#1590)
* Fixed onion request crashes * Use kotlin.Result instead --------- Co-authored-by: fanchao <git@fanchao.dev>
This commit is contained in:
parent
2a5f86ea06
commit
268b6cc097
@ -8,7 +8,6 @@ import app.cash.copper.Query
|
|||||||
import app.cash.copper.flow.observeQuery
|
import app.cash.copper.flow.observeQuery
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@ -59,15 +58,15 @@ interface ConversationRepository {
|
|||||||
fun deleteLocally(recipient: Recipient, message: MessageRecord)
|
fun deleteLocally(recipient: Recipient, message: MessageRecord)
|
||||||
fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord)
|
fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord)
|
||||||
fun setApproved(recipient: Recipient, isApproved: Boolean)
|
fun setApproved(recipient: Recipient, isApproved: Boolean)
|
||||||
suspend fun deleteForEveryone(threadId: Long, recipient: Recipient, message: MessageRecord): ResultOf<Unit>
|
suspend fun deleteForEveryone(threadId: Long, recipient: Recipient, message: MessageRecord): Result<Unit>
|
||||||
fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest?
|
fun buildUnsendRequest(recipient: Recipient, message: MessageRecord): UnsendRequest?
|
||||||
suspend fun deleteMessageWithoutUnsendRequest(threadId: Long, messages: Set<MessageRecord>): ResultOf<Unit>
|
suspend fun deleteMessageWithoutUnsendRequest(threadId: Long, messages: Set<MessageRecord>): Result<Unit>
|
||||||
suspend fun banUser(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
suspend fun banUser(threadId: Long, recipient: Recipient): Result<Unit>
|
||||||
suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): Result<Unit>
|
||||||
suspend fun deleteThread(threadId: Long): ResultOf<Unit>
|
suspend fun deleteThread(threadId: Long): Result<Unit>
|
||||||
suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit>
|
suspend fun deleteMessageRequest(thread: ThreadRecord): Result<Unit>
|
||||||
suspend fun clearAllMessageRequests(block: Boolean): ResultOf<Unit>
|
suspend fun clearAllMessageRequests(block: Boolean): Result<Unit>
|
||||||
suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit>
|
suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): Result<Unit>
|
||||||
fun declineMessageRequest(threadId: Long)
|
fun declineMessageRequest(threadId: Long)
|
||||||
fun hasReceived(threadId: Long): Boolean
|
fun hasReceived(threadId: Long): Boolean
|
||||||
}
|
}
|
||||||
@ -185,7 +184,7 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
threadId: Long,
|
threadId: Long,
|
||||||
recipient: Recipient,
|
recipient: Recipient,
|
||||||
message: MessageRecord
|
message: MessageRecord
|
||||||
): ResultOf<Unit> = suspendCoroutine { continuation ->
|
): Result<Unit> = suspendCoroutine { continuation ->
|
||||||
buildUnsendRequest(recipient, message)?.let { unsendRequest ->
|
buildUnsendRequest(recipient, message)?.let { unsendRequest ->
|
||||||
MessageSender.send(unsendRequest, recipient.address)
|
MessageSender.send(unsendRequest, recipient.address)
|
||||||
}
|
}
|
||||||
@ -196,10 +195,10 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
OpenGroupApi.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
||||||
.success {
|
.success {
|
||||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(Result.success(Unit))
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
Log.w("TAG", "Call to OpenGroupApi.deleteForEveryone failed - attempting to resume..")
|
Log.w("TAG", "Call to OpenGroupApi.deleteForEveryone failed - attempting to resume..")
|
||||||
continuation.resumeWithException(error)
|
continuation.resume(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,10 +228,10 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
SnodeAPI.deleteMessage(publicKey, listOf(serverHash))
|
SnodeAPI.deleteMessage(publicKey, listOf(serverHash))
|
||||||
.success {
|
.success {
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(Result.success(Unit))
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
Log.w("ConversationRepository", "Call to SnodeAPI.deleteMessage failed - attempting to resume..")
|
Log.w("ConversationRepository", "Call to SnodeAPI.deleteMessage failed - attempting to resume..")
|
||||||
continuation.resumeWithException(error)
|
continuation.resume(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,7 +249,7 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
override suspend fun deleteMessageWithoutUnsendRequest(
|
override suspend fun deleteMessageWithoutUnsendRequest(
|
||||||
threadId: Long,
|
threadId: Long,
|
||||||
messages: Set<MessageRecord>
|
messages: Set<MessageRecord>
|
||||||
): ResultOf<Unit> = suspendCoroutine { continuation ->
|
): Result<Unit> = suspendCoroutine { continuation ->
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)
|
||||||
if (openGroup != null) {
|
if (openGroup != null) {
|
||||||
val messageServerIDs = mutableMapOf<Long, MessageRecord>()
|
val messageServerIDs = mutableMapOf<Long, MessageRecord>()
|
||||||
@ -264,7 +263,7 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
.success {
|
.success {
|
||||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
continuation.resumeWithException(error)
|
continuation.resume(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -276,22 +275,22 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(Result.success(Unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun banUser(threadId: Long, recipient: Recipient): ResultOf<Unit> =
|
override suspend fun banUser(threadId: Long, recipient: Recipient): Result<Unit> =
|
||||||
suspendCoroutine { continuation ->
|
suspendCoroutine { continuation ->
|
||||||
val accountID = recipient.address.toString()
|
val accountID = recipient.address.toString()
|
||||||
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!!
|
val openGroup = lokiThreadDb.getOpenGroupChat(threadId)!!
|
||||||
OpenGroupApi.ban(accountID, openGroup.room, openGroup.server)
|
OpenGroupApi.ban(accountID, openGroup.room, openGroup.server)
|
||||||
.success {
|
.success {
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(Result.success(Unit))
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
continuation.resumeWithException(error)
|
continuation.resume(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): ResultOf<Unit> =
|
override suspend fun banAndDeleteAll(threadId: Long, recipient: Recipient): Result<Unit> =
|
||||||
suspendCoroutine { continuation ->
|
suspendCoroutine { continuation ->
|
||||||
// Note: This accountId could be the blinded Id
|
// Note: This accountId could be the blinded Id
|
||||||
val accountID = recipient.address.toString()
|
val accountID = recipient.address.toString()
|
||||||
@ -299,25 +298,25 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
|
|
||||||
OpenGroupApi.banAndDeleteAll(accountID, openGroup.room, openGroup.server)
|
OpenGroupApi.banAndDeleteAll(accountID, openGroup.room, openGroup.server)
|
||||||
.success {
|
.success {
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(Result.success(Unit))
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
continuation.resumeWithException(error)
|
continuation.resume(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteThread(threadId: Long): ResultOf<Unit> {
|
override suspend fun deleteThread(threadId: Long): Result<Unit> {
|
||||||
sessionJobDb.cancelPendingMessageSendJobs(threadId)
|
sessionJobDb.cancelPendingMessageSendJobs(threadId)
|
||||||
storage.deleteConversation(threadId)
|
storage.deleteConversation(threadId)
|
||||||
return ResultOf.Success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteMessageRequest(thread: ThreadRecord): ResultOf<Unit> {
|
override suspend fun deleteMessageRequest(thread: ThreadRecord): Result<Unit> {
|
||||||
sessionJobDb.cancelPendingMessageSendJobs(thread.threadId)
|
sessionJobDb.cancelPendingMessageSendJobs(thread.threadId)
|
||||||
storage.deleteConversation(thread.threadId)
|
storage.deleteConversation(thread.threadId)
|
||||||
return ResultOf.Success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clearAllMessageRequests(block: Boolean): ResultOf<Unit> {
|
override suspend fun clearAllMessageRequests(block: Boolean): Result<Unit> {
|
||||||
threadDb.readerFor(threadDb.unapprovedConversationList).use { reader ->
|
threadDb.readerFor(threadDb.unapprovedConversationList).use { reader ->
|
||||||
while (reader.next != null) {
|
while (reader.next != null) {
|
||||||
deleteMessageRequest(reader.current)
|
deleteMessageRequest(reader.current)
|
||||||
@ -325,18 +324,18 @@ class DefaultConversationRepository @Inject constructor(
|
|||||||
if (block) { setBlocked(recipient, true) }
|
if (block) { setBlocked(recipient, true) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ResultOf.Success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit> = suspendCoroutine { continuation ->
|
override suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): Result<Unit> = suspendCoroutine { continuation ->
|
||||||
storage.setRecipientApproved(recipient, true)
|
storage.setRecipientApproved(recipient, true)
|
||||||
val message = MessageRequestResponse(true)
|
val message = MessageRequestResponse(true)
|
||||||
MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = recipient.isLocalNumber)
|
MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = recipient.isLocalNumber)
|
||||||
.success {
|
.success {
|
||||||
threadDb.setHasSent(threadId, true)
|
threadDb.setHasSent(threadId, true)
|
||||||
continuation.resume(ResultOf.Success(Unit))
|
continuation.resume(Result.success(Unit))
|
||||||
}.fail { error ->
|
}.fail { error ->
|
||||||
continuation.resumeWithException(error)
|
continuation.resume(Result.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.repository
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
|
|
||||||
sealed class ResultOf<out T> {
|
|
||||||
|
|
||||||
data class Success<out R>(val value: R) : ResultOf<R>()
|
|
||||||
|
|
||||||
data class Failure(val throwable: Throwable) : ResultOf<Nothing>()
|
|
||||||
|
|
||||||
inline fun onFailure(block: (throwable: Throwable) -> Unit) = this.also {
|
|
||||||
if (this is Failure) {
|
|
||||||
block(throwable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun onSuccess(block: (value: T) -> Unit) = this.also {
|
|
||||||
if (this is Success) {
|
|
||||||
block(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <R> flatMap(mapper: (T) -> R): ResultOf<R> = when (this) {
|
|
||||||
is Success -> wrap { mapper(value) }
|
|
||||||
is Failure -> Failure(throwable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOrThrow(): T = when (this) {
|
|
||||||
is Success -> value
|
|
||||||
is Failure -> throw throwable
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
inline fun <T> wrap(block: () -> T): ResultOf<T> =
|
|
||||||
try {
|
|
||||||
Success(block())
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Failure(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,14 +3,12 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
import com.goterl.lazysodium.utils.KeyPair
|
import com.goterl.lazysodium.utils.KeyPair
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.hamcrest.CoreMatchers.endsWith
|
import org.hamcrest.CoreMatchers.endsWith
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
import org.hamcrest.CoreMatchers.notNullValue
|
import org.hamcrest.CoreMatchers.notNullValue
|
||||||
import org.hamcrest.CoreMatchers.nullValue
|
import org.hamcrest.CoreMatchers.nullValue
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.BeforeClass
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.anyLong
|
import org.mockito.Mockito.anyLong
|
||||||
@ -20,14 +18,11 @@ import org.mockito.kotlin.any
|
|||||||
import org.mockito.kotlin.mock
|
import org.mockito.kotlin.mock
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.utilities.Log
|
|
||||||
import org.thoughtcrime.securesms.BaseViewModelTest
|
import org.thoughtcrime.securesms.BaseViewModelTest
|
||||||
import org.thoughtcrime.securesms.NoOpLogger
|
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase
|
import org.thoughtcrime.securesms.database.MmsDatabase
|
||||||
import org.thoughtcrime.securesms.database.Storage
|
import org.thoughtcrime.securesms.database.Storage
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.repository.ConversationRepository
|
import org.thoughtcrime.securesms.repository.ConversationRepository
|
||||||
import org.thoughtcrime.securesms.repository.ResultOf
|
|
||||||
|
|
||||||
class ConversationViewModelTest: BaseViewModelTest() {
|
class ConversationViewModelTest: BaseViewModelTest() {
|
||||||
|
|
||||||
@ -107,7 +102,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
val message = mock<MessageRecord>()
|
val message = mock<MessageRecord>()
|
||||||
val error = Throwable()
|
val error = Throwable()
|
||||||
whenever(repository.deleteForEveryone(anyLong(), any(), any()))
|
whenever(repository.deleteForEveryone(anyLong(), any(), any()))
|
||||||
.thenReturn(ResultOf.Failure(error))
|
.thenReturn(Result.failure(error))
|
||||||
|
|
||||||
viewModel.deleteForEveryone(message)
|
viewModel.deleteForEveryone(message)
|
||||||
|
|
||||||
@ -120,7 +115,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
val message = mock<MessageRecord>()
|
val message = mock<MessageRecord>()
|
||||||
val error = Throwable()
|
val error = Throwable()
|
||||||
whenever(repository.deleteMessageWithoutUnsendRequest(anyLong(), anySet()))
|
whenever(repository.deleteMessageWithoutUnsendRequest(anyLong(), anySet()))
|
||||||
.thenReturn(ResultOf.Failure(error))
|
.thenReturn(Result.failure(error))
|
||||||
|
|
||||||
viewModel.deleteMessagesWithoutUnsendRequest(setOf(message))
|
viewModel.deleteMessagesWithoutUnsendRequest(setOf(message))
|
||||||
|
|
||||||
@ -130,7 +125,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `should emit error message on ban user failure`() = runBlockingTest {
|
fun `should emit error message on ban user failure`() = runBlockingTest {
|
||||||
val error = Throwable()
|
val error = Throwable()
|
||||||
whenever(repository.banUser(anyLong(), any())).thenReturn(ResultOf.Failure(error))
|
whenever(repository.banUser(anyLong(), any())).thenReturn(Result.failure(error))
|
||||||
|
|
||||||
viewModel.banUser(recipient)
|
viewModel.banUser(recipient)
|
||||||
|
|
||||||
@ -139,7 +134,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should emit a message on ban user success`() = runBlockingTest {
|
fun `should emit a message on ban user success`() = runBlockingTest {
|
||||||
whenever(repository.banUser(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
|
whenever(repository.banUser(anyLong(), any())).thenReturn(Result.success(Unit))
|
||||||
|
|
||||||
viewModel.banUser(recipient)
|
viewModel.banUser(recipient)
|
||||||
|
|
||||||
@ -152,7 +147,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `should emit error message on ban user and delete all failure`() = runBlockingTest {
|
fun `should emit error message on ban user and delete all failure`() = runBlockingTest {
|
||||||
val error = Throwable()
|
val error = Throwable()
|
||||||
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Failure(error))
|
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(Result.failure(error))
|
||||||
|
|
||||||
viewModel.banAndDeleteAll(messageRecord)
|
viewModel.banAndDeleteAll(messageRecord)
|
||||||
|
|
||||||
@ -161,7 +156,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should emit a message on ban user and delete all success`() = runBlockingTest {
|
fun `should emit a message on ban user and delete all success`() = runBlockingTest {
|
||||||
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
|
whenever(repository.banAndDeleteAll(anyLong(), any())).thenReturn(Result.success(Unit))
|
||||||
|
|
||||||
viewModel.banAndDeleteAll(messageRecord)
|
viewModel.banAndDeleteAll(messageRecord)
|
||||||
|
|
||||||
@ -188,7 +183,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `should remove shown message`() = runBlockingTest {
|
fun `should remove shown message`() = runBlockingTest {
|
||||||
// Given that a message is generated
|
// Given that a message is generated
|
||||||
whenever(repository.banUser(anyLong(), any())).thenReturn(ResultOf.Success(Unit))
|
whenever(repository.banUser(anyLong(), any())).thenReturn(Result.success(Unit))
|
||||||
viewModel.banUser(recipient)
|
viewModel.banUser(recipient)
|
||||||
assertThat(viewModel.uiState.value.uiMessages.size, equalTo(1))
|
assertThat(viewModel.uiState.value.uiMessages.size, equalTo(1))
|
||||||
// When the message is shown
|
// When the message is shown
|
||||||
|
Loading…
Reference in New Issue
Block a user