Private
Public Access
1
0

Implements /sendMessage using a message queue

This commit is contained in:
2023-08-07 22:52:33 -07:00
parent 7a5e1fd338
commit 0f87ea2200
7 changed files with 139 additions and 14 deletions

View File

@@ -1,2 +0,0 @@
package net.buzzert.kordophone.backend

View File

@@ -0,0 +1,9 @@
package net.buzzert.kordophone.backend.events
import net.buzzert.kordophone.backend.model.Conversation
import net.buzzert.kordophone.backend.model.Message
data class MessageDeliveredEvent(
val message: Message,
val conversation: Conversation,
)

View File

@@ -1,13 +1,27 @@
package net.buzzert.kordophone.backend.server package net.buzzert.kordophone.backend.server
import com.google.gson.annotations.SerializedName
import net.buzzert.kordophone.backend.model.Conversation import net.buzzert.kordophone.backend.model.Conversation
import net.buzzert.kordophone.backend.model.Message import net.buzzert.kordophone.backend.model.Message
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.Call import retrofit2.Call
import retrofit2.Response import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query import retrofit2.http.Query
data class SendMessageRequest(
@SerializedName("guid")
val conversationGUID: String,
@SerializedName("body")
val body: String,
@SerializedName("fileTransferGUIDs")
val transferGUIDs: List<String>?,
)
interface APIInterface { interface APIInterface {
@GET("/version") @GET("/version")
suspend fun getVersion(): ResponseBody suspend fun getVersion(): ResponseBody
@@ -17,4 +31,7 @@ interface APIInterface {
@GET("/messages") @GET("/messages")
suspend fun getMessages(@Query("guid") conversationGUID: String): Response<List<Message>> suspend fun getMessages(@Query("guid") conversationGUID: String): Response<List<Message>>
@POST("/sendMessage")
suspend fun sendMessage(@Body request: SendMessageRequest): Response<Void>
} }

View File

@@ -1,14 +1,22 @@
package net.buzzert.kordophone.backend.server package net.buzzert.kordophone.backend.server
import android.util.Log
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.runBlocking
import net.buzzert.kordophone.backend.events.MessageDeliveredEvent
import net.buzzert.kordophone.backend.model.Conversation import net.buzzert.kordophone.backend.model.Conversation
import net.buzzert.kordophone.backend.model.Message import net.buzzert.kordophone.backend.model.Message
import java.net.URL import java.net.URL
import java.util.Queue
import java.util.concurrent.ArrayBlockingQueue
class ChatRepository(apiInterface: APIInterface) { const val REPO_LOG: String = "ChatRepository"
// private val client: APIClient = APIClient(baseURL)
// private val apiInterface: APIInterface = client.getClient().create(APIInterface::class.java)
private val apiInterface: APIInterface = apiInterface class ChatRepository(private val apiInterface: APIInterface) {
public val messageDeliveredChannel = Channel<MessageDeliveredEvent>()
private val outgoingMessageQueue: ArrayBlockingQueue<Pair<Message, Conversation>> = ArrayBlockingQueue(16)
private var outgoingMessageThread: Thread? = null
suspend fun getVersion(): String { suspend fun getVersion(): String {
return apiInterface.getVersion().string() return apiInterface.getVersion().string()
@@ -21,4 +29,44 @@ class ChatRepository(apiInterface: APIInterface) {
suspend fun fetchMessages(conversation: Conversation): List<Message> { suspend fun fetchMessages(conversation: Conversation): List<Message> {
return apiInterface.getMessages(conversation.guid).body()!! return apiInterface.getMessages(conversation.guid).body()!!
} }
suspend fun enqueueOutgoingMessage(message: Message, conversation: Conversation) {
Log.d(REPO_LOG, "Enqueuing outgoing message: $message")
outgoingMessageQueue.add(Pair(message, conversation))
if (outgoingMessageThread == null) {
outgoingMessageThread = Thread { outgoingMessageQueueMain() }
outgoingMessageThread?.start()
}
}
// - private
private fun outgoingMessageQueueMain() {
Log.d(REPO_LOG, "Outgoing Message Queue Main")
while (true) {
val outgoingMessageRequest = outgoingMessageQueue.poll()?.let {
runBlocking {
val (message, conversation) = it
Log.d(REPO_LOG, "Sending message to $conversation: $message")
val result = apiInterface.sendMessage(
SendMessageRequest(
conversationGUID = conversation.guid,
body = message.text,
transferGUIDs = null,
)
)
if (result.isSuccessful) {
Log.d(REPO_LOG, "Successfully sent message.")
messageDeliveredChannel.send(MessageDeliveredEvent(message, conversation))
} else {
Log.e(REPO_LOG, "Error sending message. Enqueuing for retry.")
outgoingMessageQueue.add(it)
}
}
}
}
}
} }

View File

@@ -5,6 +5,8 @@ import net.buzzert.kordophone.backend.model.Message
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.Response import retrofit2.Response
import java.util.Date
import java.util.UUID
class MockServer: APIInterface { class MockServer: APIInterface {
val version = "Kordophone-2.0" val version = "Kordophone-2.0"
@@ -23,6 +25,19 @@ class MockServer: APIInterface {
return Response.success(messages[conversationGUID]) return Response.success(messages[conversationGUID])
} }
override suspend fun sendMessage(request: SendMessageRequest): Response<Void> {
messages[request.conversationGUID]!!.add(
Message(
text = request.body,
date = Date(),
guid = UUID.randomUUID().toString(),
sender = null, // me
)
)
return Response.success(null)
}
fun addConversation(conversation: Conversation) { fun addConversation(conversation: Conversation) {
conversations.add(conversation) conversations.add(conversation)
} }

View File

@@ -7,19 +7,13 @@ 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.ChatRepository import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophone.backend.server.MockServer import net.buzzert.kordophone.backend.server.MockServer
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import org.junit.Assert.*
import java.net.URL import java.net.URL
import java.util.Date import java.util.Date
import java.util.UUID import java.util.UUID
/** class BackendTests {
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
private val mockServer = MockServer().also { private val mockServer = MockServer().also {
val conversation = Conversation( val conversation = Conversation(
date = Date(), date = Date(),
@@ -74,4 +68,19 @@ class ExampleUnitTest {
assertEquals(message.text, "Hey") assertEquals(message.text, "Hey")
} }
@Test
fun testSendMessage() = runBlocking {
val conversation = repository.fetchConversations().first()
val outgoingMessage = Message(
date = Date(),
text = "Hello there!",
guid = UUID.randomUUID().toString(),
sender = null,
)
repository.enqueueOutgoingMessage(outgoingMessage, conversation)
val event = repository.messageDeliveredChannel.receive()
assertEquals(event.message.text, outgoingMessage.text)
}
} }

View File

@@ -0,0 +1,29 @@
package android.util
public class Log {
companion object {
@JvmStatic
fun d(tag: String, msg: String): Int {
println("DEBUG: $tag: $msg")
return 0
}
@JvmStatic
fun i(tag: String, msg: String): Int {
println("INFO: $tag: $msg")
return 0
}
@JvmStatic
fun w(tag: String, msg: String): Int {
println("WARN: $tag: $msg")
return 0
}
@JvmStatic
fun e(tag: String, msg: String): Int {
println("ERROR: $tag: $msg")
return 0
}
}
}