From a8886279c690707b84de10900d8e5fd08ec5baff Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sun, 10 Dec 2023 18:30:56 -0800 Subject: [PATCH] Update send message to account for guid returned by servers --- .idea/misc.xml | 1 - .../ui/messagelist/MessageListViewModel.kt | 17 +++++---- .../backend/db/CachedChatDatabase.kt | 22 ++++++------ .../kordophone/backend/db/model/Message.kt | 3 -- .../backend/events/MessageDeliveredEvent.kt | 2 +- .../kordophone/backend/server/APIInterface.kt | 7 +++- .../backend/server/ChatRepository.kt | 36 ++++++++++++------- 7 files changed, 52 insertions(+), 36 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index e7b1341..2530821 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/java/net/buzzert/kordophonedroid/ui/messagelist/MessageListViewModel.kt b/app/src/main/java/net/buzzert/kordophonedroid/ui/messagelist/MessageListViewModel.kt index 125a5cd..3b9246d 100644 --- a/app/src/main/java/net/buzzert/kordophonedroid/ui/messagelist/MessageListViewModel.kt +++ b/app/src/main/java/net/buzzert/kordophonedroid/ui/messagelist/MessageListViewModel.kt @@ -36,8 +36,13 @@ class MessageListViewModel @Inject constructor( } } + private data class OutgoingMessage( + val requestGuid: String, + val message: Message, + ) + private var conversation: Conversation? = null - private val pendingMessages: MutableStateFlow> = MutableStateFlow(listOf()) + private val pendingMessages: MutableStateFlow> = MutableStateFlow(listOf()) init { viewModelScope.launch { @@ -45,14 +50,14 @@ class MessageListViewModel @Inject constructor( // By now, the repository should've committed this to the store. repository.messageDeliveredChannel.collectLatest { event -> pendingMessages.value = - pendingMessages.value.filter { it.guid != event.message.guid } + pendingMessages.value.filter { it.requestGuid != event.requestGuid } } } } val messages: Flow> get() = repository.messagesChanged(conversation!!) - .combine(pendingMessages) { a, b -> a.union(b) } + .combine(pendingMessages) { a, b -> a.union(b.map { it.message }) } .map { messages -> messages .sortedBy { it.date } @@ -70,12 +75,12 @@ class MessageListViewModel @Inject constructor( conversation = conversation!!, ) - pendingMessages.value = pendingMessages.value + listOf(outgoingMessage) - repository.enqueueOutgoingMessage(outgoingMessage, conversation!!) + val outgoingGUID = repository.enqueueOutgoingMessage(outgoingMessage, conversation!!) + pendingMessages.value = pendingMessages.value + listOf(OutgoingMessage(outgoingGUID, outgoingMessage)) } fun isPendingMessage(message: Message): Boolean { - return pendingMessages.value.contains(message) + return pendingMessages.value.any { it.message.guid == message.guid } } fun synchronize() = viewModelScope.launch { diff --git a/backend/src/main/java/net/buzzert/kordophone/backend/db/CachedChatDatabase.kt b/backend/src/main/java/net/buzzert/kordophone/backend/db/CachedChatDatabase.kt index 4d06c13..b54ec26 100644 --- a/backend/src/main/java/net/buzzert/kordophone/backend/db/CachedChatDatabase.kt +++ b/backend/src/main/java/net/buzzert/kordophone/backend/db/CachedChatDatabase.kt @@ -1,5 +1,6 @@ package net.buzzert.kordophone.backend.db +import android.util.Log import io.realm.kotlin.MutableRealm import io.realm.kotlin.Realm import io.realm.kotlin.RealmConfiguration @@ -15,6 +16,7 @@ import net.buzzert.kordophone.backend.db.model.toDatabaseConversation import net.buzzert.kordophone.backend.db.model.toDatabaseMessage import net.buzzert.kordophone.backend.db.model.toRealmInstant import net.buzzert.kordophone.backend.model.GUID +import net.buzzert.kordophone.backend.server.REPO_LOG import java.lang.IllegalArgumentException import net.buzzert.kordophone.backend.model.Conversation as ModelConversation import net.buzzert.kordophone.backend.model.Message as ModelMessage @@ -41,7 +43,15 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) { } } - private val realm = Realm.open(realmConfig) + private val realm = runCatching { + Realm.open(realmConfig) + }.recover { + // We're just a caching layer, so in the event of a migration error, just delete and start over. + Log.d(REPO_LOG, "Error opening (${it.message}). Recovering by deleting database.") + Realm.deleteRealm(realmConfig) + + return@recover Realm.open(realmConfig) + }.getOrThrow() // Flow for watching changes to the database val conversationChanges: Flow> @@ -122,16 +132,6 @@ class CachedChatDatabase (private val realmConfig: RealmConfiguration) { } } - fun clearOutgoingMessages() { - realm.query(Message::class, "isOutgoing == true").find().forEach { message -> - realm.writeBlocking { - findLatest(message)?.let { - delete(it) - } - } - } - } - fun fetchMessages(conversation: ModelConversation): List { val dbConversation = getConversationByGuid(conversation.guid) return dbConversation.messages.map { it.toMessage(dbConversation.toConversation()) } diff --git a/backend/src/main/java/net/buzzert/kordophone/backend/db/model/Message.kt b/backend/src/main/java/net/buzzert/kordophone/backend/db/model/Message.kt index c27f151..132be9b 100644 --- a/backend/src/main/java/net/buzzert/kordophone/backend/db/model/Message.kt +++ b/backend/src/main/java/net/buzzert/kordophone/backend/db/model/Message.kt @@ -22,7 +22,6 @@ open class Message( var date: RealmInstant, var conversationGUID: GUID, - var isOutgoing: Boolean, ): RealmObject { constructor() : this( @@ -31,7 +30,6 @@ open class Message( sender = null, date = RealmInstant.now(), conversationGUID = ObjectId().toString(), - isOutgoing = false, ) fun toMessage(parentConversation: ModelConversation): ModelMessage { @@ -53,6 +51,5 @@ fun ModelMessage.toDatabaseMessage(outgoing: Boolean = false): Message { sender = from.sender date = from.date.toInstant().toRealmInstant() conversationGUID = from.conversation.guid - isOutgoing = outgoing } } diff --git a/backend/src/main/java/net/buzzert/kordophone/backend/events/MessageDeliveredEvent.kt b/backend/src/main/java/net/buzzert/kordophone/backend/events/MessageDeliveredEvent.kt index 75e063d..b5c1971 100644 --- a/backend/src/main/java/net/buzzert/kordophone/backend/events/MessageDeliveredEvent.kt +++ b/backend/src/main/java/net/buzzert/kordophone/backend/events/MessageDeliveredEvent.kt @@ -5,7 +5,7 @@ import net.buzzert.kordophone.backend.model.GUID import net.buzzert.kordophone.backend.model.Message data class MessageDeliveredEvent( - val guid: GUID, val message: Message, val conversation: Conversation, + val requestGuid: GUID, ) diff --git a/backend/src/main/java/net/buzzert/kordophone/backend/server/APIInterface.kt b/backend/src/main/java/net/buzzert/kordophone/backend/server/APIInterface.kt index f93f4a1..dd503de 100644 --- a/backend/src/main/java/net/buzzert/kordophone/backend/server/APIInterface.kt +++ b/backend/src/main/java/net/buzzert/kordophone/backend/server/APIInterface.kt @@ -25,6 +25,11 @@ data class SendMessageRequest( val transferGUIDs: List?, ) +data class SendMessageResponse( + @SerializedName("guid") + val sentMessageGUID: String, +) + interface APIInterface { @GET("/version") suspend fun getVersion(): ResponseBody @@ -41,7 +46,7 @@ interface APIInterface { ): Response> @POST("/sendMessage") - suspend fun sendMessage(@Body request: SendMessageRequest): Response + suspend fun sendMessage(@Body request: SendMessageRequest): Response } class ResponseDecodeError(val response: ResponseBody): Exception() diff --git a/backend/src/main/java/net/buzzert/kordophone/backend/server/ChatRepository.kt b/backend/src/main/java/net/buzzert/kordophone/backend/server/ChatRepository.kt index 393813c..b1f8e98 100644 --- a/backend/src/main/java/net/buzzert/kordophone/backend/server/ChatRepository.kt +++ b/backend/src/main/java/net/buzzert/kordophone/backend/server/ChatRepository.kt @@ -65,7 +65,7 @@ class ChatRepository( private data class OutgoingMessageInfo ( val message: Message, val conversation: Conversation, - val guid: GUID, + val requestGuid: GUID, ) private val apiInterface = apiClient.getAPIInterface() @@ -133,12 +133,6 @@ class ChatRepository( val serverConversations = fetchConversations() database.updateConversations(serverConversations) - // Delete outgoing conversations - // This is an unfortunate limitation in that we don't know what outgoing GUIDs are going to - // be before we send them. - // TODO: Keep this in mind when syncing messages after a certain GUID. The outgoing GUIDs are fake. - database.clearOutgoingMessages() - // Sync top N number of conversations' message content Log.d(REPO_LOG, "Synchronizing messages") val sortedConversations = conversations.sortedBy { it.date }.reversed() @@ -198,23 +192,39 @@ class ChatRepository( while (true) { val outgoingMessageRequest = outgoingMessageQueue.poll()?.let { runBlocking { - val guid = it.guid - val message = it.message + val outgoingMessage = it.message val conversation = it.conversation + val requestGuid = it.requestGuid - Log.d(REPO_LOG, "Sending message to $conversation: $message") + Log.d(REPO_LOG, "Sending message to $conversation: $outgoingMessage") val result = apiInterface.sendMessage( SendMessageRequest( conversationGUID = conversation.guid, - body = message.text, + body = outgoingMessage.text, transferGUIDs = null, ) ) if (result.isSuccessful) { - Log.d(REPO_LOG, "Successfully sent message.") - _messageDeliveredChannel.emit(MessageDeliveredEvent(guid, message, conversation)) + val messageGuid = result.body()?.sentMessageGUID ?: outgoingMessage.guid + Log.d(REPO_LOG, "Successfully sent message: $messageGuid") + + val newMessage = Message( + guid = messageGuid, + text = outgoingMessage.text, + sender = null, + conversation = it.conversation, + date = outgoingMessage.date + ) + + _messageDeliveredChannel.emit( + MessageDeliveredEvent( + newMessage, + conversation, + requestGuid + ) + ) } else { Log.e(REPO_LOG, "Error sending message. Enqueuing for retry.") outgoingMessageQueue.add(it)