All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.com.inkapplications.ack.client.KtorAprsDataClient.kt Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
package com.inkapplications.ack.client

import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import io.ktor.util.cio.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.consumeEach
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets

internal class KtorAprsDataClient(
    private val version: String
): AprsDataClient {
    private val receiveChannel = Channel(Channel.BUFFERED)
    private val sendChannel = Channel(Channel.RENDEZVOUS)

    override suspend fun connect(
        server: String,
        port: Int,
        credentials: Credentials,
        filters: List?,
        onConnect: suspend (ReceiveChannel, SendChannel) -> Unit
    ) {
        val socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp().connect(InetSocketAddress(server, port))
        val socketWrite = socket.openWriteChannel(autoFlush = true)
        val socketRead = socket.openReadChannel()
        val commands = filters?.let { "filter ${it.joinToString(" ")}" } ?: ""
        coroutineScope {
            launch {
                while (isActive && !socketRead.isClosedForRead) {
                    socketRead.readLine {
                        receiveChannel.offer(it)
                    }
                }
            }
            launch {
                sendChannel.consumeEach {
                    if (socketWrite.isClosedForWrite) {
                        cancel()
                    } else {
                        socketWrite.write("$it\r\n")
                    }
                }
            }
            sendChannel.send("user ${credentials.callsign} pass ${credentials.passcode} vers $version $commands")
            onConnect(receiveChannel, sendChannel)
        }
    }

    private suspend inline fun ByteReadChannel.readLine(process: (String) -> Unit) {
        val buffer = ByteBuffer.allocate(333)
        val sentinel = readUntilDelimiter(ByteBuffer.wrap("\r\n".toByteArray()), buffer)
        if (sentinel == -1) return
        val line = String(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset() + buffer.position(), StandardCharsets.US_ASCII)
        discardExact(2)
        buffer.clear()
        process(line)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy