Private
Public Access
1
0

Try only synchronizing messages after last GUID

This commit is contained in:
2024-04-15 19:38:40 -07:00
parent 50e9971694
commit d2afecafcf
8 changed files with 57 additions and 23 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -97,6 +97,7 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
date = conversation.date
unreadCount = conversation.unreadCount
lastMessagePreview = conversation.lastMessagePreview
lastMessageGUID = conversation.lastMessageGUID
}
} catch (e: NoSuchElementException) {
// Conversation does not exist. Copy it to the realm.
@@ -121,6 +122,10 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
}
fun writeMessages(messages: List<ModelMessage>, conversation: ModelConversation, outgoing: Boolean = false) {
if (messages.isEmpty()) {
return
}
val dbConversation = getManagedConversationByGuid(conversation.guid)
realm.writeBlocking {
messages
@@ -128,8 +133,17 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
.map { copyToRealm(it, updatePolicy = UpdatePolicy.ALL) }
findLatest(dbConversation)?.let {
it.lastMessagePreview = messages.last().displayText
it.date = messages.last().date.toInstant().toRealmInstant()
val lastMessage = messages.maxByOrNull { it.date }!!
val lastMessageDate = lastMessage.date.toInstant().toRealmInstant()
if (lastMessageDate > it.date) {
it.lastMessageGUID = lastMessage.guid
it.lastMessagePreview = lastMessage.displayText
// This will cause sort order to change. I think this ends
// up getting updated whenever we get conversation changes anyway.
// it.date = lastMessageDate
}
}
}
}

View File

@@ -1,17 +1,13 @@
package net.buzzert.kordophone.backend.db.model
import io.realm.kotlin.Realm
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.ext.realmSetOf
import io.realm.kotlin.ext.toRealmList
import io.realm.kotlin.types.RealmInstant
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.RealmSet
import io.realm.kotlin.types.annotations.PrimaryKey
import net.buzzert.kordophone.backend.model.GUID
import org.mongodb.kbson.ObjectId
import java.time.Instant
import net.buzzert.kordophone.backend.model.Conversation as ModelConversation
import java.util.Date
@@ -24,6 +20,7 @@ open class Conversation(
var date: RealmInstant,
var unreadCount: Int,
var lastMessageGUID: String?,
var lastMessagePreview: String?,
): RealmObject
{
@@ -35,10 +32,11 @@ open class Conversation(
date = RealmInstant.now(),
unreadCount = 0,
lastMessagePreview = null,
lastMessageGUID = null,
)
fun toConversation(): ModelConversation {
val conversation = ModelConversation(
return ModelConversation(
displayName = displayName,
participants = participants.toList(),
date = Date.from(date.toInstant()),
@@ -46,9 +44,8 @@ open class Conversation(
guid = guid,
lastMessagePreview = lastMessagePreview,
lastMessage = null,
lastFetchedMessageGUID = lastMessageGUID
)
return conversation
}
override fun equals(other: Any?): Boolean {
@@ -57,6 +54,10 @@ open class Conversation(
val o = other as Conversation
return guid == o.guid
}
override fun hashCode(): Int {
return guid.hashCode()
}
}
fun ModelConversation.toDatabaseConversation(): Conversation {
@@ -67,6 +68,7 @@ fun ModelConversation.toDatabaseConversation(): Conversation {
date = from.date.toInstant().toRealmInstant()
unreadCount = from.unreadCount
lastMessagePreview = from.lastMessagePreview
lastMessageGUID = from.lastFetchedMessageGUID
guid = from.guid
}
}

View File

@@ -27,6 +27,8 @@ data class Conversation(
@SerializedName("lastMessage")
var lastMessage: Message?,
var lastFetchedMessageGUID: String?,
) {
companion object {
fun generate(): Conversation {
@@ -38,6 +40,7 @@ data class Conversation(
unreadCount = 0,
lastMessagePreview = null,
lastMessage = null,
lastFetchedMessageGUID = null,
)
}
}
@@ -59,7 +62,7 @@ data class Conversation(
participants == o.participants &&
displayName == o.displayName &&
unreadCount == o.unreadCount &&
lastMessagePreview == o.lastMessagePreview
lastFetchedMessageGUID == o.lastFetchedMessageGUID
)
}
@@ -69,8 +72,8 @@ data class Conversation(
result = 31 * result + participants.hashCode()
result = 31 * result + (displayName?.hashCode() ?: 0)
result = 31 * result + unreadCount
result = 31 * result + (lastMessagePreview?.hashCode() ?: 0)
result = 31 * result + (lastMessage?.hashCode() ?: 0)
result = 31 * result + (lastFetchedMessageGUID?.hashCode() ?: 0)
return result
}
}

View File

@@ -182,9 +182,7 @@ class ChatRepository(
}
suspend fun synchronizeConversation(conversation: Conversation, limit: Int = 15) = withErrorChannelHandling {
// TODO: Should only fetch messages after the last GUID we know about.
// But keep in mind that outgoing message GUIDs are fake...
val messages = fetchMessages(conversation, limit = limit)
val messages = fetchMessages(conversation, limit = limit, afterGUID = conversation.lastFetchedMessageGUID)
database.writeMessages(messages, conversation)
}
@@ -233,10 +231,10 @@ class ChatRepository(
private suspend fun fetchMessages(
conversation: Conversation,
limit: Int? = null,
before: Message? = null,
after: Message? = null,
beforeGUID: String? = null,
afterGUID: String? = null,
): List<Message> {
return apiInterface.getMessages(conversation.guid, limit, before?.guid, after?.guid)
return apiInterface.getMessages(conversation.guid, limit, beforeGUID, afterGUID)
.bodyOnSuccessOrThrow()
.onEach { it.conversation = conversation }
}

View File

@@ -9,6 +9,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.buzzert.kordophone.backend.db.CachedChatDatabase
import net.buzzert.kordophone.backend.model.Message
import net.buzzert.kordophone.backend.model.OutgoingMessage
import net.buzzert.kordophone.backend.server.APIClient
import net.buzzert.kordophone.backend.server.APIInterface
import net.buzzert.kordophone.backend.server.Authentication
@@ -85,13 +86,18 @@ class BackendTests {
val (repository, mockServer) = mockRepository()
val conversation = mockServer.addTestConversations(1).first()
val outgoingMessage = MockServer.generateMessage(conversation)
val generatedMessage = MockServer.generateMessage(conversation)
val outgoingMessage = OutgoingMessage(
body = generatedMessage.text,
conversation = conversation,
attachmentUris = setOf(),
attachmentDataSource = { null },
)
val guid = repository.enqueueOutgoingMessage(outgoingMessage, conversation)
repository.enqueueOutgoingMessage(outgoingMessage)
val event = repository.messageDeliveredChannel.first()
assertEquals(event.requestGuid, guid)
assertEquals(event.message.text, outgoingMessage.text)
assertEquals(event.message.text, outgoingMessage.body)
repository.close()
}

View File

@@ -18,18 +18,19 @@ import net.buzzert.kordophone.backend.server.AuthenticationRequest
import net.buzzert.kordophone.backend.server.AuthenticationResponse
import net.buzzert.kordophone.backend.server.SendMessageRequest
import net.buzzert.kordophone.backend.server.SendMessageResponse
import net.buzzert.kordophone.backend.server.UploadAttachmentResponse
import net.buzzert.kordophone.backend.server.authenticatedWebSocketURL
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import retrofit2.Call
import retrofit2.Response
import java.util.Date
import java.util.UUID
@@ -66,7 +67,8 @@ class MockServer {
unreadCount = 0,
lastMessagePreview = null,
lastMessage = null,
guid = UUID.randomUUID().toString()
guid = UUID.randomUUID().toString(),
lastFetchedMessageGUID = null,
)
}
}
@@ -166,6 +168,8 @@ class MockServerClient(private val server: MockServer): APIClient, WebSocketList
private var updateWatchJob: Job? = null
private val gson: Gson = Gson()
override val isConfigured: Boolean = true
override fun getAPIInterface(): APIInterface {
return MockServerInterface(server)
}
@@ -268,6 +272,13 @@ class MockServerInterface(private val server: MockServer): APIInterface {
TODO("Not yet implemented")
}
override suspend fun uploadAttachment(
filename: String,
body: RequestBody
): Response<UploadAttachmentResponse> {
TODO("Not yet implemented")
}
override suspend fun authenticate(request: AuthenticationRequest): Response<AuthenticationResponse> {
// Anything goes!
val response = AuthenticationResponse(