Try only synchronizing messages after last GUID
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -97,6 +97,7 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
|
|||||||
date = conversation.date
|
date = conversation.date
|
||||||
unreadCount = conversation.unreadCount
|
unreadCount = conversation.unreadCount
|
||||||
lastMessagePreview = conversation.lastMessagePreview
|
lastMessagePreview = conversation.lastMessagePreview
|
||||||
|
lastMessageGUID = conversation.lastMessageGUID
|
||||||
}
|
}
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
// Conversation does not exist. Copy it to the realm.
|
// 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) {
|
fun writeMessages(messages: List<ModelMessage>, conversation: ModelConversation, outgoing: Boolean = false) {
|
||||||
|
if (messages.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val dbConversation = getManagedConversationByGuid(conversation.guid)
|
val dbConversation = getManagedConversationByGuid(conversation.guid)
|
||||||
realm.writeBlocking {
|
realm.writeBlocking {
|
||||||
messages
|
messages
|
||||||
@@ -128,8 +133,17 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) {
|
|||||||
.map { copyToRealm(it, updatePolicy = UpdatePolicy.ALL) }
|
.map { copyToRealm(it, updatePolicy = UpdatePolicy.ALL) }
|
||||||
|
|
||||||
findLatest(dbConversation)?.let {
|
findLatest(dbConversation)?.let {
|
||||||
it.lastMessagePreview = messages.last().displayText
|
val lastMessage = messages.maxByOrNull { it.date }!!
|
||||||
it.date = messages.last().date.toInstant().toRealmInstant()
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package net.buzzert.kordophone.backend.db.model
|
package net.buzzert.kordophone.backend.db.model
|
||||||
|
|
||||||
import io.realm.kotlin.Realm
|
|
||||||
import io.realm.kotlin.ext.realmListOf
|
import io.realm.kotlin.ext.realmListOf
|
||||||
import io.realm.kotlin.ext.realmSetOf
|
|
||||||
import io.realm.kotlin.ext.toRealmList
|
import io.realm.kotlin.ext.toRealmList
|
||||||
import io.realm.kotlin.types.RealmInstant
|
import io.realm.kotlin.types.RealmInstant
|
||||||
import io.realm.kotlin.types.RealmList
|
import io.realm.kotlin.types.RealmList
|
||||||
import io.realm.kotlin.types.RealmObject
|
import io.realm.kotlin.types.RealmObject
|
||||||
import io.realm.kotlin.types.RealmSet
|
|
||||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||||
import net.buzzert.kordophone.backend.model.GUID
|
import net.buzzert.kordophone.backend.model.GUID
|
||||||
import org.mongodb.kbson.ObjectId
|
import org.mongodb.kbson.ObjectId
|
||||||
import java.time.Instant
|
|
||||||
import net.buzzert.kordophone.backend.model.Conversation as ModelConversation
|
import net.buzzert.kordophone.backend.model.Conversation as ModelConversation
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@@ -24,6 +20,7 @@ open class Conversation(
|
|||||||
var date: RealmInstant,
|
var date: RealmInstant,
|
||||||
var unreadCount: Int,
|
var unreadCount: Int,
|
||||||
|
|
||||||
|
var lastMessageGUID: String?,
|
||||||
var lastMessagePreview: String?,
|
var lastMessagePreview: String?,
|
||||||
): RealmObject
|
): RealmObject
|
||||||
{
|
{
|
||||||
@@ -35,10 +32,11 @@ open class Conversation(
|
|||||||
date = RealmInstant.now(),
|
date = RealmInstant.now(),
|
||||||
unreadCount = 0,
|
unreadCount = 0,
|
||||||
lastMessagePreview = null,
|
lastMessagePreview = null,
|
||||||
|
lastMessageGUID = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toConversation(): ModelConversation {
|
fun toConversation(): ModelConversation {
|
||||||
val conversation = ModelConversation(
|
return ModelConversation(
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
participants = participants.toList(),
|
participants = participants.toList(),
|
||||||
date = Date.from(date.toInstant()),
|
date = Date.from(date.toInstant()),
|
||||||
@@ -46,9 +44,8 @@ open class Conversation(
|
|||||||
guid = guid,
|
guid = guid,
|
||||||
lastMessagePreview = lastMessagePreview,
|
lastMessagePreview = lastMessagePreview,
|
||||||
lastMessage = null,
|
lastMessage = null,
|
||||||
|
lastFetchedMessageGUID = lastMessageGUID
|
||||||
)
|
)
|
||||||
|
|
||||||
return conversation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -57,6 +54,10 @@ open class Conversation(
|
|||||||
val o = other as Conversation
|
val o = other as Conversation
|
||||||
return guid == o.guid
|
return guid == o.guid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return guid.hashCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ModelConversation.toDatabaseConversation(): Conversation {
|
fun ModelConversation.toDatabaseConversation(): Conversation {
|
||||||
@@ -67,6 +68,7 @@ fun ModelConversation.toDatabaseConversation(): Conversation {
|
|||||||
date = from.date.toInstant().toRealmInstant()
|
date = from.date.toInstant().toRealmInstant()
|
||||||
unreadCount = from.unreadCount
|
unreadCount = from.unreadCount
|
||||||
lastMessagePreview = from.lastMessagePreview
|
lastMessagePreview = from.lastMessagePreview
|
||||||
|
lastMessageGUID = from.lastFetchedMessageGUID
|
||||||
guid = from.guid
|
guid = from.guid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ data class Conversation(
|
|||||||
|
|
||||||
@SerializedName("lastMessage")
|
@SerializedName("lastMessage")
|
||||||
var lastMessage: Message?,
|
var lastMessage: Message?,
|
||||||
|
|
||||||
|
var lastFetchedMessageGUID: String?,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun generate(): Conversation {
|
fun generate(): Conversation {
|
||||||
@@ -38,6 +40,7 @@ data class Conversation(
|
|||||||
unreadCount = 0,
|
unreadCount = 0,
|
||||||
lastMessagePreview = null,
|
lastMessagePreview = null,
|
||||||
lastMessage = null,
|
lastMessage = null,
|
||||||
|
lastFetchedMessageGUID = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +62,7 @@ data class Conversation(
|
|||||||
participants == o.participants &&
|
participants == o.participants &&
|
||||||
displayName == o.displayName &&
|
displayName == o.displayName &&
|
||||||
unreadCount == o.unreadCount &&
|
unreadCount == o.unreadCount &&
|
||||||
lastMessagePreview == o.lastMessagePreview
|
lastFetchedMessageGUID == o.lastFetchedMessageGUID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +72,8 @@ data class Conversation(
|
|||||||
result = 31 * result + participants.hashCode()
|
result = 31 * result + participants.hashCode()
|
||||||
result = 31 * result + (displayName?.hashCode() ?: 0)
|
result = 31 * result + (displayName?.hashCode() ?: 0)
|
||||||
result = 31 * result + unreadCount
|
result = 31 * result + unreadCount
|
||||||
result = 31 * result + (lastMessagePreview?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + (lastMessage?.hashCode() ?: 0)
|
result = 31 * result + (lastMessage?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (lastFetchedMessageGUID?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,9 +182,7 @@ class ChatRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun synchronizeConversation(conversation: Conversation, limit: Int = 15) = withErrorChannelHandling {
|
suspend fun synchronizeConversation(conversation: Conversation, limit: Int = 15) = withErrorChannelHandling {
|
||||||
// TODO: Should only fetch messages after the last GUID we know about.
|
val messages = fetchMessages(conversation, limit = limit, afterGUID = conversation.lastFetchedMessageGUID)
|
||||||
// But keep in mind that outgoing message GUIDs are fake...
|
|
||||||
val messages = fetchMessages(conversation, limit = limit)
|
|
||||||
database.writeMessages(messages, conversation)
|
database.writeMessages(messages, conversation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,10 +231,10 @@ class ChatRepository(
|
|||||||
private suspend fun fetchMessages(
|
private suspend fun fetchMessages(
|
||||||
conversation: Conversation,
|
conversation: Conversation,
|
||||||
limit: Int? = null,
|
limit: Int? = null,
|
||||||
before: Message? = null,
|
beforeGUID: String? = null,
|
||||||
after: Message? = null,
|
afterGUID: String? = null,
|
||||||
): List<Message> {
|
): List<Message> {
|
||||||
return apiInterface.getMessages(conversation.guid, limit, before?.guid, after?.guid)
|
return apiInterface.getMessages(conversation.guid, limit, beforeGUID, afterGUID)
|
||||||
.bodyOnSuccessOrThrow()
|
.bodyOnSuccessOrThrow()
|
||||||
.onEach { it.conversation = conversation }
|
.onEach { it.conversation = conversation }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import net.buzzert.kordophone.backend.db.CachedChatDatabase
|
import net.buzzert.kordophone.backend.db.CachedChatDatabase
|
||||||
import net.buzzert.kordophone.backend.model.Message
|
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.APIClient
|
||||||
import net.buzzert.kordophone.backend.server.APIInterface
|
import net.buzzert.kordophone.backend.server.APIInterface
|
||||||
import net.buzzert.kordophone.backend.server.Authentication
|
import net.buzzert.kordophone.backend.server.Authentication
|
||||||
@@ -85,13 +86,18 @@ class BackendTests {
|
|||||||
val (repository, mockServer) = mockRepository()
|
val (repository, mockServer) = mockRepository()
|
||||||
|
|
||||||
val conversation = mockServer.addTestConversations(1).first()
|
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()
|
val event = repository.messageDeliveredChannel.first()
|
||||||
assertEquals(event.requestGuid, guid)
|
assertEquals(event.message.text, outgoingMessage.body)
|
||||||
assertEquals(event.message.text, outgoingMessage.text)
|
|
||||||
|
|
||||||
repository.close()
|
repository.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,18 +18,19 @@ import net.buzzert.kordophone.backend.server.AuthenticationRequest
|
|||||||
import net.buzzert.kordophone.backend.server.AuthenticationResponse
|
import net.buzzert.kordophone.backend.server.AuthenticationResponse
|
||||||
import net.buzzert.kordophone.backend.server.SendMessageRequest
|
import net.buzzert.kordophone.backend.server.SendMessageRequest
|
||||||
import net.buzzert.kordophone.backend.server.SendMessageResponse
|
import net.buzzert.kordophone.backend.server.SendMessageResponse
|
||||||
|
import net.buzzert.kordophone.backend.server.UploadAttachmentResponse
|
||||||
import net.buzzert.kordophone.backend.server.authenticatedWebSocketURL
|
import net.buzzert.kordophone.backend.server.authenticatedWebSocketURL
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
import okhttp3.WebSocket
|
import okhttp3.WebSocket
|
||||||
import okhttp3.WebSocketListener
|
import okhttp3.WebSocketListener
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -66,7 +67,8 @@ class MockServer {
|
|||||||
unreadCount = 0,
|
unreadCount = 0,
|
||||||
lastMessagePreview = null,
|
lastMessagePreview = null,
|
||||||
lastMessage = 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 var updateWatchJob: Job? = null
|
||||||
private val gson: Gson = Gson()
|
private val gson: Gson = Gson()
|
||||||
|
|
||||||
|
override val isConfigured: Boolean = true
|
||||||
|
|
||||||
override fun getAPIInterface(): APIInterface {
|
override fun getAPIInterface(): APIInterface {
|
||||||
return MockServerInterface(server)
|
return MockServerInterface(server)
|
||||||
}
|
}
|
||||||
@@ -268,6 +272,13 @@ class MockServerInterface(private val server: MockServer): APIInterface {
|
|||||||
TODO("Not yet implemented")
|
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> {
|
override suspend fun authenticate(request: AuthenticationRequest): Response<AuthenticationResponse> {
|
||||||
// Anything goes!
|
// Anything goes!
|
||||||
val response = AuthenticationResponse(
|
val response = AuthenticationResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user