From 06c284a4df3d5024f634a960978ac3f61367d98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Zag=C3=B3rski?= Date: Wed, 4 Dec 2024 09:25:32 +0100 Subject: [PATCH 01/16] feat: Call sharing metadata [WPB-14605] (#3142) * feat: Hold call sharing metadata [WPB-14605] * Adjust names --- .../logic/data/call/CallMetadataProfile.kt | 15 +- .../kalium/logic/data/call/CallRepository.kt | 46 +++++- .../logic/data/call/CallRepositoryTest.kt | 154 ++++++++++++++++++ 3 files changed, 213 insertions(+), 2 deletions(-) diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallMetadataProfile.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallMetadataProfile.kt index 96fd1f2d914..dd306c7b382 100644 --- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallMetadataProfile.kt +++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallMetadataProfile.kt @@ -24,6 +24,7 @@ import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.user.OtherUserMinimized import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.data.user.type.UserType +import kotlinx.datetime.Instant data class CallMetadataProfile( val data: Map @@ -46,7 +47,8 @@ data class CallMetadata( val maxParticipants: Int = 0, // Was used for tracking val protocol: Conversation.ProtocolInfo, val activeSpeakers: Map> = mapOf(), - val users: List = listOf() + val users: List = listOf(), + val screenShareMetadata: CallScreenSharingMetadata = CallScreenSharingMetadata() ) { fun getFullParticipants(): List = participants.map { participant -> val user = users.firstOrNull { it.id == participant.userId } @@ -66,3 +68,14 @@ data class CallMetadata( ) } } + +/** + * [activeScreenShares] - map of user ids that share screen with the start timestamp + * [completedScreenShareDurationInMillis] - total time of already ended screen shares in milliseconds + * [uniqueSharingUsers] - set of users that were sharing a screen at least once + */ +data class CallScreenSharingMetadata( + val activeScreenShares: Map = emptyMap(), + val completedScreenShareDurationInMillis: Long = 0L, + val uniqueSharingUsers: Set = emptySet() +) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallRepository.kt index e5f8bc07ec4..f45ce704881 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallRepository.kt @@ -40,6 +40,7 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.FederatedIdMapper import com.wire.kalium.logic.data.id.GroupID import com.wire.kalium.logic.data.id.QualifiedClientID +import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.data.id.SubconversationId import com.wire.kalium.logic.data.id.toCrypto @@ -441,6 +442,8 @@ internal class CallDataSource( val currentParticipantIds = call.participants.map { it.userId }.toSet() val newParticipantIds = participants.map { it.userId }.toSet() + val sharingScreenParticipantIds = participants.filter { it.isSharingScreen } + .map { participant -> participant.id } val updatedUsers = call.users.toMutableList() @@ -456,7 +459,11 @@ internal class CallDataSource( this[conversationId] = call.copy( participants = participants, maxParticipants = max(call.maxParticipants, participants.size + 1), - users = updatedUsers + users = updatedUsers, + screenShareMetadata = updateScreenSharingMetadata( + metadata = call.screenShareMetadata, + usersCurrentlySharingScreen = sharingScreenParticipantIds + ) ) } @@ -479,6 +486,43 @@ internal class CallDataSource( } } + /** + * Manages call sharing metadata for analytical purposes by tracking the following: + * - **Active Screen Shares**: Maintains a record of currently active screen shares with their start times (local to the device). + * - **Completed Screen Share Duration**: Accumulates the total duration of screen shares that have already ended. + * - **Unique Sharing Users**: Keeps a unique list of all users who have shared their screen during the call. + * + * To update the metadata, the following steps are performed: + * 1. **Calculate Ended Screen Share Time**: Determine the total time for users who stopped sharing since the last update. + * 2. **Update Active Shares**: Filter out inactive shares and add any new ones, associating them with the current start time. + * 3. **Track Unique Users**: Append ids to current set in order to keep track of unique users. + */ + private fun updateScreenSharingMetadata( + metadata: CallScreenSharingMetadata, + usersCurrentlySharingScreen: List + ): CallScreenSharingMetadata { + val now = DateTimeUtil.currentInstant() + + val alreadyEndedScreenSharesTimeInMillis = metadata.activeScreenShares + .filterKeys { id -> id !in usersCurrentlySharingScreen } + .values + .sumOf { startTime -> DateTimeUtil.calculateMillisDifference(startTime, now) } + + val updatedShares = metadata.activeScreenShares + .filterKeys { id -> id in usersCurrentlySharingScreen } + .plus( + usersCurrentlySharingScreen + .filterNot { id -> metadata.activeScreenShares.containsKey(id) } + .associateWith { now } + ) + + return metadata.copy( + activeScreenShares = updatedShares, + completedScreenShareDurationInMillis = metadata.completedScreenShareDurationInMillis + alreadyEndedScreenSharesTimeInMillis, + uniqueSharingUsers = metadata.uniqueSharingUsers.plus(usersCurrentlySharingScreen.map { id -> id.toString() }) + ) + } + private fun clearStaleParticipantTimeout(participant: ParticipantMinimized) { callingLogger.i("Clear stale participant timer") val qualifiedClient = QualifiedClientID(ClientId(participant.clientId), participant.id) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/CallRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/CallRepositoryTest.kt index d3751f62295..e1c9cb91886 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/CallRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/CallRepositoryTest.kt @@ -23,6 +23,7 @@ import com.wire.kalium.cryptography.CryptoQualifiedClientId import com.wire.kalium.cryptography.MLSClient import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.call.CallRepositoryTest.Arrangement.Companion.callerId +import com.wire.kalium.logic.data.call.CallRepositoryTest.Arrangement.Companion.participant import com.wire.kalium.logic.data.call.mapper.CallMapperImpl import com.wire.kalium.logic.data.client.MLSClientProvider import com.wire.kalium.logic.data.conversation.ClientId @@ -84,6 +85,7 @@ import kotlinx.datetime.Clock import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -1509,6 +1511,158 @@ class CallRepositoryTest { ) } + @Test + fun givenCallWithParticipantsNotSharingScreen_whenOneStartsToShare_thenSharingMetadataHasProperValues() = runTest { + // given + val otherParticipant = participant.copy(id = QualifiedID("anotherParticipantId", "participantDomain")) + val participantsList = listOf(participant, otherParticipant) + val (_, callRepository) = Arrangement() + .givenGetKnownUserMinimizedSucceeds() + .arrange() + callRepository.updateCallMetadataProfileFlow( + callMetadataProfile = CallMetadataProfile( + data = mapOf(Arrangement.conversationId to createCallMetadata().copy(participants = participantsList)) + ) + ) + + // when + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, otherParticipant.copy(isSharingScreen = true)) + ) + val callMetadata = callRepository.getCallMetadataProfile()[Arrangement.conversationId] + + // then + assertNotNull(callMetadata) + assertEquals(0L, callMetadata.screenShareMetadata.completedScreenShareDurationInMillis) + assertTrue(callMetadata.screenShareMetadata.activeScreenShares.containsKey(otherParticipant.id)) + } + + @Test + fun givenCallWithParticipantsNotSharingScreen_whenTwoStartsAndOneStops_thenSharingMetadataHasProperValues() = runTest { + // given + val (_, callRepository) = Arrangement() + .arrange() + val secondParticipant = participant.copy(id = QualifiedID("secondParticipantId", "participantDomain")) + val thirdParticipant = participant.copy(id = QualifiedID("thirdParticipantId", "participantDomain")) + val participantsList = listOf(participant, secondParticipant, thirdParticipant) + callRepository.updateCallMetadataProfileFlow( + callMetadataProfile = CallMetadataProfile( + data = mapOf(Arrangement.conversationId to createCallMetadata().copy(participants = participantsList)) + ) + ) + + // when + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, secondParticipant.copy(isSharingScreen = true), thirdParticipant.copy(isSharingScreen = true)) + ) + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, secondParticipant, thirdParticipant.copy(isSharingScreen = true)) + ) + val callMetadata = callRepository.getCallMetadataProfile()[Arrangement.conversationId] + + // then + assertNotNull(callMetadata) + assertTrue(callMetadata.screenShareMetadata.activeScreenShares.size == 1) + assertTrue(callMetadata.screenShareMetadata.activeScreenShares.containsKey(thirdParticipant.id)) + } + + @Test + fun givenCallWithParticipantsSharingScreen_whenOneStopsToShare_thenSharingMetadataHasProperValues() = runTest { + // given + val (_, callRepository) = Arrangement() + .arrange() + val otherParticipant = participant.copy(id = QualifiedID("anotherParticipantId", "participantDomain")) + val participantsList = listOf(participant, otherParticipant) + callRepository.updateCallMetadataProfileFlow( + callMetadataProfile = CallMetadataProfile( + data = mapOf(Arrangement.conversationId to createCallMetadata().copy(participants = participantsList)) + ) + ) + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, otherParticipant.copy(isSharingScreen = true)) + ) + + // when + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, otherParticipant.copy(isSharingScreen = false)) + ) + val callMetadata = callRepository.getCallMetadataProfile()[Arrangement.conversationId] + + // then + assertNotNull(callMetadata) + assertTrue(callMetadata.screenShareMetadata.activeScreenShares.isEmpty()) + } + + @Test + fun givenCallWithParticipantsSharingScreen_whenTheSameParticipantIsSharingMultipleTime_thenSharingMetadataHasUserIdOnlyOnce() = + runTest { + // given + val (_, callRepository) = Arrangement() + .arrange() + val otherParticipant = participant.copy(id = QualifiedID("anotherParticipantId", "participantDomain")) + val participantsList = listOf(participant, otherParticipant) + callRepository.updateCallMetadataProfileFlow( + callMetadataProfile = CallMetadataProfile( + data = mapOf(Arrangement.conversationId to createCallMetadata().copy(participants = participantsList)) + ) + ) + + // when + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, otherParticipant.copy(isSharingScreen = true)) + ) + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, otherParticipant.copy(isSharingScreen = false)) + ) + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, otherParticipant.copy(isSharingScreen = true)) + ) + val callMetadata = callRepository.getCallMetadataProfile()[Arrangement.conversationId] + + // then + assertNotNull(callMetadata) + assertTrue(callMetadata.screenShareMetadata.uniqueSharingUsers.size == 1) + assertTrue(callMetadata.screenShareMetadata.uniqueSharingUsers.contains(otherParticipant.id.toString())) + } + + @Test + fun givenCallWithParticipantsSharingScreen_whenTwoParticipantsAreSharing_thenSharingMetadataHasBothOfUsersIds() = runTest { + // given + val (_, callRepository) = Arrangement() + .arrange() + val secondParticipant = participant.copy(id = QualifiedID("secondParticipantId", "participantDomain")) + val thirdParticipant = participant.copy(id = QualifiedID("thirdParticipantId", "participantDomain")) + val participantsList = listOf(participant, secondParticipant, thirdParticipant) + callRepository.updateCallMetadataProfileFlow( + callMetadataProfile = CallMetadataProfile( + data = mapOf(Arrangement.conversationId to createCallMetadata().copy(participants = participantsList)) + ) + ) + + // when + callRepository.updateCallParticipants( + Arrangement.conversationId, + listOf(participant, secondParticipant.copy(isSharingScreen = true), thirdParticipant.copy(isSharingScreen = true)) + ) + val callMetadata = callRepository.getCallMetadataProfile()[Arrangement.conversationId] + + // then + assertNotNull(callMetadata) + assertTrue(callMetadata.screenShareMetadata.uniqueSharingUsers.size == 2) + assertEquals( + setOf(secondParticipant.id.toString(), thirdParticipant.id.toString()), + callMetadata.screenShareMetadata.uniqueSharingUsers + ) + } + private fun provideCall(id: ConversationId, status: CallStatus) = Call( conversationId = id, status = status, From d2b92a03348b7cec56b464593e59f929f3fd6dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Zag=C3=B3rski?= Date: Fri, 6 Dec 2024 13:45:24 +0100 Subject: [PATCH 02/16] chore: Core crypto 2.0 migration [WPB-14635] (#3141) * chore: Core crypto 2.0 migration [WPB-14635] * Code review * Rollback to INFO logs --- .../CoreCryptoCentral.kt | 19 ++++++++++ .../ProteusClientCoreCryptoImpl.kt | 37 ++++++++++++++----- .../exceptions/ProteusException.kt | 18 ++++++--- gradle/libs.versions.toml | 2 +- .../kalium/logic/CoreCryptoExceptionMapper.kt | 25 ++++++------- 5 files changed, 72 insertions(+), 29 deletions(-) diff --git a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/CoreCryptoCentral.kt b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/CoreCryptoCentral.kt index 89c70247f9e..72faee95ae6 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/CoreCryptoCentral.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/CoreCryptoCentral.kt @@ -20,7 +20,10 @@ package com.wire.kalium.cryptography import com.wire.crypto.ClientId import com.wire.crypto.CoreCrypto import com.wire.crypto.CoreCryptoCallbacks +import com.wire.crypto.CoreCryptoLogLevel +import com.wire.crypto.CoreCryptoLogger import com.wire.crypto.coreCryptoDeferredInit +import com.wire.crypto.setLogger import com.wire.kalium.cryptography.MLSClientImpl.Companion.toCrlRegistration import com.wire.kalium.cryptography.exceptions.CryptographyException import java.io.File @@ -36,12 +39,28 @@ actual suspend fun coreCryptoCentral( key = databaseKey ) coreCrypto.setCallbacks(Callbacks()) + setLogger(CoreCryptoLoggerImpl(), CoreCryptoLogLevel.INFO) return CoreCryptoCentralImpl( cc = coreCrypto, rootDir = rootDir ) } +private class CoreCryptoLoggerImpl : CoreCryptoLogger { + override fun log(level: CoreCryptoLogLevel, message: String, context: String?) { + when (level) { + CoreCryptoLogLevel.TRACE -> kaliumLogger.v("$message. $context") + CoreCryptoLogLevel.DEBUG -> kaliumLogger.d("$message. $context") + CoreCryptoLogLevel.INFO -> kaliumLogger.i("$message. $context") + CoreCryptoLogLevel.WARN -> kaliumLogger.w("$message. $context") + CoreCryptoLogLevel.ERROR -> kaliumLogger.e("$message. $context") + CoreCryptoLogLevel.OFF -> { + // nop + } + } + } +} + private class Callbacks : CoreCryptoCallbacks { override suspend fun authorize(conversationId: ByteArray, clientId: ClientId): Boolean { // We always return true because our BE is currently enforcing that this constraint is always true diff --git a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt index fdc9bc2b69c..f972b6ea165 100644 --- a/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt +++ b/cryptography/src/commonJvmAndroid/kotlin/com.wire.kalium.cryptography/ProteusClientCoreCryptoImpl.kt @@ -28,6 +28,7 @@ import io.ktor.util.encodeBase64 import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.io.File +import com.wire.crypto.ProteusException as ProteusExceptionNative @Suppress("TooManyFunctions") class ProteusClientCoreCryptoImpl private constructor( @@ -145,13 +146,12 @@ class ProteusClientCoreCryptoImpl private constructor( private inline fun wrapException(b: () -> T): T { try { return b() - } catch (e: CoreCryptoException) { - val proteusLastErrorCode = coreCrypto.proteusLastErrorCode() + } catch (e: CoreCryptoException.Proteus) { throw ProteusException( - e.message, - ProteusException.fromProteusCode(proteusLastErrorCode.toInt()), - proteusLastErrorCode.toInt(), - e + message = e.message, + code = mapProteusExceptionToErrorCode(e.v1), + intCode = mapProteusExceptionToRawIntErrorCode(e.v1), + cause = e ) } catch (e: Exception) { throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, null, e) @@ -187,11 +187,11 @@ class ProteusClientCoreCryptoImpl private constructor( return ProteusClientCoreCryptoImpl(coreCrypto) } catch (exception: ProteusStorageMigrationException) { throw exception - } catch (e: CoreCryptoException) { + } catch (e: CoreCryptoException.Proteus) { throw ProteusException( message = e.message, - code = ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()), - intCode = coreCrypto.proteusLastErrorCode().toInt(), + code = mapProteusExceptionToErrorCode(e.v1), + intCode = mapProteusExceptionToRawIntErrorCode(e.v1), cause = e.cause ) } catch (e: Exception) { @@ -218,5 +218,24 @@ class ProteusClientCoreCryptoImpl private constructor( throw ProteusStorageMigrationException("Failed to migrate from crypto box at $rootDir", exception) } } + + private fun mapProteusExceptionToErrorCode(proteusException: ProteusExceptionNative): ProteusException.Code { + return when (proteusException) { + is ProteusExceptionNative.SessionNotFound -> ProteusException.Code.SESSION_NOT_FOUND + is ProteusExceptionNative.DuplicateMessage -> ProteusException.Code.DUPLICATE_MESSAGE + is ProteusExceptionNative.RemoteIdentityChanged -> ProteusException.Code.REMOTE_IDENTITY_CHANGED + is ProteusExceptionNative.Other -> ProteusException.fromProteusCode(proteusException.v1.toInt()) + } + } + + @Suppress("MagicNumber") + private fun mapProteusExceptionToRawIntErrorCode(proteusException: ProteusExceptionNative): Int { + return when (proteusException) { + is ProteusExceptionNative.SessionNotFound -> 102 + is ProteusExceptionNative.DuplicateMessage -> 209 + is ProteusExceptionNative.RemoteIdentityChanged -> 204 + is ProteusExceptionNative.Other -> proteusException.v1.toInt() + } + } } } diff --git a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt index 547d0deefa9..18750c29f92 100644 --- a/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt +++ b/cryptography/src/commonMain/kotlin/com/wire/kalium/cryptography/exceptions/ProteusException.kt @@ -176,19 +176,25 @@ open class ProteusException(message: String?, val code: Code, val intCode: Int?, } } - // Mapping source: - // https://github.com/wireapp/proteus/blob/2.x/crates/proteus-traits/src/lib.rs - // https://github.com/wireapp/wire-web-core/blob/7383e108f5e9d15d0b82c41ed504964667463cfc/packages/proteus/README.md + /** + * Those error codes are mapped directly from [com.wire.crypto.ProteusException]: + * - [Code.SESSION_NOT_FOUND] + * - [Code.REMOTE_IDENTITY_CHANGED] + * - [Code.DUPLICATE_MESSAGE] + * + * See the mapping: [com.wire.kalium.cryptography.ProteusClientCoreCryptoImpl.Companion.mapProteusExceptionToErrorCode] + * + * [Mapping sources](https://github.com/wireapp/proteus/blob/2.x/crates/proteus-traits/src/lib.rs) + * + * [Mapping source README](https://github.com/wireapp/wire-web-core/blob/7383e108f5e9d15d0b82c41ed504964667463cfc/packages/proteus/README.md) + */ fun fromProteusCode(code: Int): Code { @Suppress("MagicNumber") return when (code) { 501 -> Code.STORAGE_ERROR - 102 -> Code.SESSION_NOT_FOUND 3, 301, 302, 303 -> Code.DECODE_ERROR - 204 -> Code.REMOTE_IDENTITY_CHANGED 206, 207, 210 -> Code.INVALID_SIGNATURE 200, 201, 202, 205, 213 -> Code.INVALID_MESSAGE - 209 -> Code.DUPLICATE_MESSAGE 211, 212 -> Code.TOO_DISTANT_FUTURE 208 -> Code.OUTDATED_MESSAGE 300 -> Code.IDENTITY_ERROR diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8fe82ae3248..6c3b94a0f59 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ pbandk = "0.14.2" turbine = "1.1.0" avs = "10.0.1" jna = "5.14.0" -core-crypto = "1.0.2" +core-crypto = "2.0.0" core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1" completeKotlin = "1.1.0" desugar-jdk = "2.1.3" diff --git a/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/CoreCryptoExceptionMapper.kt b/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/CoreCryptoExceptionMapper.kt index a41535a1dc3..d2b7342a6d1 100644 --- a/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/CoreCryptoExceptionMapper.kt +++ b/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/CoreCryptoExceptionMapper.kt @@ -18,21 +18,20 @@ package com.wire.kalium.logic import com.wire.crypto.CoreCryptoException -import uniffi.core_crypto.CryptoError +import com.wire.crypto.MlsException actual fun mapMLSException(exception: Exception): MLSFailure = - if (exception is CoreCryptoException.CryptoException) { - when (exception.error) { - is CryptoError.WrongEpoch -> MLSFailure.WrongEpoch - is CryptoError.DuplicateMessage -> MLSFailure.DuplicateMessage - is CryptoError.BufferedFutureMessage -> MLSFailure.BufferedFutureMessage - is CryptoError.SelfCommitIgnored -> MLSFailure.SelfCommitIgnored - is CryptoError.UnmergedPendingGroup -> MLSFailure.UnmergedPendingGroup - is CryptoError.StaleProposal -> MLSFailure.StaleProposal - is CryptoError.StaleCommit -> MLSFailure.StaleCommit - is CryptoError.ConversationAlreadyExists -> MLSFailure.ConversationAlreadyExists - is CryptoError.MessageEpochTooOld -> MLSFailure.MessageEpochTooOld - is CryptoError.MlsException -> MLSFailure.InternalErrors + if (exception is CoreCryptoException.Mls) { + when (exception.v1) { + is MlsException.WrongEpoch -> MLSFailure.WrongEpoch + is MlsException.DuplicateMessage -> MLSFailure.DuplicateMessage + is MlsException.BufferedFutureMessage -> MLSFailure.BufferedFutureMessage + is MlsException.SelfCommitIgnored -> MLSFailure.SelfCommitIgnored + is MlsException.UnmergedPendingGroup -> MLSFailure.UnmergedPendingGroup + is MlsException.StaleProposal -> MLSFailure.StaleProposal + is MlsException.StaleCommit -> MLSFailure.StaleCommit + is MlsException.ConversationAlreadyExists -> MLSFailure.ConversationAlreadyExists + is MlsException.MessageEpochTooOld -> MLSFailure.MessageEpochTooOld else -> MLSFailure.Generic(exception) } } else { From 3d7c7122ebdc05f93773f3d5dee1155d5710647e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BBerko?= Date: Fri, 6 Dec 2024 14:01:09 +0100 Subject: [PATCH 03/16] fix: folders slow sync [WPB-14396] (#3146) * fix: made folders sync as optional during slow sync * move first sync to fetch fav folder * test fix * removed unused use case --- .../kalium/logic/data/sync/SlowSyncStatus.kt | 3 +- .../kalium/logic/feature/UserSessionScope.kt | 8 +---- .../folder/GetFavoriteFolderUseCase.kt | 22 +++++++++--- .../folder/SyncConversationFoldersUseCase.kt | 36 ------------------- .../kalium/logic/sync/slow/SlowSyncManager.kt | 3 +- .../kalium/logic/sync/slow/SlowSyncWorker.kt | 3 -- .../folder/GetFavoriteFolderUseCaseTest.kt | 29 ++++++++++++++- .../logic/sync/slow/SlowSyncWorkerTest.kt | 15 +------- .../kalium/persistence/ConversationFolders.sq | 3 ++ .../folder/ConversationFolderDAOImpl.kt | 1 + 10 files changed, 55 insertions(+), 68 deletions(-) delete mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/SyncConversationFoldersUseCase.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/sync/SlowSyncStatus.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/sync/SlowSyncStatus.kt index 5bb50ed3cf4..631400557e7 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/sync/SlowSyncStatus.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/sync/SlowSyncStatus.kt @@ -43,6 +43,5 @@ enum class SlowSyncStep { CONTACTS, JOINING_MLS_CONVERSATIONS, RESOLVE_ONE_ON_ONE_PROTOCOLS, - LEGAL_HOLD, - CONVERSATION_FOLDERS, + LEGAL_HOLD } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index e7e1a1637a8..543d5f9030c 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -210,8 +210,6 @@ import com.wire.kalium.logic.feature.conversation.RecoverMLSConversationsUseCase import com.wire.kalium.logic.feature.conversation.SyncConversationsUseCase import com.wire.kalium.logic.feature.conversation.SyncConversationsUseCaseImpl import com.wire.kalium.logic.feature.conversation.TypingIndicatorSyncManager -import com.wire.kalium.logic.feature.conversation.folder.SyncConversationFoldersUseCase -import com.wire.kalium.logic.feature.conversation.folder.SyncConversationFoldersUseCaseImpl import com.wire.kalium.logic.feature.conversation.keyingmaterials.KeyingMaterialsManager import com.wire.kalium.logic.feature.conversation.keyingmaterials.KeyingMaterialsManagerImpl import com.wire.kalium.logic.feature.conversation.mls.MLSOneOnOneConversationResolver @@ -981,9 +979,6 @@ class UserSessionScope internal constructor( systemMessageInserter ) - private val syncConversationFolders: SyncConversationFoldersUseCase - get() = SyncConversationFoldersUseCaseImpl(conversationFolderRepository) - private val syncConnections: SyncConnectionsUseCase get() = SyncConnectionsUseCaseImpl( connectionRepository = connectionRepository @@ -1102,8 +1097,7 @@ class UserSessionScope internal constructor( syncContacts, joinExistingMLSConversations, fetchLegalHoldForSelfUserFromRemoteUseCase, - oneOnOneResolver, - syncConversationFolders + oneOnOneResolver ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCase.kt index c8e7b950850..45565664e16 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCase.kt @@ -17,9 +17,13 @@ */ package com.wire.kalium.logic.feature.conversation.folder +import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.conversation.ConversationFolder import com.wire.kalium.logic.data.conversation.folders.ConversationFolderRepository import com.wire.kalium.logic.feature.conversation.folder.GetFavoriteFolderUseCase.Result +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.functional.flatMap +import com.wire.kalium.logic.functional.flatMapLeft import com.wire.kalium.logic.functional.fold /** @@ -41,9 +45,19 @@ internal class GetFavoriteFolderUseCaseImpl( ) : GetFavoriteFolderUseCase { override suspend operator fun invoke(): Result { - return conversationFolderRepository.getFavoriteConversationFolder().fold( - { Result.Failure }, - { Result.Success(it) } - ) + return conversationFolderRepository.getFavoriteConversationFolder() + .flatMapLeft { + if (it is StorageFailure.DataNotFound) { + conversationFolderRepository.fetchConversationFolders().flatMap { + conversationFolderRepository.getFavoriteConversationFolder() + } + } else { + Either.Left(it) + } + } + .fold( + { Result.Failure }, + { Result.Success(it) } + ) } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/SyncConversationFoldersUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/SyncConversationFoldersUseCase.kt deleted file mode 100644 index 216290437e3..00000000000 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/SyncConversationFoldersUseCase.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.kalium.logic.feature.conversation.folder - -import com.wire.kalium.logic.CoreFailure -import com.wire.kalium.logic.data.conversation.folders.ConversationFolderRepository -import com.wire.kalium.logic.functional.Either - -internal interface SyncConversationFoldersUseCase { - suspend operator fun invoke(): Either -} - -/** - * This use case will sync against the backend the conversation folders of the current user. - */ -internal class SyncConversationFoldersUseCaseImpl( - private val conversationRepository: ConversationFolderRepository, -) : SyncConversationFoldersUseCase { - override suspend operator fun invoke(): Either = conversationRepository.fetchConversationFolders() -} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt index a7bce64bfa5..f34f1ec3f8d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt @@ -210,7 +210,8 @@ internal class SlowSyncManager( * Useful when a new step is added to Slow Sync, or when we fix some bug in Slow Sync, * and we'd like to get all users to take advantage of the fix. */ - const val CURRENT_VERSION = 9 + const val CURRENT_VERSION = 8 + // because we already had version 9, the next version should be 10 val MIN_RETRY_DELAY = 1.seconds val MAX_RETRY_DELAY = 10.minutes diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt index 2022b6c0f32..35fc040ee08 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorker.kt @@ -26,7 +26,6 @@ import com.wire.kalium.logic.data.event.EventRepository import com.wire.kalium.logic.data.sync.SlowSyncStep import com.wire.kalium.logic.feature.connection.SyncConnectionsUseCase import com.wire.kalium.logic.feature.conversation.SyncConversationsUseCase -import com.wire.kalium.logic.feature.conversation.folder.SyncConversationFoldersUseCase import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCase import com.wire.kalium.logic.feature.legalhold.FetchLegalHoldForSelfUserFromRemoteUseCase @@ -72,7 +71,6 @@ internal class SlowSyncWorkerImpl( private val joinMLSConversations: JoinExistingMLSConversationsUseCase, private val fetchLegalHoldForSelfUserFromRemoteUseCase: FetchLegalHoldForSelfUserFromRemoteUseCase, private val oneOnOneResolver: OneOnOneResolver, - private val syncConversationFolders: SyncConversationFoldersUseCase, logger: KaliumLogger = kaliumLogger ) : SlowSyncWorker { @@ -104,7 +102,6 @@ internal class SlowSyncWorkerImpl( .continueWithStep(SlowSyncStep.CONTACTS, syncContacts::invoke) .continueWithStep(SlowSyncStep.JOINING_MLS_CONVERSATIONS, joinMLSConversations::invoke) .continueWithStep(SlowSyncStep.RESOLVE_ONE_ON_ONE_PROTOCOLS, oneOnOneResolver::resolveAllOneOnOneConversations) - .continueWithStep(SlowSyncStep.CONVERSATION_FOLDERS, syncConversationFolders::invoke) .flatMap { saveLastProcessedEventIdIfNeeded(lastProcessedEventIdToSaveOnSuccess) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCaseTest.kt index ca6fc8e156f..23064583cf3 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/folder/GetFavoriteFolderUseCaseTest.kt @@ -18,6 +18,7 @@ package com.wire.kalium.logic.feature.conversation.folder import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.StorageFailure import com.wire.kalium.logic.data.conversation.ConversationFolder import com.wire.kalium.logic.data.conversation.folders.ConversationFolderRepository import com.wire.kalium.logic.feature.conversation.folder.GetFavoriteFolderUseCase.Result @@ -28,6 +29,7 @@ import io.mockative.coEvery import io.mockative.coVerify import io.mockative.mock import io.mockative.once +import io.mockative.twice import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -53,7 +55,7 @@ class GetFavoriteFolderUseCaseTest { } @Test - fun givenFavoriteFolderDoesNotExist_WhenInvoked_ThenReturnFailure() = runTest { + fun givenFavoriteFolderReturnsFailure_WhenInvoked_ThenReturnFailure() = runTest { val (arrangement, getFavoriteFolderUseCase) = Arrangement() .withFavoriteFolder(Either.Left(CoreFailure.Unknown(null))) .arrange() @@ -67,6 +69,25 @@ class GetFavoriteFolderUseCaseTest { }.wasInvoked(exactly = once) } + @Test + fun givenFavoriteFolderDoesNotExist_WhenInvoked_ThenFetchConversationFoldersIsTriggered() = runTest { + val (arrangement, getFavoriteFolderUseCase) = Arrangement() + .withFavoriteFolder(Either.Left(StorageFailure.DataNotFound)) + .withFetchConversationFolders(Either.Right(Unit)) + .arrange() + + val result = getFavoriteFolderUseCase() + + assertIs(result) + + coVerify { + arrangement.conversationFolderRepository.getFavoriteConversationFolder() + }.wasInvoked(exactly = twice) + coVerify { + arrangement.conversationFolderRepository.fetchConversationFolders() + }.wasInvoked(exactly = once) + } + private class Arrangement { @Mock val conversationFolderRepository = mock(ConversationFolderRepository::class) @@ -81,6 +102,12 @@ class GetFavoriteFolderUseCaseTest { }.returns(either) } + suspend fun withFetchConversationFolders(either: Either) = apply { + coEvery { + conversationFolderRepository.fetchConversationFolders() + }.returns(either) + } + fun arrange(block: Arrangement.() -> Unit = { }) = apply(block).let { this to getFavoriteFolderUseCase } } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt index facf5ddb70b..6a89816ead8 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncWorkerTest.kt @@ -24,7 +24,6 @@ import com.wire.kalium.logic.data.sync.SlowSyncStep import com.wire.kalium.logic.data.user.LegalHoldStatus import com.wire.kalium.logic.feature.connection.SyncConnectionsUseCase import com.wire.kalium.logic.feature.conversation.SyncConversationsUseCase -import com.wire.kalium.logic.feature.conversation.folder.SyncConversationFoldersUseCase import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver import com.wire.kalium.logic.feature.featureConfig.SyncFeatureConfigsUseCase import com.wire.kalium.logic.feature.legalhold.FetchLegalHoldForSelfUserFromRemoteUseCase @@ -72,7 +71,6 @@ class SlowSyncWorkerTest { .withJoinMLSConversationsSuccess() .withResolveOneOnOneConversationsSuccess() .withFetchLegalHoldStatusSuccess() - .withSyncFoldersSuccess() .arrange() worker.slowSyncStepsFlow(successfullyMigration).collect() @@ -410,7 +408,6 @@ class SlowSyncWorkerTest { .withJoinMLSConversationsSuccess() .withResolveOneOnOneConversationsSuccess() .withFetchLegalHoldStatusSuccess() - .withSyncFoldersSuccess() .arrange() slowSyncWorker.slowSyncStepsFlow(successfullyMigration).collect() @@ -514,9 +511,6 @@ class SlowSyncWorkerTest { @Mock val fetchLegalHoldForSelfUserFromRemoteUseCase = mock(FetchLegalHoldForSelfUserFromRemoteUseCase::class) - @Mock - val syncConversationFoldersUseCase = mock(SyncConversationFoldersUseCase::class) - init { runBlocking { withLastProcessedEventIdReturning(Either.Right("lastProcessedEventId")) @@ -534,8 +528,7 @@ class SlowSyncWorkerTest { joinMLSConversations = joinMLSConversations, updateSupportedProtocols = updateSupportedProtocols, fetchLegalHoldForSelfUserFromRemoteUseCase = fetchLegalHoldForSelfUserFromRemoteUseCase, - oneOnOneResolver = oneOnOneResolver, - syncConversationFolders = syncConversationFoldersUseCase + oneOnOneResolver = oneOnOneResolver ) suspend fun withSyncSelfUserFailure() = apply { @@ -651,12 +644,6 @@ class SlowSyncWorkerTest { oneOnOneResolver.resolveAllOneOnOneConversations(any()) }.returns(success) } - - suspend fun withSyncFoldersSuccess() = apply { - coEvery { - syncConversationFoldersUseCase.invoke() - }.returns(success) - } } private companion object { diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq index e1e5d8f14cb..db44a482e77 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq @@ -62,3 +62,6 @@ DELETE FROM LabeledConversation WHERE conversation_id = ? AND folder_id = ?; clearFolders: DELETE FROM ConversationFolder; + +clearLabeledConversations: +DELETE FROM LabeledConversation; diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt index 3c19bd871b3..3316f030bd9 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt @@ -79,6 +79,7 @@ class ConversationFolderDAOImpl internal constructor( override suspend fun updateConversationFolders(folderWithConversationsList: List) = withContext(coroutineContext) { conversationFoldersQueries.transaction { + conversationFoldersQueries.clearLabeledConversations() conversationFoldersQueries.clearFolders() folderWithConversationsList.forEach { folderWithConversations -> conversationFoldersQueries.upsertFolder( From 645b7dbc7655f504fe77aa10c3a7c94e48bb4c92 Mon Sep 17 00:00:00 2001 From: boris Date: Fri, 6 Dec 2024 15:24:48 +0200 Subject: [PATCH 04/16] feat: API v7 changes (#3145) --- .../api/v0/authenticated/ConversationApiV0.kt | 2 +- .../api/v7/authenticated/ConversationApiV7.kt | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt index 145a833620f..600c6102788 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v0/authenticated/ConversationApiV0.kt @@ -306,7 +306,7 @@ internal open class ConversationApiV0 internal constructor( } } - private suspend fun handleServiceAddedResponse( + protected suspend fun handleServiceAddedResponse( httpResponse: HttpResponse ): NetworkResponse = when (httpResponse.status) { diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v7/authenticated/ConversationApiV7.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v7/authenticated/ConversationApiV7.kt index cd10ebd3b2f..5e0d237b95a 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/api/v7/authenticated/ConversationApiV7.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/api/v7/authenticated/ConversationApiV7.kt @@ -19,16 +19,23 @@ package com.wire.kalium.network.api.v7.authenticated import com.wire.kalium.network.AuthenticatedNetworkClient +import com.wire.kalium.network.api.authenticated.conversation.AddServiceRequest import com.wire.kalium.network.api.authenticated.conversation.ConversationResponse import com.wire.kalium.network.api.authenticated.conversation.ConversationResponseV6 import com.wire.kalium.network.api.model.ApiModelMapper import com.wire.kalium.network.api.model.ApiModelMapperImpl +import com.wire.kalium.network.api.model.ConversationId +import com.wire.kalium.network.api.model.ServiceAddedResponse import com.wire.kalium.network.api.model.UserId import com.wire.kalium.network.api.v6.authenticated.ConversationApiV6 +import com.wire.kalium.network.exceptions.KaliumException import com.wire.kalium.network.utils.NetworkResponse import com.wire.kalium.network.utils.mapSuccess import com.wire.kalium.network.utils.wrapKaliumResponse import io.ktor.client.request.get +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import okio.IOException internal open class ConversationApiV7 internal constructor( authenticatedNetworkClient: AuthenticatedNetworkClient, @@ -42,7 +49,21 @@ internal open class ConversationApiV7 internal constructor( apiModelMapper.fromApiV6(it) } + override suspend fun addService( + addServiceRequest: AddServiceRequest, + conversationId: ConversationId + ): NetworkResponse = try { + httpClient.post("$PATH_BOT/$PATH_CONVERSATIONS/${conversationId.value}") { + setBody(addServiceRequest) + }.let { response -> + handleServiceAddedResponse(response) + } + } catch (e: IOException) { + NetworkResponse.Error(KaliumException.GenericError(e)) + } + protected companion object { const val PATH_ONE_2_ONE_CONVERSATIONS = "one2one-conversations" + const val PATH_BOT = "bot" } } From 212ddce4c8974f358ea8537e83d78940dd026c4b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:56:30 +0000 Subject: [PATCH 05/16] =?UTF-8?q?fix:=20update=20federation=20flag=20when?= =?UTF-8?q?=20fetching=20server=20config=20[WPB-14728]=20=F0=9F=8D=92=20(#?= =?UTF-8?q?3144)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Commit with unresolved merge conflicts * Update ServerConfigRepository.kt * Update ServerConfigurationDAO.kt --------- Co-authored-by: Mohamad Jaara --- .../server/CustomServerConfigRepository.kt | 6 ++- .../server/ServerConfigRepository.kt | 22 +++++++--- .../server/UpdateApiVersionsUseCase.kt | 2 +- .../CustomServerConfigRepositoryTest.kt | 6 +-- .../ServerConfigRepositoryTest.kt | 4 +- .../server/UpdateApiVersionUseCaseTest.kt | 26 ++++++------ .../kalium/persistence/GlobalDBBaseTest.kt | 1 + .../kalium/persistence/ServerConfiguration.sq | 4 +- .../daokaliumdb/ServerConfigurationDAO.kt | 8 ++-- .../daokaliumdb/ServerConfigurationDAOTest.kt | 41 +++++++++++++++---- .../persistence/globalDB/AccountsDAOTest.kt | 7 ++++ 11 files changed, 90 insertions(+), 37 deletions(-) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/CustomServerConfigRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/CustomServerConfigRepository.kt index 519f7943258..8025edc4393 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/CustomServerConfigRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/CustomServerConfigRepository.kt @@ -94,7 +94,11 @@ internal class CustomServerConfigDataSource internal constructor( val storedConfigId = serverConfigurationDAO.configByLinks(serverConfigMapper.toEntity(links))?.id if (storedConfigId != null) { // if already exists then just update it - serverConfigurationDAO.updateApiVersion(storedConfigId, metadata.commonApiVersion.version) + serverConfigurationDAO.updateServerMetaData( + id = storedConfigId, + federation = metadata.federation, + commonApiVersion = metadata.commonApiVersion.version + ) if (metadata.federation) serverConfigurationDAO.setFederationToTrue(storedConfigId) storedConfigId } else { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt index faef6688735..4fd77d402b4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt @@ -57,9 +57,9 @@ interface ServerConfigRepository { suspend fun fetchApiVersionAndStore(links: ServerConfig.Links): Either /** - * update the api version of a locally stored config + * update the api version and federation status of a locally stored config */ - suspend fun updateConfigApiVersion(serverConfig: ServerConfig): Either + suspend fun updateConfigMetaData(serverConfig: ServerConfig): Either /** * Return the server links and metadata for the given userId @@ -92,7 +92,11 @@ internal class ServerConfigDataSource( val storedConfigId = dao.configByLinks(serverConfigMapper.toEntity(links))?.id if (storedConfigId != null) { // if already exists then just update it - dao.updateApiVersion(storedConfigId, metadata.commonApiVersion.version) + dao.updateServerMetaData( + id = storedConfigId, + federation = metadata.federation, + commonApiVersion = metadata.commonApiVersion.version + ) if (metadata.federation) dao.setFederationToTrue(storedConfigId) storedConfigId } else { @@ -132,9 +136,17 @@ internal class ServerConfigDataSource( storeConfig(links, metaData) } - override suspend fun updateConfigApiVersion(serverConfig: ServerConfig): Either = + override suspend fun updateConfigMetaData(serverConfig: ServerConfig): Either = fetchMetadata(serverConfig.links) - .flatMap { wrapStorageRequest { dao.updateApiVersion(serverConfig.id, it.commonApiVersion.version) } } + .flatMap { newMetaData -> + wrapStorageRequest { + dao.updateServerMetaData( + id = serverConfig.id, + federation = newMetaData.federation, + commonApiVersion = newMetaData.commonApiVersion.version + ) + } + } override suspend fun configForUser(userId: UserId): Either = wrapStorageRequest { dao.configForUser(userId.toDao()) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionsUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionsUseCase.kt index 0d6eb9dea57..db4d2746aa0 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionsUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionsUseCase.kt @@ -80,6 +80,6 @@ class UpdateApiVersionsUseCaseImpl internal constructor( } else { null } - serverConfigRepoProvider(serverConfig, proxyCredentials).updateConfigApiVersion(serverConfig) + serverConfigRepoProvider(serverConfig, proxyCredentials).updateConfigMetaData(serverConfig) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/CustomServerConfigRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/CustomServerConfigRepositoryTest.kt index 401b0fbc908..3737f9b8900 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/CustomServerConfigRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/CustomServerConfigRepositoryTest.kt @@ -78,7 +78,7 @@ class CustomServerConfigRepositoryTest { arrangement.serverConfigurationDAO.insert(any()) }.wasNotInvoked() coVerify { - arrangement.serverConfigurationDAO.updateApiVersion(any(), any()) + arrangement.serverConfigurationDAO.updateServerMetaData(any(), any(), any()) }.wasInvoked(exactly = once) coVerify { arrangement.serverConfigurationDAO.setFederationToTrue(any()) @@ -106,7 +106,7 @@ class CustomServerConfigRepositoryTest { arrangement.serverConfigurationDAO.insert(any()) }.wasInvoked(exactly = once) coVerify { - arrangement.serverConfigurationDAO.updateApiVersion(any(), any()) + arrangement.serverConfigurationDAO.updateServerMetaData(any(), any(), any()) }.wasNotInvoked() coVerify { arrangement.serverConfigurationDAO.setFederationToTrue(any()) @@ -145,7 +145,7 @@ class CustomServerConfigRepositoryTest { arrangement.serverConfigurationDAO.insert(any()) }.wasInvoked(exactly = once) coVerify { - arrangement.serverConfigurationDAO.updateApiVersion(any(), any()) + arrangement.serverConfigurationDAO.updateServerMetaData(any(), any(), any()) }.wasNotInvoked() coVerify { arrangement.serverConfigurationDAO.setFederationToTrue(any()) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/ServerConfigRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/ServerConfigRepositoryTest.kt index 79c65a3dd2a..8f2e1416856 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/ServerConfigRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/ServerConfigRepositoryTest.kt @@ -131,7 +131,7 @@ class ServerConfigRepositoryTest { arrangement.serverConfigDAO.insert(any()) }.wasNotInvoked() coVerify { - arrangement.serverConfigDAO.updateApiVersion(any(), any()) + arrangement.serverConfigDAO.updateServerMetaData(any(), any(), any()) }.wasInvoked(exactly = once) coVerify { arrangement.serverConfigDAO.setFederationToTrue(any()) @@ -159,7 +159,7 @@ class ServerConfigRepositoryTest { arrangement.serverConfigDAO.insert(any()) }.wasInvoked(exactly = once) coVerify { - arrangement.serverConfigDAO.updateApiVersion(any(), any()) + arrangement.serverConfigDAO.updateServerMetaData(any(), any(), any()) }.wasNotInvoked() coVerify { arrangement.serverConfigDAO.setFederationToTrue(any()) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionUseCaseTest.kt index d883b34e4fe..3401f58963a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionUseCaseTest.kt @@ -77,7 +77,7 @@ class UpdateApiVersionUseCaseTest { ) ) ) - withUpdateConfigApiVersion(serverConfig1, Either.Right(Unit)) + withUpdateConfigMetaData(serverConfig1, Either.Right(Unit)) } updateApiVersionsUseCase() @@ -88,7 +88,7 @@ class UpdateApiVersionUseCaseTest { }.wasNotInvoked() coVerify { - arrangement.serverConfigRepository1.updateConfigApiVersion(eq(serverConfig1)) + arrangement.serverConfigRepository1.updateConfigMetaData(eq(serverConfig1)) }.wasInvoked(exactly = once) } @@ -111,7 +111,7 @@ class UpdateApiVersionUseCaseTest { ) ) ) - withUpdateConfigApiVersion(serverConfig1, Either.Right(Unit)) + withUpdateConfigMetaData(serverConfig1, Either.Right(Unit)) } updateApiVersionsUseCase() @@ -122,7 +122,7 @@ class UpdateApiVersionUseCaseTest { }.wasNotInvoked() coVerify { - arrangement.serverConfigRepository1.updateConfigApiVersion(any()) + arrangement.serverConfigRepository1.updateConfigMetaData(any()) }.wasInvoked(exactly = once) } @@ -145,7 +145,7 @@ class UpdateApiVersionUseCaseTest { ) ) ) - withUpdateConfigApiVersion(serverConfig1, Either.Right(Unit)) + withUpdateConfigMetaData(serverConfig1, Either.Right(Unit)) withProxyCredForUser(userId1.toDao(), ProxyCredentialsEntity("user", "pass")) } @@ -158,7 +158,7 @@ class UpdateApiVersionUseCaseTest { }.wasInvoked(exactly = once) coVerify { - arrangement.serverConfigRepository1.updateConfigApiVersion(any()) + arrangement.serverConfigRepository1.updateConfigMetaData(any()) }.wasInvoked(exactly = once) } @@ -190,8 +190,8 @@ class UpdateApiVersionUseCaseTest { ) ) ) - withUpdateConfigApiVersion(serverConfig1, Either.Right(Unit)) - withUpdateConfigApiVersion(serverConfig2, Either.Right(Unit)) + withUpdateConfigMetaData(serverConfig1, Either.Right(Unit)) + withUpdateConfigMetaData(serverConfig2, Either.Right(Unit)) withProxyCredForUser(userId2.toDao(), ProxyCredentialsEntity("user", "pass")) } @@ -208,11 +208,11 @@ class UpdateApiVersionUseCaseTest { }.wasNotInvoked() coVerify { - arrangement.serverConfigRepository1.updateConfigApiVersion(any()) + arrangement.serverConfigRepository1.updateConfigMetaData(any()) }.wasInvoked(exactly = once) coVerify { - arrangement.serverConfigRepository2.updateConfigApiVersion(any()) + arrangement.serverConfigRepository2.updateConfigMetaData(any()) }.wasInvoked(exactly = once) } @@ -249,16 +249,16 @@ class UpdateApiVersionUseCaseTest { }.returns(result) } - suspend fun withUpdateConfigApiVersion( + suspend fun withUpdateConfigMetaData( serverConfig: ServerConfig, result: Either ) { when (serverConfig.id) { serverConfig1.id -> - coEvery { serverConfigRepository1.updateConfigApiVersion(any()) } + coEvery { serverConfigRepository1.updateConfigMetaData(any()) } .returns(result) - serverConfig2.id -> coEvery { serverConfigRepository2.updateConfigApiVersion(any()) } + serverConfig2.id -> coEvery { serverConfigRepository2.updateConfigMetaData(any()) } .returns(result) else -> throw IllegalArgumentException("Unexpected server config: $serverConfig") diff --git a/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt b/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt index edb29062eb1..df3c50b7c8d 100644 --- a/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt +++ b/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt @@ -19,6 +19,7 @@ package com.wire.kalium.persistence import com.wire.kalium.persistence.db.GlobalDatabaseBuilder +import kotlinx.coroutines.test.TestDispatcher actual abstract class GlobalDBBaseTest { actual fun deleteDatabase() { diff --git a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq index 48a8d2fe6fe..4fa81bcab28 100644 --- a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq +++ b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq @@ -28,8 +28,8 @@ insert: INSERT OR FAIL INTO ServerConfiguration(id, apiBaseUrl, accountBaseUrl, webSocketBaseUrl, blackListUrl, teamsUrl, websiteUrl, title, isOnPremises, federation, domain, commonApiVersion, apiProxyHost, apiProxyNeedsAuthentication, apiProxyPort) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?,?); -updateApiVersion: -UPDATE ServerConfiguration SET commonApiVersion = ? WHERE id = ?; +updateServerMetaData: +UPDATE ServerConfiguration SET federation = ?, commonApiVersion = ? WHERE id = ?; /** this function will be used when a config get updated from v0 where domain can be null */ updateApiVersionAndDomain: diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt index 7774eae162a..04c83e96f77 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt @@ -125,8 +125,8 @@ interface ServerConfigurationDAO { suspend fun allConfig(): List fun configById(id: String): ServerConfigEntity? suspend fun configByLinks(links: ServerConfigEntity.Links): ServerConfigEntity? - suspend fun updateApiVersion(id: String, commonApiVersion: Int) suspend fun getCommonApiVersion(domain: String): Int + suspend fun updateServerMetaData(id: String, federation: Boolean, commonApiVersion: Int) suspend fun updateApiVersionAndDomain(id: String, domain: String, commonApiVersion: Int) suspend fun configForUser(userId: UserIDEntity): ServerConfigEntity? suspend fun setFederationToTrue(id: String) @@ -210,8 +210,10 @@ internal class ServerConfigurationDAOImpl internal constructor( }.executeAsOneOrNull() } - override suspend fun updateApiVersion(id: String, commonApiVersion: Int) = withContext(queriesContext) { - queries.updateApiVersion(commonApiVersion, id) + override suspend fun updateServerMetaData(id: String, federation: Boolean, commonApiVersion: Int) { + withContext(queriesContext) { + queries.updateServerMetaData(federation, commonApiVersion, id) + } } override suspend fun getCommonApiVersion(domain: String): Int = withContext(queriesContext) { diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt index 5ab0fe61e87..befaae85ff9 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt @@ -24,17 +24,26 @@ import com.wire.kalium.persistence.GlobalDBBaseTest import com.wire.kalium.persistence.db.GlobalDatabaseBuilder import com.wire.kalium.persistence.model.ServerConfigEntity import com.wire.kalium.persistence.utils.stubs.newServerConfig +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import kotlinx.coroutines.withContext import kotlin.test.AfterTest import kotlin.test.BeforeTest +import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull -@OptIn(ExperimentalCoroutinesApi::class) class ServerConfigurationDAOTest : GlobalDBBaseTest() { private val config1 = newServerConfig(id = 1) @@ -142,13 +151,31 @@ class ServerConfigurationDAOTest : GlobalDBBaseTest() { @Test fun givenNewApiVersion_thenItCanBeUpdated() = runTest { - insertConfig(config1) - val newVersion = config1.metaData.copy(apiVersion = 2) - val expected = config1.copy(metaData = newVersion) + val oldConfig = config1.copy( + metaData = config1.metaData.copy( + apiVersion = 1, + federation = false + ), + ) - globalDatabaseBuilder.serverConfigurationDAO.updateApiVersion(config1.id, newVersion.apiVersion) - val actual = globalDatabaseBuilder.serverConfigurationDAO.configById(config1.id) - assertEquals(expected, actual) + val newVersion = config1.metaData.copy( + apiVersion = 2, + federation = true + ) + + val expected = oldConfig.copy(metaData = newVersion) + + insertConfig(oldConfig) + globalDatabaseBuilder.serverConfigurationDAO.updateServerMetaData( + id = oldConfig.id, + federation = true, + commonApiVersion = 2 + ) + globalDatabaseBuilder.serverConfigurationDAO.configById(oldConfig.id) + .also { actual -> + assertEquals(expected.metaData.federation, actual!!.metaData.federation) + assertEquals(expected.metaData.apiVersion, actual!!.metaData.apiVersion) + } } @Test diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt index 31ba21d5bb5..d5d55507e5e 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt @@ -29,8 +29,15 @@ import com.wire.kalium.persistence.db.GlobalDatabaseBuilder import com.wire.kalium.persistence.model.LogoutReason import com.wire.kalium.persistence.model.ServerConfigEntity import com.wire.kalium.persistence.model.SsoIdEntity +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals From d8b69f1202e0ea88889c98bf1e2f9cd5016d197c Mon Sep 17 00:00:00 2001 From: Yamil Medina Date: Fri, 6 Dec 2024 13:44:11 -0300 Subject: [PATCH 06/16] revert: conference simulcast support (WPB-11480) (#3107) (#3149) * Revert "feat: conference simulcast support (WPB-11480) (#3107)" This reverts commit 9273703fe301924bf7ae8c17723c57bb443df41f. * chore: empty commit bump --- .../wire/kalium/logic/data/call/CallClient.kt | 42 ++----------------- gradle/libs.versions.toml | 18 ++++---- .../logic/feature/call/CallManagerImpl.kt | 11 ++--- .../serialization/LenientJsonSerializer.kt | 32 -------------- 4 files changed, 16 insertions(+), 87 deletions(-) delete mode 100644 util/src/commonMain/kotlin/com.wire.kalium.util/serialization/LenientJsonSerializer.kt diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallClient.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallClient.kt index 0c2f3bb9b79..7545545157e 100644 --- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallClient.kt +++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallClient.kt @@ -18,55 +18,21 @@ package com.wire.kalium.logic.data.call -import com.wire.kalium.util.serialization.LenientJsonSerializer -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.nullable -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json @Serializable data class CallClient( @SerialName("userid") val userId: String, @SerialName("clientid") val clientId: String, - @SerialName("in_subconv") val isMemberOfSubconversation: Boolean = false, - @SerialName("quality") - @Serializable(with = CallQuality.CallQualityAsIntSerializer::class) - val quality: CallQuality = CallQuality.LOW + @SerialName("in_subconv") val isMemberOfSubconversation: Boolean = false ) @Serializable data class CallClientList( @SerialName("clients") val clients: List ) { - fun toJsonString(): String = LenientJsonSerializer.json.encodeToString(serializer(), this) -} - -enum class CallQuality { - ANY, - LOW, - HIGH; - - data object CallQualityAsIntSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("quality", PrimitiveKind.INT).nullable - - override fun serialize(encoder: Encoder, value: CallQuality) { - encoder.encodeInt(value.ordinal) - } - - @OptIn(ExperimentalSerializationApi::class) - override fun deserialize(decoder: Decoder): CallQuality { - val value = if (decoder.decodeNotNullMark()) decoder.decodeInt() else 0 - return when (value) { - 1 -> LOW - 2 -> HIGH - else -> ANY - } - } - } + // TODO(optimization): Use a shared Json instance instead of creating one every time. + fun toJsonString(): String = Json { isLenient = true }.encodeToString(serializer(), this) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c3b94a0f59..9060b93e117 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ activity-compose = "1.9.0" app-compat = "1.6.1" android-paging3 = "3.2.1" cli-kt = "3.5.0" -coroutines = "1.8.1" +coroutines = "1.8.0" compose-compiler = "1.5.13" compose-ui = "1.6.6" compose-material = "1.6.6" @@ -17,12 +17,12 @@ okio = "3.9.0" ok-http = "4.12.0" mockative = "2.2.0" android-work = "2.9.0" -android-test-runner = "1.6.2" -android-test-core-ktx = "1.6.1" -android-test-rules = "1.6.1" -android-test-core = "1.6.1" +android-test-runner = "1.5.2" +android-test-core-ktx = "1.5.0" +android-test-rules = "1.5.0" +android-test-core = "1.5.0" androidx-arch = "2.2.0" -androidx-test-orchestrator = "1.5.1" +androidx-test-orchestrator = "1.4.2" androidx-sqlite = "2.4.0" benasher-uuid = "0.8.0" ktx-datetime = { strictly = "0.5.0" } @@ -37,12 +37,12 @@ sqldelight = "2.0.1" sqlcipher-android = "4.5.6" pbandk = "0.14.2" turbine = "1.1.0" -avs = "10.0.1" +avs = "9.10.16" jna = "5.14.0" core-crypto = "2.0.0" core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1" completeKotlin = "1.1.0" -desugar-jdk = "2.1.3" +desugar-jdk = "2.0.4" kermit = "2.0.3" detekt = "1.23.6" agp = "8.5.2" @@ -50,7 +50,7 @@ dokka = "1.8.20" carthage = "0.0.1" libsodiumBindings = "0.8.7" protobufCodegen = "0.9.4" -annotation = "1.9.1" +annotation = "1.7.1" mordant = "2.0.0-beta13" apache-tika = "2.9.2" mockk = "1.13.10" diff --git a/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt b/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt index 90244f7eb76..693c76d98f9 100644 --- a/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt +++ b/logic/src/commonJvmAndroid/kotlin/com/wire/kalium/logic/feature/call/CallManagerImpl.kt @@ -463,19 +463,14 @@ class CallManagerImpl internal constructor( callClients: CallClientList ) { withCalling { - // Mapping Needed to support calls between federated and non federated environments (domain separation) + // Needed to support calls between federated and non federated environments val clients = callClients.clients.map { callClient -> CallClient( - userId = federatedIdMapper.parseToFederatedId(callClient.userId), - clientId = callClient.clientId, - isMemberOfSubconversation = callClient.isMemberOfSubconversation, - quality = callClient.quality + federatedIdMapper.parseToFederatedId(callClient.userId), + callClient.clientId ) } val clientsJson = CallClientList(clients).toJsonString() - callingLogger.d( - "$TAG - wcall_request_video_streams() called -> Requesting video streams for conversation = ${conversationId.toLogString()}" - ) val conversationIdString = federatedIdMapper.parseToFederatedId(conversationId) calling.wcall_request_video_streams( inst = it, diff --git a/util/src/commonMain/kotlin/com.wire.kalium.util/serialization/LenientJsonSerializer.kt b/util/src/commonMain/kotlin/com.wire.kalium.util/serialization/LenientJsonSerializer.kt deleted file mode 100644 index 3d6507cf8ff..00000000000 --- a/util/src/commonMain/kotlin/com.wire.kalium.util/serialization/LenientJsonSerializer.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.kalium.util.serialization - -import kotlinx.serialization.json.Json - -/** - * The json serializer for shared usage. - */ -object LenientJsonSerializer { - - val json = Json { - isLenient = true - encodeDefaults = true - ignoreUnknownKeys = true - } -} From 46ab0bc9d2bbb0a2190f93eef72c6e6749943502 Mon Sep 17 00:00:00 2001 From: Mohamad Jaara Date: Fri, 6 Dec 2024 23:48:15 +0100 Subject: [PATCH 07/16] feat: set api v7 as supported (#3152) Co-authored-by: Vitor Hugo Schwaab --- .../kotlin/com/wire/kalium/network/BackendMetaDataUtil.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/src/commonMain/kotlin/com/wire/kalium/network/BackendMetaDataUtil.kt b/network/src/commonMain/kotlin/com/wire/kalium/network/BackendMetaDataUtil.kt index be435578cf3..179e1b936fa 100644 --- a/network/src/commonMain/kotlin/com/wire/kalium/network/BackendMetaDataUtil.kt +++ b/network/src/commonMain/kotlin/com/wire/kalium/network/BackendMetaDataUtil.kt @@ -23,11 +23,11 @@ import com.wire.kalium.network.api.unbound.versioning.VersionInfoDTO // They are not truly constants as set is not a primitive type, yet are treated as one in this context @Suppress("MagicNumber") -val SupportedApiVersions = setOf(0, 1, 2, 4, 5, 6) +val SupportedApiVersions: Set = setOf(0, 1, 2, 4, 5, 6, 7) // They are not truly constants as set is not a primitive type, yet are treated as one in this context @Suppress("MagicNumber") -val DevelopmentApiVersions = setOf(7) +val DevelopmentApiVersions: Set = emptySet() // You can use scripts/generate_new_api_version.sh or gradle task network:generateNewApiVersion to // bump API version and generate all needed classes From 26b7d4b4dcf2b50b64a3979e6211094a7a5d63d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BBerko?= Date: Tue, 10 Dec 2024 11:34:56 +0100 Subject: [PATCH 08/16] feat: user folders [WPB-14442] (#3147) * feat: user folders * detekt fix * removed duplicated sq query * fix: favorite folder error handling, non paginatated conversations from folder * tests fix --- .../data/conversation/ConversationFilter.kt | 23 ++++++++-- .../data/conversation/ConversationFolder.kt | 9 ++-- .../data/conversation/ConversationMapper.kt | 17 +++---- .../conversation/ConversationRepository.kt | 4 +- .../ConversationRepositoryExtensions.kt | 2 +- .../folders/ConversationFolderRepository.kt | 10 +++- .../feature/conversation/ConversationScope.kt | 4 ++ ...rsationListDetailsWithEventsUseCaseImpl.kt | 24 +++++++--- .../ObserveConversationsFromFolderUseCase.kt | 10 ++-- .../folder/ObserveUserFoldersUseCase.kt | 46 +++++++++++++++++++ .../kalium/persistence/ConversationFolders.sq | 10 ++-- .../folder/ConversationFolderDAO.kt | 3 +- .../folder/ConversationFolderDAOImpl.kt | 20 +++++++- .../folder/ConversationFolderDAOTest.kt | 8 ++-- 14 files changed, 151 insertions(+), 39 deletions(-) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveUserFoldersUseCase.kt diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFilter.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFilter.kt index f1ca2a7bf39..a76fac75ca0 100644 --- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFilter.kt +++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFilter.kt @@ -17,9 +17,22 @@ */ package com.wire.kalium.logic.data.conversation -enum class ConversationFilter { - ALL, - FAVORITES, - GROUPS, - ONE_ON_ONE +import kotlinx.serialization.Serializable + +@Serializable +sealed class ConversationFilter { + @Serializable + data object All : ConversationFilter() + + @Serializable + data object Favorites : ConversationFilter() + + @Serializable + data object Groups : ConversationFilter() + + @Serializable + data object OneOnOne : ConversationFilter() + + @Serializable + data class Folder(val folderName: String, val folderId: String) : ConversationFilter() } diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFolder.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFolder.kt index 675e9f5794f..cf64352dd3d 100644 --- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFolder.kt +++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationFolder.kt @@ -18,11 +18,14 @@ package com.wire.kalium.logic.data.conversation import com.wire.kalium.logic.data.id.QualifiedID +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +@Serializable data class ConversationFolder( - val id: String, - val name: String, - val type: FolderType + @SerialName("id") val id: String, + @SerialName("name") val name: String, + @SerialName("folder_type") val type: FolderType ) data class FolderWithConversations( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt index cc2d9995a75..6382559ee23 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationMapper.kt @@ -676,15 +676,16 @@ internal fun ConversationEntity.VerificationStatus.toModel(): Conversation.Verif } internal fun ConversationFilter.toDao(): ConversationFilterEntity = when (this) { - ConversationFilter.ALL -> ConversationFilterEntity.ALL - ConversationFilter.FAVORITES -> ConversationFilterEntity.FAVORITES - ConversationFilter.GROUPS -> ConversationFilterEntity.GROUPS - ConversationFilter.ONE_ON_ONE -> ConversationFilterEntity.ONE_ON_ONE + ConversationFilter.All -> ConversationFilterEntity.ALL + ConversationFilter.Favorites -> ConversationFilterEntity.FAVORITES + ConversationFilter.Groups -> ConversationFilterEntity.GROUPS + ConversationFilter.OneOnOne -> ConversationFilterEntity.ONE_ON_ONE + is ConversationFilter.Folder -> ConversationFilterEntity.ALL // TODO think how to secure that } internal fun ConversationFilterEntity.toModel(): ConversationFilter = when (this) { - ConversationFilterEntity.ALL -> ConversationFilter.ALL - ConversationFilterEntity.FAVORITES -> ConversationFilter.FAVORITES - ConversationFilterEntity.GROUPS -> ConversationFilter.GROUPS - ConversationFilterEntity.ONE_ON_ONE -> ConversationFilter.ONE_ON_ONE + ConversationFilterEntity.ALL -> ConversationFilter.All + ConversationFilterEntity.FAVORITES -> ConversationFilter.Favorites + ConversationFilterEntity.GROUPS -> ConversationFilter.Groups + ConversationFilterEntity.ONE_ON_ONE -> ConversationFilter.OneOnOne } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt index f199fe862f7..2508b24762a 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt @@ -132,12 +132,12 @@ interface ConversationRepository { suspend fun observeConversationList(): Flow> suspend fun observeConversationListDetails( fromArchive: Boolean, - conversationFilter: ConversationFilter = ConversationFilter.ALL + conversationFilter: ConversationFilter = ConversationFilter.All ): Flow> suspend fun observeConversationListDetailsWithEvents( fromArchive: Boolean = false, - conversationFilter: ConversationFilter = ConversationFilter.ALL + conversationFilter: ConversationFilter = ConversationFilter.All ): Flow> suspend fun getConversationIds( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryExtensions.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryExtensions.kt index aec18932a97..c41464ea225 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryExtensions.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepositoryExtensions.kt @@ -73,5 +73,5 @@ data class ConversationQueryConfig( val fromArchive: Boolean = false, val onlyInteractionEnabled: Boolean = false, val newActivitiesOnTop: Boolean = false, - val conversationFilter: ConversationFilter = ConversationFilter.ALL, + val conversationFilter: ConversationFilter = ConversationFilter.All, ) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/folders/ConversationFolderRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/folders/ConversationFolderRepository.kt index 70ad373bdfa..5db72788cbd 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/folders/ConversationFolderRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/folders/ConversationFolderRepository.kt @@ -34,6 +34,7 @@ import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.flatMap import com.wire.kalium.logic.functional.flatMapLeft import com.wire.kalium.logic.functional.map +import com.wire.kalium.logic.functional.mapRight import com.wire.kalium.logic.functional.onFailure import com.wire.kalium.logic.functional.onSuccess import com.wire.kalium.logic.kaliumLogger @@ -57,6 +58,7 @@ internal interface ConversationFolderRepository { suspend fun addConversationToFolder(conversationId: QualifiedID, folderId: String): Either suspend fun removeConversationFromFolder(conversationId: QualifiedID, folderId: String): Either suspend fun syncConversationFoldersFromLocal(): Either + suspend fun observeUserFolders(): Flow>> } internal class ConversationFolderDataSource internal constructor( @@ -72,7 +74,7 @@ internal class ConversationFolderDataSource internal constructor( } override suspend fun getFavoriteConversationFolder(): Either = wrapStorageRequest { - conversationFolderDAO.getFavoriteConversationFolder().toModel() + conversationFolderDAO.getFavoriteConversationFolder()?.toModel() } override suspend fun observeConversationsFromFolder(folderId: String): Flow> = @@ -152,4 +154,10 @@ internal class ConversationFolderDataSource internal constructor( } } } + + override suspend fun observeUserFolders(): Flow>> { + return conversationFolderDAO.observeUserFolders() + .wrapStorageRequest() + .mapRight { folderEntities -> folderEntities.map { it.toModel() } } + } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt index 0f3be756ff3..bc65552ad2d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ConversationScope.kt @@ -55,6 +55,8 @@ import com.wire.kalium.logic.feature.conversation.folder.GetFavoriteFolderUseCas import com.wire.kalium.logic.feature.conversation.folder.GetFavoriteFolderUseCaseImpl import com.wire.kalium.logic.feature.conversation.folder.ObserveConversationsFromFolderUseCase import com.wire.kalium.logic.feature.conversation.folder.ObserveConversationsFromFolderUseCaseImpl +import com.wire.kalium.logic.feature.conversation.folder.ObserveUserFoldersUseCase +import com.wire.kalium.logic.feature.conversation.folder.ObserveUserFoldersUseCaseImpl import com.wire.kalium.logic.feature.conversation.folder.RemoveConversationFromFavoritesUseCase import com.wire.kalium.logic.feature.conversation.folder.RemoveConversationFromFavoritesUseCaseImpl import com.wire.kalium.logic.feature.conversation.guestroomlink.CanCreatePasswordProtectedLinksUseCase @@ -361,4 +363,6 @@ class ConversationScope internal constructor( get() = AddConversationToFavoritesUseCaseImpl(conversationFolderRepository) val removeConversationFromFavorites: RemoveConversationFromFavoritesUseCase get() = RemoveConversationFromFavoritesUseCaseImpl(conversationFolderRepository) + val observeUserFolders: ObserveUserFoldersUseCase + get() = ObserveUserFoldersUseCaseImpl(conversationFolderRepository) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationListDetailsWithEventsUseCaseImpl.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationListDetailsWithEventsUseCaseImpl.kt index 4f7f9529f61..5a3b59405e8 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationListDetailsWithEventsUseCaseImpl.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ObserveConversationListDetailsWithEventsUseCaseImpl.kt @@ -45,16 +45,26 @@ internal class ObserveConversationListDetailsWithEventsUseCaseImpl( fromArchive: Boolean, conversationFilter: ConversationFilter ): Flow> { - return if (conversationFilter == ConversationFilter.FAVORITES) { - when (val result = getFavoriteFolder()) { - GetFavoriteFolderUseCase.Result.Failure -> { - flowOf(emptyList()) + return when (conversationFilter) { + ConversationFilter.Favorites -> { + when (val result = getFavoriteFolder()) { + GetFavoriteFolderUseCase.Result.Failure -> { + flowOf(emptyList()) + } + + is GetFavoriteFolderUseCase.Result.Success -> + conversationFolderRepository.observeConversationsFromFolder(result.folder.id) } + } - is GetFavoriteFolderUseCase.Result.Success -> conversationFolderRepository.observeConversationsFromFolder(result.folder.id) + is ConversationFilter.Folder -> { + conversationFolderRepository.observeConversationsFromFolder(conversationFilter.folderId) } - } else { - conversationRepository.observeConversationListDetailsWithEvents(fromArchive, conversationFilter) + + ConversationFilter.All, + ConversationFilter.Groups, + ConversationFilter.OneOnOne -> + conversationRepository.observeConversationListDetailsWithEvents(fromArchive, conversationFilter) } } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveConversationsFromFolderUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveConversationsFromFolderUseCase.kt index b0ffa60fa93..549b951c5fc 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveConversationsFromFolderUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveConversationsFromFolderUseCase.kt @@ -19,7 +19,10 @@ package com.wire.kalium.logic.feature.conversation.folder import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents import com.wire.kalium.logic.data.conversation.folders.ConversationFolderRepository +import com.wire.kalium.util.KaliumDispatcher +import com.wire.kalium.util.KaliumDispatcherImpl import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn /** * This use case will observe and return the list of conversations from given folder. @@ -31,9 +34,10 @@ fun interface ObserveConversationsFromFolderUseCase { internal class ObserveConversationsFromFolderUseCaseImpl( private val conversationFolderRepository: ConversationFolderRepository, + private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl ) : ObserveConversationsFromFolderUseCase { - override suspend operator fun invoke(folderId: String): Flow> { - return conversationFolderRepository.observeConversationsFromFolder(folderId) - } + override suspend operator fun invoke(folderId: String): Flow> = + conversationFolderRepository.observeConversationsFromFolder(folderId) + .flowOn(dispatchers.io) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveUserFoldersUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveUserFoldersUseCase.kt new file mode 100644 index 00000000000..3935cd2af28 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/folder/ObserveUserFoldersUseCase.kt @@ -0,0 +1,46 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.feature.conversation.folder + +import com.wire.kalium.logic.data.conversation.ConversationFolder +import com.wire.kalium.logic.data.conversation.folders.ConversationFolderRepository +import com.wire.kalium.logic.functional.mapToRightOr +import com.wire.kalium.util.KaliumDispatcher +import com.wire.kalium.util.KaliumDispatcherImpl +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn + +/** + * This use case will observe and return the list of all user folders. + * @see ConversationFolder + */ +fun interface ObserveUserFoldersUseCase { + suspend operator fun invoke(): Flow> +} + +internal class ObserveUserFoldersUseCaseImpl( + private val conversationFolderRepository: ConversationFolderRepository, + private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl +) : ObserveUserFoldersUseCase { + + override suspend operator fun invoke(): Flow> { + return conversationFolderRepository.observeUserFolders() + .mapToRightOr(emptyList()) + .flowOn(dispatchers.io) + } +} diff --git a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq index db44a482e77..38d2c6db4bf 100644 --- a/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq +++ b/persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationFolders.sq @@ -17,6 +17,10 @@ CREATE TABLE LabeledConversation ( PRIMARY KEY (folder_id, conversation_id) ); +getUserFolders: +SELECT * FROM ConversationFolder +WHERE folder_type != 'FAVORITE'; + getAllFoldersWithConversations: SELECT conversationFolder.id AS label_id, @@ -60,8 +64,8 @@ VALUES(?, ?); deleteLabeledConversation: DELETE FROM LabeledConversation WHERE conversation_id = ? AND folder_id = ?; -clearFolders: -DELETE FROM ConversationFolder; - clearLabeledConversations: DELETE FROM LabeledConversation; + +clearFolders: +DELETE FROM ConversationFolder; diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAO.kt index ed1ae82a9f8..0c201de5b8d 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAO.kt @@ -24,8 +24,9 @@ import kotlinx.coroutines.flow.Flow interface ConversationFolderDAO { suspend fun getFoldersWithConversations(): List suspend fun observeConversationListFromFolder(folderId: String): Flow> - suspend fun getFavoriteConversationFolder(): ConversationFolderEntity + suspend fun getFavoriteConversationFolder(): ConversationFolderEntity? suspend fun updateConversationFolders(folderWithConversationsList: List) suspend fun addConversationToFolder(conversationId: QualifiedIDEntity, folderId: String) suspend fun removeConversationFromFolder(conversationId: QualifiedIDEntity, folderId: String) + suspend fun observeUserFolders(): Flow> } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt index 3316f030bd9..6e703b82419 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOImpl.kt @@ -18,6 +18,7 @@ package com.wire.kalium.persistence.dao.conversation.folder import app.cash.sqldelight.coroutines.asFlow +import com.wire.kalium.persistence.ConversationFolder import com.wire.kalium.persistence.ConversationFoldersQueries import com.wire.kalium.persistence.GetAllFoldersWithConversations import com.wire.kalium.persistence.dao.QualifiedIDEntity @@ -26,6 +27,7 @@ import com.wire.kalium.persistence.dao.conversation.ConversationDetailsWithEvent import com.wire.kalium.persistence.util.mapToList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext @@ -35,6 +37,14 @@ class ConversationFolderDAOImpl internal constructor( ) : ConversationFolderDAO { private val conversationDetailsWithEventsMapper = ConversationDetailsWithEventsMapper + override suspend fun observeUserFolders(): Flow> { + return conversationFoldersQueries.getUserFolders() + .asFlow() + .mapToList() + .map { it.map(::toEntity) } + .flowOn(coroutineContext) + } + override suspend fun getFoldersWithConversations(): List = withContext(coroutineContext) { val labeledConversationList = conversationFoldersQueries.getAllFoldersWithConversations().executeAsList().map(::toEntity) @@ -59,6 +69,12 @@ class ConversationFolderDAOImpl internal constructor( conversationId = row.conversation_id ) + private fun toEntity(row: ConversationFolder) = ConversationFolderEntity( + id = row.id, + name = row.name, + type = row.folder_type + ) + override suspend fun observeConversationListFromFolder(folderId: String): Flow> { return conversationFoldersQueries.getConversationsFromFolder( folderId, @@ -69,11 +85,11 @@ class ConversationFolderDAOImpl internal constructor( .flowOn(coroutineContext) } - override suspend fun getFavoriteConversationFolder(): ConversationFolderEntity { + override suspend fun getFavoriteConversationFolder(): ConversationFolderEntity? { return conversationFoldersQueries.getFavoriteFolder { id, name, folderType -> ConversationFolderEntity(id, name, folderType) } - .executeAsOne() + .executeAsOneOrNull() } override suspend fun updateConversationFolders(folderWithConversationsList: List) = diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOTest.kt index c55255dd733..d533ccc6dec 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/conversation/folder/ConversationFolderDAOTest.kt @@ -59,7 +59,8 @@ class ConversationFolderDAOTest : BaseDatabaseTest() { id = folderId, name = "folderName", type = ConversationFolderTypeEntity.USER, - conversationIdList = listOf(conversationEntity1.id)) + conversationIdList = listOf(conversationEntity1.id) + ) db.conversationFolderDAO.updateConversationFolders(listOf(conversationFolderEntity)) val result = db.conversationFolderDAO.observeConversationListFromFolder(folderId).first().first() @@ -79,12 +80,13 @@ class ConversationFolderDAOTest : BaseDatabaseTest() { id = folderId, name = "", type = ConversationFolderTypeEntity.FAVORITE, - conversationIdList = listOf(conversationEntity1.id)) + conversationIdList = listOf(conversationEntity1.id) + ) db.conversationFolderDAO.updateConversationFolders(listOf(conversationFolderEntity)) val result = db.conversationFolderDAO.getFavoriteConversationFolder() - assertEquals(folderId, result.id) + assertEquals(folderId, result?.id) } @Test From abcd037802987a6964464b7c0e96eac6e512bd4f Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Wed, 11 Dec 2024 12:04:16 +0100 Subject: [PATCH 09/16] feat: add usecase to get team url (WPB-14872) (#3157) * feat: add usecase to get team url * feat:detekt * feat: detekt * feat: detekt --- .../server/ServerConfigRepository.kt | 3 ++ .../kalium/logic/feature/UserSessionScope.kt | 8 +++++ .../logic/feature/server/GetTeamUrlUseCase.kt | 31 +++++++++++++++++++ .../kalium/persistence/ServerConfiguration.sq | 3 ++ .../daokaliumdb/ServerConfigurationDAO.kt | 7 +++++ 5 files changed, 52 insertions(+) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/GetTeamUrlUseCase.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt index 4fd77d402b4..a09c8241f17 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt @@ -66,6 +66,7 @@ interface ServerConfigRepository { */ suspend fun configForUser(userId: UserId): Either suspend fun commonApiVersion(domain: String): Either + suspend fun getTeamUrlForUser(userId: UserId): String? } @Suppress("LongParameterList", "TooManyFunctions") @@ -165,4 +166,6 @@ internal class ServerConfigDataSource( is ApiVersionDTO.Valid -> Either.Right(it) } }.map { serverConfigMapper.fromDTO(it) } + + override suspend fun getTeamUrlForUser(userId: UserId): String? = dao.teamUrlForUser(userId.toDao()) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index 543d5f9030c..3b40fb7cab4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -291,6 +291,7 @@ import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveSelfDeletionTim import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveTeamSettingsSelfDeletingStatusUseCase import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveTeamSettingsSelfDeletingStatusUseCaseImpl import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCaseImpl +import com.wire.kalium.logic.feature.server.GetTeamUrlUseCase import com.wire.kalium.logic.feature.service.ServiceScope import com.wire.kalium.logic.feature.session.GetProxyCredentialsUseCase import com.wire.kalium.logic.feature.session.GetProxyCredentialsUseCaseImpl @@ -2144,6 +2145,13 @@ class UserSessionScope internal constructor( kaliumLogger = userScopedLogger, ) + val getTeamUrlUseCase: GetTeamUrlUseCase by lazy { + GetTeamUrlUseCase( + userId, + authenticationScope.serverConfigRepository, + ) + } + /** * This will start subscribers of observable work per user session, as long as the user is logged in. * When the user logs out, this work will be canceled. diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/GetTeamUrlUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/GetTeamUrlUseCase.kt new file mode 100644 index 00000000000..fc11e34b573 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/GetTeamUrlUseCase.kt @@ -0,0 +1,31 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.feature.server + +import com.wire.kalium.logic.configuration.server.ServerConfigRepository +import com.wire.kalium.logic.data.user.UserId + +/** + * Use case to get the team url for the current user. + */ +class GetTeamUrlUseCase internal constructor( + private val selfUserId: UserId, + private val serverConfigRepository: ServerConfigRepository +) { + suspend operator fun invoke(): String = serverConfigRepository.getTeamUrlForUser(selfUserId) ?: "" +} diff --git a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq index 4fa81bcab28..dfcd7807ffd 100644 --- a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq +++ b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq @@ -56,6 +56,9 @@ SELECT * FROM ServerConfiguration WHERE title = ? AND apiBaseUrl = ? AND webSock getByUser: SELECT * FROM ServerConfiguration WHERE id = (SELECT server_config_id FROM Accounts WHERE id = :userId); +getTeamUrlByUser: +SELECT teamsUrl FROM ServerConfiguration WHERE id = (SELECT server_config_id FROM Accounts WHERE id = :userId); + getServerConfigsWithAccIdWithLastCheckBeforeDate: SELECT sc.*, acc.id FROM Accounts AS acc LEFT JOIN ServerConfiguration AS sc ON acc.server_config_id == sc.id diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt index 04c83e96f77..00b56040372 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt @@ -118,6 +118,7 @@ internal object ServerConfigMapper { ) } +@Suppress("TooManyFunctions") interface ServerConfigurationDAO { suspend fun deleteById(id: String) suspend fun insert(insertData: InsertData) @@ -129,6 +130,7 @@ interface ServerConfigurationDAO { suspend fun updateServerMetaData(id: String, federation: Boolean, commonApiVersion: Int) suspend fun updateApiVersionAndDomain(id: String, domain: String, commonApiVersion: Int) suspend fun configForUser(userId: UserIDEntity): ServerConfigEntity? + suspend fun teamUrlForUser(userId: UserIDEntity): String? suspend fun setFederationToTrue(id: String) suspend fun getServerConfigsWithAccIdWithLastCheckBeforeDate(date: String): Flow> suspend fun updateBlackListCheckDate(configIds: Set, date: String) @@ -152,6 +154,7 @@ interface ServerConfigurationDAO { ) } +@Suppress("TooManyFunctions") internal class ServerConfigurationDAOImpl internal constructor( private val queries: ServerConfigurationQueries, private val queriesContext: CoroutineContext, @@ -240,4 +243,8 @@ internal class ServerConfigurationDAOImpl internal constructor( override suspend fun updateBlackListCheckDate(configIds: Set, date: String) = withContext(queriesContext) { queries.updateLastBlackListCheckByIds(date, configIds) } + + override suspend fun teamUrlForUser(userId: UserIDEntity): String? = withContext(queriesContext) { + queries.getTeamUrlByUser(userId).executeAsOneOrNull() + } } From 0667f9b780a8262768b0c37af3d49d4f83c55701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= <30429749+saleniuk@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:59:42 +0100 Subject: [PATCH 10/16] feat: use case to change cipher_profile [WPB-14826] (#3163) * feat: use case to change cipher_profile [WPB-14826] * change cipher_profile value to be platform specific * fix detekt --- .../kalium/logic/feature/UserSessionScope.kt | 1 + .../feature/debug/ChangeProfilingUseCase.kt | 32 +++++++++++++++++++ .../kalium/logic/feature/debug/DebugScope.kt | 4 +++ .../kalium/persistence/db/UserDatabase.kt | 9 +++++- .../persistence/db/UserDatabaseBuilder.kt | 23 ++++++++++++- 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index 3b40fb7cab4..80a977d714e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -1831,6 +1831,7 @@ class UserSessionScope internal constructor( legalHoldHandler, notificationTokenRepository, this, + userStorage, userScopedLogger, ) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt new file mode 100644 index 00000000000..83588fea472 --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/ChangeProfilingUseCase.kt @@ -0,0 +1,32 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.feature.debug + +import com.wire.kalium.logic.di.UserStorage + +class ChangeProfilingUseCase( + private val userStorage: UserStorage, +) { + /** + * Changes the profiling of the database (cipher_profile) if the profile is specified and the database is encrypted + * @param enabled true to enable profiling, false to disable + */ + operator fun invoke(enabled: Boolean) { + userStorage.database.changeProfiling(enabled) + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt index 92d7176faaf..34b31cdf4b7 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/debug/DebugScope.kt @@ -40,6 +40,7 @@ import com.wire.kalium.logic.data.prekey.PreKeyRepository import com.wire.kalium.logic.data.sync.SlowSyncRepository import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.data.user.UserRepository +import com.wire.kalium.logic.di.UserStorage import com.wire.kalium.logic.feature.message.MLSMessageCreator import com.wire.kalium.logic.feature.message.MLSMessageCreatorImpl import com.wire.kalium.logic.feature.message.MessageEnvelopeCreator @@ -92,6 +93,7 @@ class DebugScope internal constructor( private val legalHoldHandler: LegalHoldHandler, private val notificationTokenRepository: NotificationTokenRepository, private val scope: CoroutineScope, + userStorage: UserStorage, logger: KaliumLogger, internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl, ) { @@ -224,4 +226,6 @@ class DebugScope internal constructor( clientRepository, notificationTokenRepository, ) + + val changeProfiling: ChangeProfilingUseCase = ChangeProfilingUseCase(userStorage) } diff --git a/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt index 39f73fdb694..4491d1bf47c 100644 --- a/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt +++ b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt @@ -51,7 +51,14 @@ actual fun userDatabaseBuilder( ) { isWALEnabled = enableWAL } - return UserDatabaseBuilder(userId, driver, dispatcher, platformDatabaseData, isEncryptionEnabled) + return UserDatabaseBuilder( + userId = userId, + sqlDriver = driver, + dispatcher = dispatcher, + platformDatabaseData = platformDatabaseData, + isEncrypted = isEncryptionEnabled, + cipherProfile = "logcat", + ) } actual fun userDatabaseDriverByPath( diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt index 7c75ee447b6..fdf7c7bac9e 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt @@ -112,13 +112,15 @@ internal expect fun userDatabaseDriverByPath( enableWAL: Boolean ): SqlDriver +@Suppress("LongParameterList") class UserDatabaseBuilder internal constructor( private val userId: UserIDEntity, internal val sqlDriver: SqlDriver, dispatcher: CoroutineDispatcher, private val platformDatabaseData: PlatformDatabaseData, private val isEncrypted: Boolean, - private val queriesContext: CoroutineContext = KaliumDispatcherImpl.io + private val queriesContext: CoroutineContext = KaliumDispatcherImpl.io, + private val cipherProfile: String? = null, ) { internal val database: UserDatabase = UserDatabase( @@ -316,6 +318,25 @@ class UserDatabaseBuilder internal constructor( */ fun dbFileLocation(): String? = getDatabaseAbsoluteFileLocation(platformDatabaseData, userId) + /** + * Changes the profiling of the database (cipher_profile) if the profile is specified and the database is encrypted + * @param enabled true to enable profiling, false to disable + */ + fun changeProfiling(enabled: Boolean) { + if (isEncrypted && cipherProfile != null) { + val cipherProfileValue = if (enabled) cipherProfile else "off" + sqlDriver.executeQuery( + identifier = null, + sql = "PRAGMA cipher_profile='$cipherProfileValue'", + mapper = { + it.next() + it.getLong(0).let { QueryResult.Value(it) } + }, + parameters = 0, + ) + } + } + /** * drops DB connection and delete the DB file */ From 5baff8ebe160a176b3871d2a059708a894a5f8c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 01:34:50 +0000 Subject: [PATCH 11/16] chore(deps): bump detekt from 1.23.6 to 1.23.7 (#3169) Bumps `detekt` from 1.23.6 to 1.23.7. Updates `io.gitlab.arturbosch.detekt:detekt-cli` from 1.23.6 to 1.23.7 - [Release notes](https://github.com/detekt/detekt/releases) - [Commits](https://github.com/detekt/detekt/compare/v1.23.6...v1.23.7) Updates `io.gitlab.arturbosch.detekt:detekt-api` from 1.23.6 to 1.23.7 - [Release notes](https://github.com/detekt/detekt/releases) - [Commits](https://github.com/detekt/detekt/compare/v1.23.6...v1.23.7) Updates `io.gitlab.arturbosch.detekt:detekt-test` from 1.23.6 to 1.23.7 - [Release notes](https://github.com/detekt/detekt/releases) - [Commits](https://github.com/detekt/detekt/compare/v1.23.6...v1.23.7) --- updated-dependencies: - dependency-name: io.gitlab.arturbosch.detekt:detekt-cli dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.gitlab.arturbosch.detekt:detekt-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.gitlab.arturbosch.detekt:detekt-test dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yamil Medina --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9060b93e117..b6746e68430 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,7 @@ core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1" completeKotlin = "1.1.0" desugar-jdk = "2.0.4" kermit = "2.0.3" -detekt = "1.23.6" +detekt = "1.23.7" agp = "8.5.2" dokka = "1.8.20" carthage = "0.0.1" From 203d128ae4770543062035e46881ae4e9ceb4c95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:41:28 +0100 Subject: [PATCH 12/16] chore(deps): bump srvaroa/labeler from 1.8.2 to 1.12.0 (#3155) Bumps [srvaroa/labeler](https://github.com/srvaroa/labeler) from 1.8.2 to 1.12.0. - [Release notes](https://github.com/srvaroa/labeler/releases) - [Commits](https://github.com/srvaroa/labeler/compare/v1.8.2...v1.12.0) --- updated-dependencies: - dependency-name: srvaroa/labeler dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yamil Medina Co-authored-by: Vitor Hugo Schwaab --- .github/workflows/label-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml index 39c404a16ed..73ad05c39ab 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -9,6 +9,6 @@ jobs: name: Label PR based on title runs-on: ubuntu-latest steps: - - uses: srvaroa/labeler@v1.8.2 + - uses: srvaroa/labeler@v1.12.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From bafd544122633b521fe4b15f40a0d19688cb1590 Mon Sep 17 00:00:00 2001 From: boris Date: Fri, 13 Dec 2024 14:07:34 +0200 Subject: [PATCH 13/16] fix: Login to second device does not have MLS capabilities (#3164) Co-authored-by: Yamil Medina --- .../api/authenticated/conversation/ConversationResponse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt b/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt index 71452d4f8b8..1347c7f1110 100644 --- a/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt +++ b/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/authenticated/conversation/ConversationResponse.kt @@ -114,7 +114,7 @@ data class ConversationResponse( @Serializable data class ConversationResponseV3( @SerialName("creator") - val creator: String, + val creator: String?, @SerialName("members") val members: ConversationMembersResponse, From 051e3eaf191bb5b9c4c1683750984191b7af8b2e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:49:30 +0000 Subject: [PATCH 14/16] chore: upgrade to cc3, fixes new_transaction in paralell (#3170) (#3171) Co-authored-by: Yamil Medina --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6746e68430..d99f1c21b15 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ pbandk = "0.14.2" turbine = "1.1.0" avs = "9.10.16" jna = "5.14.0" -core-crypto = "2.0.0" +core-crypto = "3.0.0" core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1" completeKotlin = "1.1.0" desugar-jdk = "2.0.4" From 5396afa48e4e3f639cbef60a73329a1294fcb665 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:49:38 +0000 Subject: [PATCH 15/16] test: add tests for old format parser in capabilities (#3173) (#3174) Co-authored-by: Yamil Medina --- .../api/v0/user/client/ClientApiV0Test.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/network/src/commonTest/kotlin/com/wire/kalium/api/v0/user/client/ClientApiV0Test.kt b/network/src/commonTest/kotlin/com/wire/kalium/api/v0/user/client/ClientApiV0Test.kt index dd3280dde52..574ef891390 100644 --- a/network/src/commonTest/kotlin/com/wire/kalium/api/v0/user/client/ClientApiV0Test.kt +++ b/network/src/commonTest/kotlin/com/wire/kalium/api/v0/user/client/ClientApiV0Test.kt @@ -24,9 +24,9 @@ import com.wire.kalium.mocks.responses.ClientResponseJson import com.wire.kalium.mocks.responses.RegisterClientRequestJson import com.wire.kalium.mocks.responses.RegisterTokenJson import com.wire.kalium.mocks.responses.UpdateClientRequestJson -import com.wire.kalium.network.api.base.authenticated.client.ClientApi import com.wire.kalium.network.api.authenticated.client.ClientCapabilityDTO import com.wire.kalium.network.api.authenticated.client.UpdateClientCapabilitiesRequest +import com.wire.kalium.network.api.base.authenticated.client.ClientApi import com.wire.kalium.network.api.v0.authenticated.ClientApiV0 import com.wire.kalium.network.exceptions.KaliumException import com.wire.kalium.network.utils.NetworkResponse @@ -58,7 +58,26 @@ internal class ClientApiV0Test : ApiTest() { val clientApi: ClientApi = ClientApiV0(networkClient) val response = clientApi.registerClient(REGISTER_CLIENT_REQUEST.serializableData) assertTrue(response.isSuccessful()) - assertEquals(response.value, VALID_REGISTER_CLIENT_RESPONSE.serializableData) + assertEquals(VALID_REGISTER_CLIENT_RESPONSE.serializableData, response.value) + } + + @Test + fun givenAValidRegisterClientRequest_whenCallingTheRegisterClientEndpointWithOldFormat_theRequestShouldBeConfiguredCorrectly() = + runTest { + val networkClient = mockAuthenticatedNetworkClient( + VALID_REGISTER_CLIENT_OLD_RESPONSE.rawJson, + statusCode = HttpStatusCode.Created, + assertion = { + assertPost() + assertJson() + assertNoQueryParams() + assertPathEqual(PATH_CLIENTS) + } + ) + val clientApi: ClientApi = ClientApiV0(networkClient) + val response = clientApi.registerClient(REGISTER_CLIENT_REQUEST.serializableData) + assertTrue(response.isSuccessful()) + assertEquals(VALID_REGISTER_CLIENT_OLD_RESPONSE.serializableData, response.value) } @Test @@ -93,6 +112,7 @@ internal class ClientApiV0Test : ApiTest() { assertTrue(response.isSuccessful()) } + @Test fun givenAValidUpdateClientCapabilitiesRequest_whenCallingTheUpdateClientEndpoint_theRequestShouldBeConfiguredCorrectly() = runTest { @@ -172,6 +192,7 @@ internal class ClientApiV0Test : ApiTest() { const val PATH_CLIENTS = "/clients" val REGISTER_CLIENT_REQUEST = RegisterClientRequestJson.valid val VALID_REGISTER_CLIENT_RESPONSE = ClientResponseJson.valid + val VALID_REGISTER_CLIENT_OLD_RESPONSE = ClientResponseJson.validCapabilitiesObject val UPDATE_CLIENT_REQUEST = UpdateClientRequestJson.valid val ERROR_RESPONSE = ErrorResponseJson.valid.serializableData val VALID_PUSH_TOKEN_REQUEST = RegisterTokenJson.validPushTokenRequest From 6fb2177d32698d263d1481d5cec3cd7def0b2ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Zag=C3=B3rski?= Date: Mon, 16 Dec 2024 14:15:53 +0100 Subject: [PATCH 16/16] feat: Handle needToRemoveLocally flag [WPB-14603] (#3158) * feat: Handle needToRemoveLocally flag [WPB-14603] * Code review --------- Co-authored-by: Yamil Medina --- .../logic/data/message/MessageContent.kt | 3 +- .../logic/data/message/ProtoContentMapper.kt | 6 +- .../ClearConversationContentUseCase.kt | 3 +- .../ClearConversationContentHandler.kt | 13 +- .../ClearConversationContentHandlerTest.kt | 212 ++++++++++++++++++ .../src/main/proto/messages.proto | 1 + 6 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/ClearConversationContentHandlerTest.kt diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt index 56103c2371f..b386f096073 100644 --- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt +++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt @@ -259,7 +259,8 @@ sealed interface MessageContent { data class Cleared( val conversationId: ConversationId, - val time: Instant + val time: Instant, + val needToRemoveLocally: Boolean ) : Signaling // server message content types diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt index 036fa81bf0f..31013c6e633 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt @@ -574,13 +574,15 @@ class ProtoContentMapperImpl( Cleared( conversationId = readableContent.conversationId.value, qualifiedConversationId = idMapper.toProtoModel(readableContent.conversationId), - clearedTimestamp = readableContent.time.toEpochMilliseconds() + clearedTimestamp = readableContent.time.toEpochMilliseconds(), + needToRemoveLocally = readableContent.needToRemoveLocally ) ) private fun unpackCleared(protoContent: GenericMessage.Content.Cleared) = MessageContent.Cleared( conversationId = extractConversationId(protoContent.value.qualifiedConversationId, protoContent.value.conversationId), - time = Instant.fromEpochMilliseconds(protoContent.value.clearedTimestamp) + time = Instant.fromEpochMilliseconds(protoContent.value.clearedTimestamp), + needToRemoveLocally = protoContent.value.needToRemoveLocally ?: false ) private fun toProtoLegalHoldStatus(legalHoldStatus: Conversation.LegalHoldStatus): LegalHoldStatus = diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ClearConversationContentUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ClearConversationContentUseCase.kt index 27c20d7eeff..d884b86693f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ClearConversationContentUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/ClearConversationContentUseCase.kt @@ -66,7 +66,8 @@ internal class ClearConversationContentUseCaseImpl( id = uuid4().toString(), content = MessageContent.Cleared( conversationId = conversationId, - time = DateTimeUtil.currentInstant() + time = DateTimeUtil.currentInstant(), + needToRemoveLocally = false // TODO Handle in upcoming tasks ), // sending the message to clear this conversation conversationId = selfConversationId, diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/ClearConversationContentHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/ClearConversationContentHandler.kt index 83402b724f5..044e6efe9df 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/ClearConversationContentHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/ClearConversationContentHandler.kt @@ -41,11 +41,18 @@ internal class ClearConversationContentHandlerImpl( message: Message.Signaling, messageContent: MessageContent.Cleared ) { - val isMessageComingFromOtherClient = message.senderUserId == selfUserId + val isMessageComingFromAnotherUser = message.senderUserId != selfUserId val isMessageDestinedForSelfConversation: Boolean = isMessageSentInSelfConversation(message) - if (isMessageComingFromOtherClient && isMessageDestinedForSelfConversation) { - conversationRepository.clearContent(messageContent.conversationId) + if (isMessageComingFromAnotherUser) { + when { + !messageContent.needToRemoveLocally && !isMessageDestinedForSelfConversation -> return + messageContent.needToRemoveLocally && !isMessageDestinedForSelfConversation -> conversationRepository.deleteConversation( + messageContent.conversationId + ) + + else -> conversationRepository.clearContent(messageContent.conversationId) + } } } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/ClearConversationContentHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/ClearConversationContentHandlerTest.kt new file mode 100644 index 00000000000..39ce8e1d7ad --- /dev/null +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/ClearConversationContentHandlerTest.kt @@ -0,0 +1,212 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.sync.receiver.conversation + +import com.wire.kalium.logic.data.conversation.ClientId +import com.wire.kalium.logic.data.conversation.ConversationRepository +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.message.IsMessageSentInSelfConversationUseCase +import com.wire.kalium.logic.data.message.Message +import com.wire.kalium.logic.data.message.MessageContent +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.framework.TestUser +import com.wire.kalium.logic.functional.Either +import com.wire.kalium.logic.sync.receiver.handler.ClearConversationContentHandler +import com.wire.kalium.logic.sync.receiver.handler.ClearConversationContentHandlerImpl +import io.mockative.Mock +import io.mockative.any +import io.mockative.coEvery +import io.mockative.coVerify +import io.mockative.mock +import io.mockative.once +import kotlinx.coroutines.test.runTest +import kotlinx.datetime.Instant +import kotlin.test.Test + +class ClearConversationContentHandlerTest { + + @Test + fun givenMessageFromOtherClient_whenMessageNeedsToBeRemovedLocallyAndUserIsNotPartOfConversation_thenWholeConversationShouldBeDeleted() = + runTest { + // given + val (arrangement, handler) = Arrangement() + .withMessageSentInSelfConversation(false) + .arrange() + + // when + handler.handle( + message = MESSAGE, + messageContent = MessageContent.Cleared( + conversationId = CONVERSATION_ID, + time = Instant.DISTANT_PAST, + needToRemoveLocally = true + ) + ) + + // then + coVerify { arrangement.conversationRepository.deleteConversation(any()) } + .wasInvoked(exactly = once) + coVerify { arrangement.conversationRepository.clearContent(any()) } + .wasNotInvoked() + } + + @Test + fun givenMessageFromOtherClient_whenMessageNeedsToBeRemovedLocallyAndUserIsPartOfConversation_thenOnlyContentShouldBeCleared() = + runTest { + // given + val (arrangement, handler) = Arrangement() + .withMessageSentInSelfConversation(true) + .arrange() + + // when + handler.handle( + message = MESSAGE, + messageContent = MessageContent.Cleared( + conversationId = CONVERSATION_ID, + time = Instant.DISTANT_PAST, + needToRemoveLocally = true + ) + ) + + // then + coVerify { arrangement.conversationRepository.deleteConversation(any()) } + .wasNotInvoked() + coVerify { arrangement.conversationRepository.clearContent(any()) } + .wasInvoked(exactly = once) + } + + @Test + fun givenMessageFromOtherClient_whenMessageDoesNotNeedToBeRemovedAndUserIsNotPartOfConversation_thenContentNorConversationShouldBeRemoved() = + runTest { + // given + val (arrangement, handler) = Arrangement() + .withMessageSentInSelfConversation(false) + .arrange() + + // when + handler.handle( + message = MESSAGE, + messageContent = MessageContent.Cleared( + conversationId = CONVERSATION_ID, + time = Instant.DISTANT_PAST, + needToRemoveLocally = false + ) + ) + + // then + coVerify { arrangement.conversationRepository.deleteConversation(any()) } + .wasNotInvoked() + coVerify { arrangement.conversationRepository.clearContent(any()) } + .wasNotInvoked() + } + + @Test + fun givenMessageFromOtherClient_whenMessageDoesNotNeedToBeRemovedAndUserIsPartOfConversation_thenContentShouldBeRemoved() = runTest { + // given + val (arrangement, handler) = Arrangement() + .withMessageSentInSelfConversation(true) + .arrange() + + // when + handler.handle( + message = MESSAGE, + messageContent = MessageContent.Cleared( + conversationId = CONVERSATION_ID, + time = Instant.DISTANT_PAST, + needToRemoveLocally = false + ) + ) + + // then + coVerify { arrangement.conversationRepository.deleteConversation(any()) } + .wasNotInvoked() + coVerify { arrangement.conversationRepository.clearContent(any()) } + .wasInvoked(exactly = once) + } + + @Test + fun givenMessageFromTheSameClient_whenHandleIsInvoked_thenContentNorConversationShouldBeRemoved() = runTest { + // given + val (arrangement, handler) = Arrangement() + .withMessageSentInSelfConversation(true) + .arrange() + + // when + handler.handle( + message = OWN_MESSAGE, + messageContent = MessageContent.Cleared( + conversationId = CONVERSATION_ID, + time = Instant.DISTANT_PAST, + needToRemoveLocally = false + ) + ) + + // then + coVerify { arrangement.conversationRepository.deleteConversation(any()) } + .wasNotInvoked() + coVerify { arrangement.conversationRepository.clearContent(any()) } + .wasNotInvoked() + } + + + private class Arrangement { + @Mock + val conversationRepository = mock(ConversationRepository::class) + + @Mock + val isMessageSentInSelfConversationUseCase = mock(IsMessageSentInSelfConversationUseCase::class) + + suspend fun withMessageSentInSelfConversation(isSentInSelfConv: Boolean) = apply { + coEvery { isMessageSentInSelfConversationUseCase(any()) }.returns(isSentInSelfConv) + } + + suspend fun arrange(): Pair = + this to ClearConversationContentHandlerImpl( + conversationRepository = conversationRepository, + selfUserId = TestUser.USER_ID, + isMessageSentInSelfConversation = isMessageSentInSelfConversationUseCase, + ).apply { + coEvery { conversationRepository.deleteConversation(any()) }.returns(Either.Right(Unit)) + coEvery { conversationRepository.clearContent(any()) }.returns(Either.Right(Unit)) + } + } + + companion object { + private val CONVERSATION_ID = ConversationId("conversationId", "domain") + private val OTHER_USER_ID = UserId("otherUserId", "domain") + + private val MESSAGE_CONTENT = MessageContent.DataTransfer( + trackingIdentifier = MessageContent.DataTransfer.TrackingIdentifier( + identifier = "abcd-1234-efgh-5678" + ) + ) + val MESSAGE = Message.Signaling( + id = "messageId", + content = MESSAGE_CONTENT, + conversationId = CONVERSATION_ID, + date = Instant.DISTANT_PAST, + senderUserId = OTHER_USER_ID, + senderClientId = ClientId("deviceId"), + status = Message.Status.Sent, + isSelfMessage = false, + expirationData = null, + ) + + val OWN_MESSAGE = MESSAGE.copy(senderUserId = TestUser.USER_ID) + } +} diff --git a/protobuf-codegen/src/main/proto/messages.proto b/protobuf-codegen/src/main/proto/messages.proto index efd576ac5b9..9a08423e27f 100644 --- a/protobuf-codegen/src/main/proto/messages.proto +++ b/protobuf-codegen/src/main/proto/messages.proto @@ -185,6 +185,7 @@ message Cleared { required int64 cleared_timestamp = 2; // only optional to maintain backwards compatibility optional QualifiedConversationId qualified_conversation_id = 3; + optional bool needToRemoveLocally = 4 [default = false]; } message MessageHide {