Skip to content

Commit 72b6d4c

Browse files
authored
feat: Delete local conversation use case [#WPB-14601] (#3182)
* feat: Delete local conversation use case [#WPB-14601] * Renaming * Remove visibility and ephemeral from assets query
1 parent ecdca03 commit 72b6d4c

File tree

11 files changed

+405
-1
lines changed

11 files changed

+405
-1
lines changed

logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt

+7
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ interface ConversationRepository {
224224
): Either<CoreFailure, Unit>
225225

226226
suspend fun deleteConversation(conversationId: ConversationId): Either<CoreFailure, Unit>
227+
suspend fun deleteConversationLocally(conversationId: ConversationId): Either<CoreFailure, Unit>
227228

228229
/**
229230
* Deletes all conversation messages
@@ -884,6 +885,12 @@ internal class ConversationDataSource internal constructor(
884885
}
885886
}
886887

888+
override suspend fun deleteConversationLocally(conversationId: ConversationId): Either<CoreFailure, Unit> {
889+
return wrapStorageRequest {
890+
conversationDAO.deleteConversationByQualifiedID(conversationId.toDao())
891+
}
892+
}
893+
887894
override suspend fun clearContent(conversationId: ConversationId): Either<StorageFailure, Unit> =
888895
wrapStorageRequest {
889896
conversationDAO.clearContent(conversationId.toDao())

logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt

+12
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ internal interface MessageRepository {
254254
conversationId: ConversationId
255255
): Either<StorageFailure, AssetTransferStatus>
256256

257+
suspend fun getAllAssetIdsFromConversationId(
258+
conversationId: ConversationId,
259+
): Either<StorageFailure, List<String>>
260+
257261
suspend fun getSenderNameByMessageId(conversationId: ConversationId, messageId: String): Either<CoreFailure, String>
258262
suspend fun getNextAudioMessageInConversation(conversationId: ConversationId, messageId: String): Either<CoreFailure, String>
259263
}
@@ -710,6 +714,14 @@ internal class MessageDataSource internal constructor(
710714
messageDAO.getMessageAssetTransferStatus(messageId, conversationId.toDao()).toModel()
711715
}
712716

717+
override suspend fun getAllAssetIdsFromConversationId(
718+
conversationId: ConversationId
719+
): Either<StorageFailure, List<String>> {
720+
return wrapStorageRequest {
721+
messageDAO.getAllMessageAssetIdsForConversationId(conversationId = conversationId.toDao())
722+
}
723+
}
724+
713725
override suspend fun getSenderNameByMessageId(conversationId: ConversationId, messageId: String): Either<CoreFailure, String> =
714726
wrapStorageRequest { messageDAO.getSenderNameById(messageId, conversationId.toDao()) }
715727

logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,9 @@ class UserSessionScope internal constructor(
18091809
this,
18101810
userScopedLogger,
18111811
refreshUsersWithoutMetadata,
1812-
sessionManager.getServerConfig().links
1812+
sessionManager.getServerConfig().links,
1813+
messages.messageRepository,
1814+
assetRepository
18131815
)
18141816
}
18151817

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2024 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
package com.wire.kalium.logic.feature.conversation
19+
20+
import com.wire.kalium.logic.CoreFailure
21+
import com.wire.kalium.logic.data.asset.AssetRepository
22+
import com.wire.kalium.logic.data.id.ConversationId
23+
import com.wire.kalium.logic.data.message.MessageRepository
24+
import com.wire.kalium.logic.functional.Either
25+
import com.wire.kalium.logic.functional.flatMap
26+
27+
interface ClearConversationAssetsLocallyUseCase {
28+
/**
29+
* Clear all conversation assets from local storage
30+
*
31+
* @param conversationId - id of conversation in which assets should be cleared
32+
*/
33+
suspend operator fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit>
34+
}
35+
36+
internal class ClearConversationAssetsLocallyUseCaseImpl(
37+
private val messageRepository: MessageRepository,
38+
private val assetRepository: AssetRepository
39+
) : ClearConversationAssetsLocallyUseCase {
40+
override suspend fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit> {
41+
return messageRepository.getAllAssetIdsFromConversationId(conversationId)
42+
.flatMap { ids ->
43+
if (ids.isEmpty()) return Either.Right(Unit)
44+
45+
ids.map { id -> assetRepository.deleteAssetLocally(id) }
46+
.reduce { acc, either ->
47+
acc.flatMap { either }
48+
}
49+
}
50+
}
51+
}

logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt

+16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.wire.kalium.logger.KaliumLogger
2323
import com.wire.kalium.logic.cache.SelfConversationIdProvider
2424
import com.wire.kalium.logic.configuration.server.ServerConfig
2525
import com.wire.kalium.logic.configuration.server.ServerConfigRepository
26+
import com.wire.kalium.logic.data.asset.AssetRepository
2627
import com.wire.kalium.logic.data.connection.ConnectionRepository
2728
import com.wire.kalium.logic.data.conversation.ConversationGroupRepository
2829
import com.wire.kalium.logic.data.conversation.ConversationRepository
@@ -38,6 +39,7 @@ import com.wire.kalium.logic.data.conversation.folders.ConversationFolderReposit
3839
import com.wire.kalium.logic.data.id.CurrentClientIdProvider
3940
import com.wire.kalium.logic.data.id.QualifiedIdMapper
4041
import com.wire.kalium.logic.data.id.SelfTeamIdProvider
42+
import com.wire.kalium.logic.data.message.MessageRepository
4143
import com.wire.kalium.logic.data.message.PersistMessageUseCase
4244
import com.wire.kalium.logic.data.properties.UserPropertyRepository
4345
import com.wire.kalium.logic.data.team.TeamRepository
@@ -115,6 +117,8 @@ class ConversationScope internal constructor(
115117
private val kaliumLogger: KaliumLogger,
116118
private val refreshUsersWithoutMetadata: RefreshUsersWithoutMetadataUseCase,
117119
private val serverConfigLinks: ServerConfig.Links,
120+
internal val messageRepository: MessageRepository,
121+
internal val assetRepository: AssetRepository,
118122
internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl,
119123
) {
120124

@@ -266,6 +270,18 @@ class ConversationScope internal constructor(
266270
selfConversationIdProvider
267271
)
268272

273+
val clearConversationAssetsLocally: ClearConversationAssetsLocallyUseCase
274+
get() = ClearConversationAssetsLocallyUseCaseImpl(
275+
messageRepository,
276+
assetRepository
277+
)
278+
279+
val deleteConversationLocallyUseCase: DeleteConversationLocallyUseCase
280+
get() = DeleteConversationLocallyUseCaseImpl(
281+
conversationRepository,
282+
clearConversationAssetsLocally
283+
)
284+
269285
val joinConversationViaCode: JoinConversationViaCodeUseCase
270286
get() = JoinConversationViaCodeUseCase(conversationGroupRepository, selfUserId)
271287

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2024 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
package com.wire.kalium.logic.feature.conversation
19+
20+
import com.wire.kalium.logic.CoreFailure
21+
import com.wire.kalium.logic.data.conversation.ConversationRepository
22+
import com.wire.kalium.logic.data.id.ConversationId
23+
import com.wire.kalium.logic.functional.Either
24+
import com.wire.kalium.logic.functional.flatMap
25+
26+
interface DeleteConversationLocallyUseCase {
27+
/**
28+
* Delete local conversation which:
29+
* - Clear all local assets
30+
* - Clear content
31+
* - Remove conversation
32+
*
33+
* @param conversationId - id of conversation to delete
34+
*/
35+
suspend operator fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit>
36+
}
37+
38+
internal class DeleteConversationLocallyUseCaseImpl(
39+
private val conversationRepository: ConversationRepository,
40+
private val clearLocalConversationAssets: ClearConversationAssetsLocallyUseCase
41+
) : DeleteConversationLocallyUseCase {
42+
43+
override suspend fun invoke(conversationId: ConversationId): Either<CoreFailure, Unit> {
44+
return clearLocalConversationAssets(conversationId)
45+
.flatMap { conversationRepository.clearContent(conversationId) }
46+
.flatMap { conversationRepository.deleteConversationLocally(conversationId) }
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2024 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
package com.wire.kalium.logic.feature.conversation
19+
20+
import com.wire.kalium.logic.CoreFailure
21+
import com.wire.kalium.logic.data.asset.AssetRepository
22+
import com.wire.kalium.logic.data.id.ConversationId
23+
import com.wire.kalium.logic.data.message.MessageRepository
24+
import com.wire.kalium.logic.functional.Either
25+
import io.mockative.Mock
26+
import io.mockative.any
27+
import io.mockative.coEvery
28+
import io.mockative.coVerify
29+
import io.mockative.mock
30+
import kotlinx.coroutines.test.runTest
31+
import kotlin.test.Test
32+
import kotlin.test.assertIs
33+
34+
class ClearConversationAssetsLocallyUseCaseTest {
35+
36+
@Test
37+
fun givenConversationAssetIds_whenAllDeletionsAreSuccess_thenSuccessResultIsPropagated() = runTest {
38+
// given
39+
val ids = listOf("id_1", "id_2")
40+
val (arrangement, useCase) = Arrangement()
41+
.withAssetIdsResponse(ids)
42+
.withAssetClearSuccess("id_1")
43+
.withAssetClearSuccess("id_2")
44+
.arrange()
45+
46+
// when
47+
val result = useCase(ConversationId("someValue", "someDomain"))
48+
49+
// then
50+
assertIs<Either.Right<Unit>>(result)
51+
coVerify { arrangement.assetRepository.deleteAssetLocally(any()) }.wasInvoked(exactly = 2)
52+
}
53+
54+
@Test
55+
fun givenConversationAssetIds_whenOneDeletionFailed_thenFailureResultIsPropagated() = runTest {
56+
// given
57+
val ids = listOf("id_1", "id_2")
58+
val (arrangement, useCase) = Arrangement()
59+
.withAssetIdsResponse(ids)
60+
.withAssetClearSuccess("id_1")
61+
.withAssetClearError("id_2")
62+
.arrange()
63+
64+
// when
65+
val result = useCase(ConversationId("someValue", "someDomain"))
66+
67+
// then
68+
assertIs<Either.Left<Unit>>(result)
69+
coVerify { arrangement.assetRepository.deleteAssetLocally(any()) }.wasInvoked(exactly = 2)
70+
}
71+
72+
@Test
73+
fun givenEmptyConversationAssetIds_whenInvoked_thenDeletionsAreNotInvoked() = runTest {
74+
// given
75+
val (arrangement, useCase) = Arrangement()
76+
.withAssetIdsResponse(emptyList())
77+
.arrange()
78+
79+
// when
80+
val result = useCase(ConversationId("someValue", "someDomain"))
81+
82+
// then
83+
assertIs<Either.Right<Unit>>(result)
84+
coVerify { arrangement.assetRepository.deleteAssetLocally(any()) }.wasNotInvoked()
85+
}
86+
87+
private class Arrangement {
88+
@Mock
89+
val messageRepository = mock(MessageRepository::class)
90+
91+
@Mock
92+
val assetRepository = mock(AssetRepository::class)
93+
94+
suspend fun withAssetClearSuccess(id: String) = apply {
95+
coEvery { assetRepository.deleteAssetLocally(id) }.returns(Either.Right(Unit))
96+
}
97+
98+
suspend fun withAssetClearError(id: String) = apply {
99+
coEvery { assetRepository.deleteAssetLocally(id) }.returns(Either.Left(CoreFailure.Unknown(null)))
100+
}
101+
102+
suspend fun withAssetIdsResponse(ids: List<String>) = apply {
103+
coEvery { messageRepository.getAllAssetIdsFromConversationId(any()) }.returns(Either.Right(ids))
104+
}
105+
106+
fun arrange() = this to ClearConversationAssetsLocallyUseCaseImpl(
107+
messageRepository = messageRepository,
108+
assetRepository = assetRepository
109+
)
110+
}
111+
}

0 commit comments

Comments
 (0)