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

commonMain.korlibs.io.stream.async.BufferedAsyncStream.kt Maven / Gradle / Ivy

@file:Suppress("PackageDirectoryMismatch")

package korlibs.io.stream

import korlibs.io.stream.*
import korlibs.memory.*
import kotlinx.coroutines.sync.*
import kotlin.math.*

fun AsyncStream.buffered(blockSize: Int = 2048, blocksToRead: Int = 0x10): AsyncStream = BufferedStreamBase(this.base, blockSize, blocksToRead).toAsyncStream(this.position)

private class BufferedStreamBase(val base: AsyncStreamBase, val blockSize: Int = 2048, val blocksToRead: Int = 0x10) : AsyncStreamBase() {
    private val bsize = blockSize * blocksToRead

    override suspend fun read(position: Long, buffer: ByteArray, offset: Int, len: Int): Int = _read(position, buffer, offset, len)

    var cachedData = byteArrayOf()
    var cachedSector = -1L

    override fun toString(): String = "Buffered[$blocksToRead*$blockSize]:$base"

    suspend fun _read(position: Long, buffer: ByteArray, offset: Int, len: Int): Int {
        if (base.hasLength() && position >= base.getLength()) return -1
        val sector = position / bsize
        if (cachedSector != sector) {
            val pos = sector * bsize
            val len = if (base.hasLength()) minOf(bsize.toLong(), base.getLength() - pos).toInt() else bsize
            //println("$base, position=$position, offset=$offset, len=$len, base.getLength()=${base.getLength()}\n")
            cachedData = base.readBytes(pos, len)
            cachedSector = sector
        }
        val soffset = (position % bsize).toInt()
        val available = cachedData.size - soffset
        val toRead = min(available, len)
        arraycopy(cachedData, soffset, buffer, offset, toRead)
        return toRead
    }

    override suspend fun write(position: Long, buffer: ByteArray, offset: Int, len: Int) {
        base.write(position, buffer, offset, len)
    }

    override suspend fun setLength(value: Long) = base.setLength(value)
    override suspend fun getLength(): Long = base.getLength()
    override suspend fun close() = base.close()
}

suspend fun AsyncBufferedInputStream.readBufferedLine(limit: Int = 0x1000) =
    readUntil('\n'.code.toByte(), including = false, limit = limit).decodeToString()

fun AsyncInputStream.bufferedInput(bufferSize: Int = 0x2000): AsyncBufferedInputStream =
    AsyncBufferedInputStream(this, bufferSize)

class AsyncBufferedInputStream(val base: AsyncInputStream, val initialBits: Int = 10) : AsyncInputStream {
    private val buf = SimpleBytesDeque(initialBits)

    private val queue = Mutex()
    private val temp = buf.tempBuffer

    suspend fun require(len: Int = 1) = queue.withLock {
        while (buf.availableRead < len) {
            val read = base.read(temp, 0, temp.size)
            if (read <= 0) break
            buf.write(temp, 0, read)
        }
    }

    override suspend fun read(buffer: ByteArray, offset: Int, len: Int): Int {
        if (buf.availableRead < len) require()
        return buf.read(buffer, offset, len)
    }

    override suspend fun read(): Int {
        if (buf.availableRead < 1) require()
        return buf.readByte()
    }

    suspend fun readUntil(end: Byte, including: Boolean = true, limit: Int = 0x1000): ByteArray {
        val out = ByteArrayBuilder()
        loop@while (true) {
            require()
            if (buf.availableRead == 0) break@loop
            while (buf.availableRead > 0) {
                val byteInt = buf.readByte()
                if (byteInt < 0) break@loop
                val byte = byteInt.toByte()
                //println("chunk: $chunk, ${chunk.size}")
                if (including || byte != end) {
                    out.append(byte)
                }
                if (byte == end || out.size >= limit) break@loop
            }
        }
        return out.toByteArray()
    }

    override suspend fun close() {
        base.close()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy