mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-25 06:25:40 +03:00
remove SyncableModel and SyncUtils
not needed for our requirements + it simplifies code Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
a84e69b6bb
commit
fe4bf942ab
9 changed files with 46 additions and 175 deletions
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
@ -161,7 +162,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
|
||||
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
|
||||
|
||||
if (loadFromServer && monitor.isOnline.first()) {
|
||||
if (loadFromServer) {
|
||||
sync(withNetworkParams)
|
||||
}
|
||||
|
||||
|
@ -292,7 +293,6 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
val loadFromServer = hasToLoadPreviousMessagesFromServer(messageId)
|
||||
|
||||
if (loadFromServer) {
|
||||
|
||||
val fieldMap = getFieldMap(
|
||||
lookIntoFuture = false,
|
||||
includeLastKnown = true,
|
||||
|
@ -368,6 +368,11 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun sync(bundle: Bundle): List<ChatMessageEntity>? {
|
||||
if (!monitor.isOnline.first()) {
|
||||
Log.d(TAG, "Device is offline, can't load chat messages from server")
|
||||
return null
|
||||
}
|
||||
|
||||
val result = getMessagesFromServer(bundle) ?: return listOf()
|
||||
var chatMessagesFromSync: List<ChatMessageEntity>? = null
|
||||
|
||||
|
|
|
@ -7,12 +7,11 @@
|
|||
|
||||
package com.nextcloud.talk.conversationlist.data
|
||||
|
||||
import com.nextcloud.talk.data.sync.Syncable
|
||||
import com.nextcloud.talk.models.domain.ConversationModel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OfflineConversationsRepository : Syncable {
|
||||
interface OfflineConversationsRepository {
|
||||
|
||||
/**
|
||||
* Stream of a list of rooms, for use in the conversation list.
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.conversationlist.data.network
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import android.util.Log
|
||||
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
|
||||
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
|
||||
import com.nextcloud.talk.data.database.dao.ConversationsDao
|
||||
import com.nextcloud.talk.data.database.mappers.asEntity
|
||||
import com.nextcloud.talk.data.database.mappers.asModel
|
||||
import com.nextcloud.talk.data.database.model.ConversationEntity
|
||||
import com.nextcloud.talk.data.sync.Synchronizer
|
||||
import com.nextcloud.talk.data.sync.changeListSync
|
||||
import com.nextcloud.talk.data.network.NetworkMonitor
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.models.domain.ConversationModel
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
@ -35,9 +34,9 @@ import javax.inject.Inject
|
|||
class OfflineFirstConversationsRepository @Inject constructor(
|
||||
private val dao: ConversationsDao,
|
||||
private val network: ConversationsNetworkDataSource,
|
||||
private val monitor: NetworkMonitor,
|
||||
private val currentUserProviderNew: CurrentUserProviderNew
|
||||
) : OfflineConversationsRepository, Synchronizer {
|
||||
|
||||
) : OfflineConversationsRepository {
|
||||
override val roomListFlow: Flow<List<ConversationModel>>
|
||||
get() = _roomListFlow
|
||||
private val _roomListFlow: MutableSharedFlow<List<ConversationModel>> = MutableSharedFlow()
|
||||
|
@ -56,7 +55,7 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
|||
if (list.isNotEmpty()) {
|
||||
_roomListFlow.emit(list)
|
||||
}
|
||||
this@OfflineFirstConversationsRepository.sync(bundleOf())
|
||||
sync()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,39 +63,28 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
|||
scope.launch {
|
||||
val id = user.id!!
|
||||
val model = getConversation(id, roomToken)
|
||||
model?.let { _conversationFlow.emit(model) }
|
||||
model.let { _conversationFlow.emit(model) }
|
||||
}
|
||||
|
||||
override suspend fun syncWith(bundle: Bundle, synchronizer: Synchronizer): Boolean =
|
||||
synchronizer.changeListSync(
|
||||
modelFetcher = {
|
||||
return@changeListSync getConversationsFromServer()
|
||||
},
|
||||
// not needed
|
||||
versionUpdater = {},
|
||||
modelDeleter = {},
|
||||
modelUpdater = { models ->
|
||||
val list = models.filterIsInstance<Conversation>().map {
|
||||
it.asEntity(user.id!!)
|
||||
}
|
||||
dao.upsertConversations(list)
|
||||
}
|
||||
)
|
||||
private suspend fun sync() {
|
||||
if (!monitor.isOnline.first()) {
|
||||
Log.d(OfflineFirstChatRepository.TAG, "Device is offline, can't load conversations from server")
|
||||
return
|
||||
}
|
||||
|
||||
private fun getConversationsFromServer(): List<Conversation> {
|
||||
val list = network.getRooms(user, user.baseUrl!!, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map { list ->
|
||||
return@map list.map {
|
||||
it.apply {
|
||||
id = roomId!!.toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
.blockingSingle()
|
||||
try {
|
||||
val conversationsList = network.getRooms(user, user.baseUrl!!, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.blockingSingle()
|
||||
|
||||
return list ?: listOf()
|
||||
val list = conversationsList.map {
|
||||
it.asEntity(user.id!!)
|
||||
}
|
||||
dao.upsertConversations(list)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Something went wrong when fetching conversations", e)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getListOfConversations(accountId: Long): List<ConversationModel> =
|
||||
|
@ -104,8 +92,12 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
|||
it.map(ConversationEntity::asModel)
|
||||
}.first()
|
||||
|
||||
private suspend fun getConversation(accountId: Long, token: String): ConversationModel? {
|
||||
private suspend fun getConversation(accountId: Long, token: String): ConversationModel {
|
||||
val entity = dao.getConversationForUser(accountId, token).first()
|
||||
return entity?.asModel()
|
||||
return entity.asModel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = OfflineFirstConversationsRepository::class.simpleName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,9 +191,10 @@ class RepositoryModule {
|
|||
fun provideOfflineFirstConversationsRepository(
|
||||
dao: ConversationsDao,
|
||||
dataSource: ConversationsNetworkDataSource,
|
||||
networkMonitor: NetworkMonitor,
|
||||
currentUserProviderNew: CurrentUserProviderNew
|
||||
): OfflineConversationsRepository {
|
||||
return OfflineFirstConversationsRepository(dao, dataSource, currentUserProviderNew)
|
||||
return OfflineFirstConversationsRepository(dao, dataSource, networkMonitor, currentUserProviderNew)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.data.changeListVersion
|
||||
|
||||
/**
|
||||
* Models any changes from the network, agnostic to what data is being modeled.
|
||||
* Implemented by Models that support offline synchronization.
|
||||
*/
|
||||
interface SyncableModel {
|
||||
|
||||
/**
|
||||
* Model identifier.
|
||||
*/
|
||||
var id: Long
|
||||
|
||||
/**
|
||||
* Model deletion checker.
|
||||
*/
|
||||
var markedForDeletion: Boolean
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.data.sync
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import com.nextcloud.talk.data.changeListVersion.SyncableModel
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
/**
|
||||
* Interface marker for a class that manages synchronization between local data and a remote
|
||||
* source for a [Syncable].
|
||||
*/
|
||||
interface Synchronizer {
|
||||
|
||||
// TODO include any other helper functions here that the Synchronizer needs
|
||||
|
||||
/**
|
||||
* Syntactic sugar to call [Syncable.syncWith] while omitting the synchronizer argument
|
||||
*/
|
||||
suspend fun Syncable.sync(bundle: Bundle) = this@sync.syncWith(bundle, this@Synchronizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface marker for a class that is synchronized with a remote source. Syncing must not be
|
||||
* performed concurrently and it is the [Synchronizer]'s responsibility to ensure this.
|
||||
*/
|
||||
interface Syncable {
|
||||
/**
|
||||
* Synchronizes the local database backing the repository with the network.
|
||||
* Takes in a [bundle] to retrieve other metadata needed
|
||||
*
|
||||
* Returns if the sync was successful or not.
|
||||
*/
|
||||
suspend fun syncWith(bundle: Bundle, synchronizer: Synchronizer): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts [block], returning a successful [Result] if it succeeds, otherwise a [Result.Failure]
|
||||
* taking care not to break structured concurrency
|
||||
*/
|
||||
private suspend fun <T> suspendRunCatching(block: suspend () -> T): Result<T> =
|
||||
try {
|
||||
Result.success(block())
|
||||
} catch (cancellationException: CancellationException) {
|
||||
throw cancellationException
|
||||
} catch (exception: Exception) {
|
||||
Log.e(
|
||||
"suspendRunCatching",
|
||||
"Failed to evaluate a suspendRunCatchingBlock. Returning failure Result",
|
||||
exception
|
||||
)
|
||||
Result.failure(exception)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for syncing a repository with the network.
|
||||
* [modelFetcher] Fetches the change list for the model
|
||||
* [versionUpdater] Updates the version after a successful sync
|
||||
* [modelDeleter] Deletes models by consuming the ids of the models that have been deleted.
|
||||
* [modelUpdater] Updates models by consuming the ids of the models that have changed.
|
||||
*
|
||||
* Note that the blocks defined above are never run concurrently, and the [Synchronizer]
|
||||
* implementation must guarantee this.
|
||||
*/
|
||||
suspend fun Synchronizer.changeListSync(
|
||||
modelFetcher: suspend () -> List<SyncableModel>,
|
||||
versionUpdater: (Long) -> Unit,
|
||||
modelDeleter: suspend (List<Long>) -> Unit,
|
||||
modelUpdater: suspend (List<SyncableModel>) -> Unit
|
||||
) = suspendRunCatching {
|
||||
// Fetch the change list since last sync (akin to a git fetch)
|
||||
val changeList = modelFetcher()
|
||||
if (changeList.isEmpty()) return@suspendRunCatching true
|
||||
|
||||
// Splits the models marked for deletion from the ones that are updated or new
|
||||
val (deleted, updated) = changeList.partition(SyncableModel::markedForDeletion)
|
||||
|
||||
// Delete models that have been deleted server-side
|
||||
modelDeleter(deleted.map(SyncableModel::id))
|
||||
|
||||
// Using the fetch list, pull down and upsert the changes (akin to a git pull)
|
||||
modelUpdater(updated)
|
||||
|
||||
// Update the last synced version (akin to updating local git HEAD)
|
||||
val latestVersion = changeList.last().id
|
||||
versionUpdater(latestVersion)
|
||||
}.isSuccess
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
package com.nextcloud.talk.models.domain
|
||||
|
||||
import com.nextcloud.talk.data.changeListVersion.SyncableModel
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessageJson
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
|
@ -62,9 +61,7 @@ class ConversationModel(
|
|||
var recordingConsentRequired: Int = 0,
|
||||
var remoteServer: String? = null,
|
||||
var remoteToken: String? = null,
|
||||
override var id: Long = roomId?.toLong() ?: 0,
|
||||
override var markedForDeletion: Boolean = false
|
||||
) : SyncableModel {
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun mapToConversationModel(conversation: Conversation, user: User): ConversationModel {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
|
@ -10,7 +10,6 @@ package com.nextcloud.talk.models.json.chat
|
|||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import com.nextcloud.talk.data.changeListVersion.SyncableModel
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType
|
||||
import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
|
|
@ -12,10 +12,9 @@ package com.nextcloud.talk.models.json.conversations
|
|||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessageJson
|
||||
import com.nextcloud.talk.data.changeListVersion.SyncableModel
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.models.domain.ConversationModel
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessageJson
|
||||
import com.nextcloud.talk.models.json.converters.ConversationObjectTypeConverter
|
||||
import com.nextcloud.talk.models.json.converters.EnumLobbyStateConverter
|
||||
import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
|
||||
|
@ -156,10 +155,7 @@ data class Conversation(
|
|||
@JsonField(name = ["remoteToken"])
|
||||
var remoteToken: String? = null,
|
||||
|
||||
override var id: Long = 0,
|
||||
override var markedForDeletion: Boolean = false
|
||||
|
||||
) : Parcelable, SyncableModel {
|
||||
) : Parcelable {
|
||||
@Deprecated("Use ConversationUtil")
|
||||
val isPublic: Boolean
|
||||
get() = ConversationEnums.ConversationType.ROOM_PUBLIC_CALL == type
|
||||
|
|
Loading…
Reference in a new issue