Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/develop' into fix/s…
Browse files Browse the repository at this point in the history
…low-conversation-list-queries-cherry-pick

# Conflicts:
#	persistence/src/commonMain/db_user/com/wire/kalium/persistence/ConversationDetailsWithEvents.sq
  • Loading branch information
saleniuk committed Nov 7, 2024
2 parents f578921 + f417fd4 commit 90f486b
Show file tree
Hide file tree
Showing 31 changed files with 903 additions and 502 deletions.
3 changes: 3 additions & 0 deletions calling/consumer-proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
-keep class com.waz.call.CaptureDevice { *; }
-keep class com.waz.media.manager.** { *; }
-keep class com.waz.service.call.** { *; }
-dontwarn org.webrtc.CalledByNative
-dontwarn org.webrtc.JniCommon
-dontwarn org.webrtc.audio.AudioDeviceModule

# Avs SoundLink
-keep class com.waz.soundlink.SoundLinkAPI { *; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ data class QualifiedID(
@SerialName("domain")
val domain: String
) {
override fun toString(): String = if (domain.isEmpty()) value else "$value$VALUE_DOMAIN_SEPARATOR$domain"
override fun toString(): String =
if (domain.isEmpty()) value else "$value$VALUE_DOMAIN_SEPARATOR$domain"

fun toLogString(): String = if (domain.isEmpty()) {
value.obfuscateId()
Expand All @@ -43,6 +44,16 @@ data class QualifiedID(

fun toPlainID(): PlainId = PlainId(value)

/**
* This checks if the domain in either instances is blank. If it is, it will compare only the value.
* To be used when when of the instance do not have domain due to the API limitations.
*/
fun equalsIgnoringBlankDomain(other: QualifiedID): Boolean {
if (domain.isBlank() || other.domain.isBlank()) {
return value == other.value
}
return this == other
}
}

const val VALUE_DOMAIN_SEPARATOR = '@'
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ sqldelight = "2.0.1"
sqlcipher-android = "4.6.1"
pbandk = "0.14.2"
turbine = "1.1.0"
avs = "9.10.14"
avs = "9.10.16"
jna = "5.14.0"
core-crypto = "1.0.2"
core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,20 @@ import com.wire.kalium.network.api.base.authenticated.conversation.ConversationA
import com.wire.kalium.persistence.dao.QualifiedIDEntity
import com.wire.kalium.persistence.dao.client.ClientDAO
import com.wire.kalium.persistence.dao.conversation.ConversationDAO
import com.wire.kalium.persistence.dao.conversation.ConversationDetailsWithEventsEntity
import com.wire.kalium.persistence.dao.conversation.ConversationEntity
import com.wire.kalium.persistence.dao.conversation.ConversationMetaDataDAO
import com.wire.kalium.persistence.dao.member.MemberDAO
import com.wire.kalium.persistence.dao.message.MessageDAO
import com.wire.kalium.persistence.dao.message.draft.MessageDraftDAO
import com.wire.kalium.persistence.dao.unread.ConversationUnreadEventEntity
import com.wire.kalium.util.DelicateKaliumApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.datetime.Instant

Expand Down Expand Up @@ -127,11 +132,7 @@ interface ConversationRepository {
suspend fun getConversationList(): Either<StorageFailure, Flow<List<Conversation>>>
suspend fun observeConversationList(): Flow<List<Conversation>>
suspend fun observeConversationListDetails(fromArchive: Boolean): Flow<List<ConversationDetails>>
suspend fun observeConversationListDetailsWithEvents(
fromArchive: Boolean = false,
onlyInteractionsEnabled: Boolean = false,
newActivitiesOnTop: Boolean = false,
): Flow<List<ConversationDetailsWithEvents>>
suspend fun observeConversationListDetailsWithEvents(fromArchive: Boolean = false): Flow<List<ConversationDetailsWithEvents>>
suspend fun getConversationIds(
type: Conversation.Type,
protocol: Conversation.Protocol,
Expand Down Expand Up @@ -317,6 +318,7 @@ internal class ConversationDataSource internal constructor(
private val memberDAO: MemberDAO,
private val conversationApi: ConversationApi,
private val messageDAO: MessageDAO,
private val messageDraftDAO: MessageDraftDAO,
private val clientDAO: ClientDAO,
private val clientApi: ClientApi,
private val conversationMetaDataDAO: ConversationMetaDataDAO,
Expand Down Expand Up @@ -520,17 +522,28 @@ internal class ConversationDataSource internal constructor(
conversationViewEntityList.map { conversationViewEntity -> conversationMapper.fromDaoModelToDetails(conversationViewEntity) }
}

override suspend fun observeConversationListDetailsWithEvents(
fromArchive: Boolean,
onlyInteractionsEnabled: Boolean,
newActivitiesOnTop: Boolean,
): Flow<List<ConversationDetailsWithEvents>> =
conversationDAO.getAllConversationDetailsWithEvents(fromArchive, onlyInteractionsEnabled, newActivitiesOnTop)
.map { conversationDetailsWithEventsViewEntityList ->
conversationDetailsWithEventsViewEntityList.map { conversationDetailsWithEventsViewEntity ->
conversationMapper.fromDaoModelToDetailsWithEvents(conversationDetailsWithEventsViewEntity)
}
override suspend fun observeConversationListDetailsWithEvents(fromArchive: Boolean): Flow<List<ConversationDetailsWithEvents>> =
combine(
conversationDAO.getAllConversationDetails(fromArchive),
if (fromArchive) flowOf(listOf()) else messageDAO.observeLastMessages(),
messageDAO.observeConversationsUnreadEvents(),
messageDraftDAO.observeMessageDrafts()
) { conversationList, lastMessageList, unreadEvents, drafts ->
val lastMessageMap = lastMessageList.associateBy { it.conversationId }
val messageDraftMap = drafts.filter { it.text.isNotBlank() }.associateBy { it.conversationId }
val unreadEventsMap = unreadEvents.associateBy { it.conversationId }

conversationList.map { conversation ->
conversationMapper.fromDaoModelToDetailsWithEvents(
ConversationDetailsWithEventsEntity(
conversationViewEntity = conversation,
lastMessage = lastMessageMap[conversation.id],
messageDraft = messageDraftMap[conversation.id],
unreadEvents = unreadEventsMap[conversation.id] ?: ConversationUnreadEventEntity(conversation.id, mapOf()),
)
)
}
}

override suspend fun fetchMlsOneToOneConversation(userId: UserId): Either<CoreFailure, Conversation> =
wrapApiRequest {
Expand Down Expand Up @@ -983,7 +996,7 @@ internal class ConversationDataSource internal constructor(
}

override suspend fun getConversationDetailsByMLSGroupId(mlsGroupId: GroupID): Either<CoreFailure, ConversationDetails> =
wrapStorageRequest { conversationDAO.getConversationByGroupID(mlsGroupId.value) }
wrapStorageRequest { conversationDAO.getConversationDetailsByGroupID(mlsGroupId.value) }
.map { conversationMapper.fromDaoModelToDetails(it) }

override suspend fun observeUnreadArchivedConversationsCount(): Flow<Long> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ class UserSessionScope internal constructor(
userStorage.database.memberDAO,
authenticatedNetworkContainer.conversationApi,
userStorage.database.messageDAO,
userStorage.database.messageDraftDAO,
userStorage.database.clientDAO,
authenticatedNetworkContainer.clientApi,
userStorage.database.conversationMetaDataDAO,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ internal class RestoreBackupUseCaseImpl(
importEncryptedBackup(extractedBackupRootPath, password)
}
}
.fold({ it }, { RestoreBackupResult.Success })
.fold({ error ->
kaliumLogger.e("$TAG Failed to restore the backup, reason: ${error.failure}")
error
}, {
kaliumLogger.i("$TAG Backup restored successfully")
RestoreBackupResult.Success
})
}

private suspend fun importUnencryptedBackup(
Expand Down Expand Up @@ -140,7 +146,7 @@ internal class RestoreBackupUseCaseImpl(
val extractedFilesRootPath = createExtractedFilesRootPath()
return extractFiles(tempCompressedFileSource, extractedFilesRootPath)
.fold({
kaliumLogger.e("Failed to extract backup files")
kaliumLogger.e("$TAG Failed to extract backup files")
Either.Left(Failure(BackupIOFailure("Failed to extract backup files")))
}, {
Either.Right(extractedFilesRootPath)
Expand Down Expand Up @@ -176,7 +182,7 @@ internal class RestoreBackupUseCaseImpl(
return if (backupSize > 0) {
// On successful decryption, we still need to extract the zip file to do sanity checks and get the database file
extractFiles(kaliumFileSystem.source(extractedBackupPath), extractedBackupRootPath).fold({
kaliumLogger.e("Failed to extract encrypted backup files")
kaliumLogger.e("$TAG Failed to extract encrypted backup files")
Either.Left(Failure(BackupIOFailure("Failed to extract encrypted backup files")))
}, {
kaliumFileSystem.delete(extractedBackupPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ internal class RestoreWebBackupUseCaseImpl(
importWebBackup(backupRootPath, this)
} else {
Either.Left(IncompatibleBackup("invoke: The provided backup format is not supported"))
}.fold({ RestoreBackupResult.Failure(it) }, { RestoreBackupResult.Success })
}.fold({ error ->
kaliumLogger.e("$TAG Failed to restore the backup, reason: $error")
RestoreBackupResult.Failure(error)
}, {
kaliumLogger.i("$TAG Successfully restored the backup")
RestoreBackupResult.Success
})
}

private suspend fun importWebBackup(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ class ConversationScope internal constructor(
val observeConversationListDetails: ObserveConversationListDetailsUseCase
get() = ObserveConversationListDetailsUseCaseImpl(conversationRepository)

val observeConversationListDetailsWithEvents: ObserveConversationListDetailsWithEventsUseCase
get() = ObserveConversationListDetailsWithEventsUseCaseImpl(conversationRepository)

val observeConversationMembers: ObserveConversationMembersUseCase
get() = ObserveConversationMembersUseCaseImpl(conversationRepository, userRepository)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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

import com.wire.kalium.logic.data.conversation.ConversationDetails
import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents
import com.wire.kalium.logic.data.conversation.ConversationRepository
import kotlinx.coroutines.flow.Flow

/**
* This use case will observe and return the list of conversation details for the current user.
* @see ConversationDetails
*/
fun interface ObserveConversationListDetailsWithEventsUseCase {
suspend operator fun invoke(fromArchive: Boolean): Flow<List<ConversationDetailsWithEvents>>
}

internal class ObserveConversationListDetailsWithEventsUseCaseImpl(
private val conversationRepository: ConversationRepository,
) : ObserveConversationListDetailsWithEventsUseCase {

override suspend operator fun invoke(fromArchive: Boolean): Flow<List<ConversationDetailsWithEvents>> {
return conversationRepository.observeConversationListDetailsWithEvents(fromArchive)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import com.wire.kalium.logic.data.user.UserRepository
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.map
import com.wire.kalium.util.KaliumDispatcherImpl
import kotlinx.coroutines.withContext
import okio.Path

interface UploadUserAvatarUseCase {
Expand All @@ -40,17 +42,20 @@ interface UploadUserAvatarUseCase {

internal class UploadUserAvatarUseCaseImpl(
private val userDataSource: UserRepository,
private val assetDataSource: AssetRepository
private val assetDataSource: AssetRepository,
private val dispatcher: KaliumDispatcherImpl = KaliumDispatcherImpl
) : UploadUserAvatarUseCase {

override suspend operator fun invoke(imageDataPath: Path, imageDataSize: Long): UploadAvatarResult {
return assetDataSource.uploadAndPersistPublicAsset("image/jpg", imageDataPath, imageDataSize).flatMap { asset ->
userDataSource.updateSelfUser(newAssetId = asset.key).map { asset }
}.fold({
UploadAvatarResult.Failure(it)
}) { updatedAsset ->
UploadAvatarResult.Success(UserAssetId(updatedAsset.key, updatedAsset.domain))
} // TODO(assets): remove old assets, non blocking this response, as will imply deleting locally and remotely
return withContext(dispatcher.io) {
assetDataSource.uploadAndPersistPublicAsset("image/jpg", imageDataPath, imageDataSize).flatMap { asset ->
userDataSource.updateSelfUser(newAssetId = asset.key).map { asset }
}.fold({
UploadAvatarResult.Failure(it)
}) { updatedAsset ->
UploadAvatarResult.Success(UserAssetId(updatedAsset.key, updatedAsset.domain))
} // TODO(assets): remove old assets, non blocking this response, as will imply deleting locally and remotely
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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

import com.benasher44.uuid.uuid4
import com.wire.kalium.logger.KaliumLogLevel
import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.logic.logStructuredJson
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlin.time.Duration

/**
* Logs the sync process by providing structured logs.
* It logs the sync process start and completion with the syncId as a unique identifier.
*/
internal class SyncManagerLogger(
private val logger: KaliumLogger,
private val syncId: String,
private val syncType: SyncType,
private val syncStartedMoment: Instant
) {

/**
* Logs the sync process start.
*/
fun logSyncStarted() {
logger.withFeatureId(KaliumLogger.Companion.ApplicationFlow.SYNC).logStructuredJson(
level = KaliumLogLevel.INFO,
leadingMessage = "Started sync process",
jsonStringKeyValues = mapOf(
"syncMetadata" to mapOf(
"id" to syncId,
"status" to SyncStatus.STARTED.name,
"type" to syncType.name
)
)
)
}

/**
* Logs the sync process completion.
* Optionally, it can pass the duration of the sync process,
* useful for incremental sync that can happen between collecting states.
*
* @param duration optional the duration of the sync process.
*/
fun logSyncCompleted(duration: Duration = Clock.System.now() - syncStartedMoment) {
val logMap = mapOf(
"id" to syncId,
"status" to SyncStatus.COMPLETED.name,
"type" to syncType.name,
"performanceData" to mapOf("timeTakenInMillis" to duration.inWholeMilliseconds)
)

logger.withFeatureId(KaliumLogger.Companion.ApplicationFlow.SYNC).logStructuredJson(
level = KaliumLogLevel.INFO,
leadingMessage = "Completed sync process",
jsonStringKeyValues = mapOf("syncMetadata" to logMap)
)
}
}

internal enum class SyncStatus {
STARTED,
COMPLETED
}

internal enum class SyncType {
SLOW,
INCREMENTAL
}

/**
* Provides a new [SyncManagerLogger] instance with the given parameters.
* @param syncType the [SyncType] that will log.
* @param syncId the unique identifier for the sync process.
* @param syncStartedMoment the moment when the sync process started.
*/
internal fun KaliumLogger.provideNewSyncManagerLogger(
syncType: SyncType,
syncId: String = uuid4().toString(),
syncStartedMoment: Instant = Clock.System.now()
) = SyncManagerLogger(this, syncId, syncType, syncStartedMoment)
Loading

0 comments on commit 90f486b

Please sign in to comment.