check if sync succeeded

Decrease message limit for retries of message loading

make it possible to add any amount (up to 100) of messages to UI for initial loading.

add logging

only make initial request for chat messages when newest message from DB is not equal the lastReadMessage that is offered by the conversation

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2024-10-01 14:49:13 +02:00
parent a331ba6372
commit 818a843143
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B

View File

@ -108,26 +108,63 @@ class OfflineFirstChatRepository @Inject constructor(
override fun loadInitialMessages(withNetworkParams: Bundle): Job =
scope.launch {
Log.d(TAG, "---- loadInitialMessages ------------")
Log.d(TAG, "conversationModel.internalId: " + conversationModel.internalId)
newXChatLastCommonRead = conversationModel.lastCommonReadMessage
val fieldMap = getFieldMap(
lookIntoFuture = false,
includeLastKnown = true,
setReadMarker = true,
lastKnown = null
)
withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
withNetworkParams.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token)
Log.d(TAG, "conversationModel.lastReadMessage:" + conversationModel.lastReadMessage)
sync(withNetworkParams)
var newestMessageId = chatDao.getNewestMessageId(internalConversationId)
Log.d(TAG, "newestMessageId: $newestMessageId")
if (newestMessageId.toInt() == 0) {
Log.d(TAG, "newestMessageId from db was 0. Must only happen when chat is loaded for the first time")
}
val newestMessageId = chatDao.getNewestMessageId(internalConversationId)
Log.d(TAG, "newestMessageId after sync: $newestMessageId")
if (conversationModel.lastReadMessage.toLong() != newestMessageId) {
Log.d(TAG, "An online request is made because chat is not up to date")
showLast100MessagesBeforeAndEqual(
// set up field map to load the newest messages
val fieldMap = getFieldMap(
lookIntoFuture = false,
includeLastKnown = true,
setReadMarker = true,
lastKnown = null
)
withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
withNetworkParams.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token)
Log.d(TAG, "Starting online request for initial loading")
val chatMessageEntities = sync(withNetworkParams)
if (chatMessageEntities == null) {
Log.e(TAG, "initial loading of messages failed")
}
newestMessageId = chatDao.getNewestMessageId(internalConversationId)
Log.d(TAG, "newestMessageId after sync: $newestMessageId")
} else {
Log.d(TAG, "Initial online request is skipped because offline messages are up to date")
}
val chatBlock = getBlockOfMessage(newestMessageId.toInt())
val amountBetween = chatDao.getCountBetweenMessageIds(
internalConversationId,
chatDao.getNewestMessageId(internalConversationId)
newestMessageId,
chatBlock!!.oldestMessageId
)
Log.d(TAG, "amount of messages between newestMessageId and oldest message of same ChatBlock:$amountBetween")
val limit = if (amountBetween > DEFAULT_MESSAGES_LIMIT) {
DEFAULT_MESSAGES_LIMIT
} else {
amountBetween
}
Log.d(TAG, "limit of messages to load for UI (max 100 to ensure performance is okay):$limit")
showMessagesBeforeAndEqual(
internalConversationId,
newestMessageId,
limit
)
// delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing
@ -175,10 +212,11 @@ class OfflineFirstChatRepository @Inject constructor(
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
if (loadFromServer) {
Log.d(TAG, "Starting online request for loadMoreMessages")
sync(withNetworkParams)
}
showLast100MessagesBefore(internalConversationId, beforeMessageId)
showMessagesBefore(internalConversationId, beforeMessageId, DEFAULT_MESSAGES_LIMIT)
updateUiForLastCommonRead()
}
@ -205,6 +243,7 @@ class OfflineFirstChatRepository @Inject constructor(
// sync database with server (This is a long blocking call because long polling (lookIntoFuture) is set)
networkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
Log.d(TAG, "Starting online request for long polling")
val resultsFromSync = sync(networkParams)
if (!resultsFromSync.isNullOrEmpty()) {
val chatMessages = resultsFromSync.map(ChatMessageEntity::asModel)
@ -240,15 +279,15 @@ class OfflineFirstChatRepository @Inject constructor(
loadFromServer = false
} else {
// we know that beforeMessageId and blockForMessage.oldestMessageId are in the same block.
// As we want the last 100 entries before beforeMessageId, we calculate if these messages are 100
// entries apart from each other
// As we want the last DEFAULT_MESSAGES_LIMIT entries before beforeMessageId, we calculate if these
// messages are DEFAULT_MESSAGES_LIMIT entries apart from each other
val amountBetween = chatDao.getCountBetweenMessageIds(
internalConversationId,
beforeMessageId,
blockForMessage.oldestMessageId
)
loadFromServer = amountBetween < 100
loadFromServer = amountBetween < DEFAULT_MESSAGES_LIMIT
Log.d(
TAG,
@ -263,7 +302,8 @@ class OfflineFirstChatRepository @Inject constructor(
lookIntoFuture: Boolean,
includeLastKnown: Boolean,
setReadMarker: Boolean,
lastKnown: Int?
lastKnown: Int?,
limit: Int = DEFAULT_MESSAGES_LIMIT
): HashMap<String, Int> {
val fieldMap = HashMap<String, Int>()
@ -278,7 +318,7 @@ class OfflineFirstChatRepository @Inject constructor(
}
fieldMap["timeout"] = if (lookIntoFuture) 30 else 0
fieldMap["limit"] = 100
fieldMap["limit"] = limit
fieldMap["lookIntoFuture"] = if (lookIntoFuture) 1 else 0
fieldMap["setReadMarker"] = if (setReadMarker) 1 else 0
@ -294,12 +334,12 @@ class OfflineFirstChatRepository @Inject constructor(
lookIntoFuture = false,
includeLastKnown = true,
setReadMarker = false,
lastKnown = messageId.toInt()
lastKnown = messageId.toInt(),
limit = 1
)
bundle.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
// Although only the single message will be returned, a server request will load 100 messages.
// If this turns out to be confusion for debugging we could load set the limit to 1 for this request.
Log.d(TAG, "Starting online request for single message (e.g. a reply)")
sync(bundle)
}
return chatDao.getChatMessageForConversation(internalConversationId, messageId)
@ -308,59 +348,70 @@ class OfflineFirstChatRepository @Inject constructor(
@Suppress("UNCHECKED_CAST")
private fun getMessagesFromServer(bundle: Bundle): Pair<Int, List<ChatMessageJson>>? {
Log.d(TAG, "An online request is made!!!!!!!!!!!!!!!!!!!!")
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
try {
val result = network.pullChatMessages(credentials, urlForChatting, fieldMap)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// .timeout(3, TimeUnit.SECONDS)
.map { it ->
when (it.code()) {
HTTP_CODE_OK -> {
Log.d(TAG, "getMessagesFromServer HTTP_CODE_OK")
newXChatLastCommonRead = it.headers()["X-Chat-Last-Common-Read"]?.let {
Integer.parseInt(it)
var attempts = 1
while (attempts < 5) {
Log.d(TAG, "message limit: " + fieldMap["limit"])
try {
val result = network.pullChatMessages(credentials, urlForChatting, fieldMap)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { it ->
when (it.code()) {
HTTP_CODE_OK -> {
Log.d(TAG, "getMessagesFromServer HTTP_CODE_OK")
newXChatLastCommonRead = it.headers()["X-Chat-Last-Common-Read"]?.let {
Integer.parseInt(it)
}
return@map Pair(
HTTP_CODE_OK,
(it.body() as ChatOverall).ocs!!.data!!
)
}
return@map Pair(
HTTP_CODE_OK,
(it.body() as ChatOverall).ocs!!.data!!
)
}
HTTP_CODE_NOT_MODIFIED -> {
Log.d(TAG, "getMessagesFromServer HTTP_CODE_NOT_MODIFIED")
HTTP_CODE_NOT_MODIFIED -> {
Log.d(TAG, "getMessagesFromServer HTTP_CODE_NOT_MODIFIED")
return@map Pair(
HTTP_CODE_NOT_MODIFIED,
listOf<ChatMessageJson>()
)
}
return@map Pair(
HTTP_CODE_NOT_MODIFIED,
listOf<ChatMessageJson>()
)
}
HTTP_CODE_PRECONDITION_FAILED -> {
Log.d(TAG, "getMessagesFromServer HTTP_CODE_PRECONDITION_FAILED")
HTTP_CODE_PRECONDITION_FAILED -> {
Log.d(TAG, "getMessagesFromServer HTTP_CODE_PRECONDITION_FAILED")
return@map Pair(
HTTP_CODE_PRECONDITION_FAILED,
listOf<ChatMessageJson>()
)
}
return@map Pair(
HTTP_CODE_PRECONDITION_FAILED,
listOf<ChatMessageJson>()
)
}
else -> {
return@map Pair(
HTTP_CODE_PRECONDITION_FAILED,
listOf<ChatMessageJson>()
)
else -> {
return@map Pair(
HTTP_CODE_PRECONDITION_FAILED,
listOf<ChatMessageJson>()
)
}
}
}
.blockingSingle()
return result
} catch (e: Exception) {
Log.e(TAG, "Something went wrong when pulling chat messages (attempt: $attempts)", e)
attempts++
val newMessageLimit = when (attempts) {
2 -> 50
3 -> 10
else -> 5
}
.blockingSingle()
return result
} catch (e: Exception) {
Log.e(TAG, "Something went wrong when pulling chat messages", e)
fieldMap["limit"] = newMessageLimit
}
}
Log.e(TAG, "All attempts to get messages from server failed")
return null
}
@ -370,7 +421,12 @@ class OfflineFirstChatRepository @Inject constructor(
return null
}
val result = getMessagesFromServer(bundle) ?: return listOf()
val result = getMessagesFromServer(bundle)
if (result == null) {
Log.d(TAG, "No result from server")
return null
}
var chatMessagesFromSync: List<ChatMessageEntity>? = null
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
@ -471,7 +527,7 @@ class OfflineFirstChatRepository @Inject constructor(
ChatMessage.SystemMessageType.CLEARED_CHAT -> {
// for lookIntoFuture just deleting everything would be fine.
// But lets say we did not open the chat for a while and in between it was cleared.
// We just load the last 100 messages but this don't contain the system message.
// We just load the last messages but this don't contain the system message.
// We scroll up and load the system message. Deleting everything is not an option as we
// would loose the messages that we want to keep. We only want to
// delete the messages and chatBlocks older than the system message.
@ -488,13 +544,12 @@ class OfflineFirstChatRepository @Inject constructor(
* 304 is returned when oldest message of chat was queried or when long polling request returned with no
* modification. hasHistory is only set to false, when 304 was returned for the the oldest message
*/
private fun getHasHistory(statusCode: Int, lookIntoFuture: Boolean): Boolean {
return if (statusCode == HTTP_CODE_NOT_MODIFIED) {
private fun getHasHistory(statusCode: Int, lookIntoFuture: Boolean): Boolean =
if (statusCode == HTTP_CODE_NOT_MODIFIED) {
lookIntoFuture
} else {
true
}
}
private suspend fun getBlockOfMessage(queriedMessageId: Int?): ChatBlockEntity? {
var blockContainingQueriedMessage: ChatBlockEntity? = null
@ -563,7 +618,7 @@ class OfflineFirstChatRepository @Inject constructor(
}
}
private suspend fun showLast100MessagesBeforeAndEqual(internalConversationId: String, messageId: Long) {
private suspend fun showMessagesBeforeAndEqual(internalConversationId: String, messageId: Long, limit: Int) {
suspend fun getMessagesBeforeAndEqual(
messageId: Long,
internalConversationId: String,
@ -580,7 +635,7 @@ class OfflineFirstChatRepository @Inject constructor(
val list = getMessagesBeforeAndEqual(
messageId,
internalConversationId,
100
limit
)
if (list.isNotEmpty()) {
@ -589,7 +644,7 @@ class OfflineFirstChatRepository @Inject constructor(
}
}
private suspend fun showLast100MessagesBefore(internalConversationId: String, messageId: Long) {
private suspend fun showMessagesBefore(internalConversationId: String, messageId: Long, limit: Int) {
suspend fun getMessagesBefore(
messageId: Long,
internalConversationId: String,
@ -606,7 +661,7 @@ class OfflineFirstChatRepository @Inject constructor(
val list = getMessagesBefore(
messageId,
internalConversationId,
100
limit
)
if (list.isNotEmpty()) {
@ -638,5 +693,6 @@ class OfflineFirstChatRepository @Inject constructor(
private const val HTTP_CODE_PRECONDITION_FAILED = 412
private const val HALF_SECOND = 500L
private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100
private const val DEFAULT_MESSAGES_LIMIT = 100
}
}