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

commonMain.com.fleeksoft.ksoup.internal.ConstrainableSource.kt Maven / Gradle / Ivy

package com.fleeksoft.ksoup.internal

/**
 * A com.fleeksoft.ksoup internal class (so don't use it as there is no contract API) that enables constraints on an Input Stream,
 * namely a maximum read size, and the ability to Thread.interrupt() the read.
 */
import com.fleeksoft.ksoup.ported.BufferReader
import com.fleeksoft.ksoup.ported.System
import okio.Buffer

internal class ConstrainableSource(
    bufferReader: BufferReader,
    maxSize: Int,
) : BufferReader(bufferReader) {
    companion object {
        private const val DEFAULT_SIZE = 1024 * 32

        fun wrap(
            bufferReader: BufferReader,
            maxSize: Int,
        ): ConstrainableSource {
            return if (bufferReader is ConstrainableSource) {
                bufferReader
            } else {
                ConstrainableSource(bufferReader, maxSize)
            }
        }
    }

    private val capped: Boolean = maxSize != 0
    private var startTime = System.nanoTime()
    private var timeout: Long = 0 // optional max time of request
    private var remaining = maxSize
    private var interrupted = false

    init {
        require(maxSize >= 0) { "maxSize must be 0 (unlimited) or larger" }
    }

    fun fullyRead(): Boolean = this.exhausted()

    override fun read(
        sink: ByteArray,
        offset: Int,
        byteCount: Int,
    ): Int {
        if (interrupted || capped && remaining <= 0) {
            return -1
        }
        if (expired()) {
            throw Exception("Read timeout")
        }

        val toRead = if (capped && byteCount > remaining) remaining else byteCount

        return try {
            val calculatedByteCount: Int = if (this.size() > 0) this.size().toInt() else toRead
            val read =
                getBuffer().read(
                    sink = sink,
                    offset = 0,
                    byteCount = calculatedByteCount,
                )
            if (!this.exhausted()) {
                remaining -= read
            }
            read
        } catch (e: Exception) {
            throw e
        }
    }

    fun readToByteBuffer(max: Int): BufferReader {
        require(max >= 0) { "maxSize must be 0 (unlimited) or larger" }
        val localCapped = max > 0
        val bufferSize = if (localCapped && max < DEFAULT_SIZE) max else DEFAULT_SIZE

        var read: Int
        var remaining = max

        val buffer = Buffer()

        while (true) {
            val size: Int = if (this.size() > 0) this.size().toInt() else bufferSize
            val readBuffer = ByteArray(size)
            read = this.read(readBuffer, 0, size)
            if (read > 0) {
                buffer.write(readBuffer, 0, read)
            }
            if (this.exhausted()) break
            if (localCapped) {
                if (read >= remaining) {
                    break
                }
                remaining -= read
            }
        }
        return BufferReader(buffer)
    }

    fun timeout(
        startTimeNanos: Long,
        timeoutMillis: Long,
    ): ConstrainableSource {
        this.startTime = startTimeNanos
        this.timeout = timeoutMillis * 1_000_000
        return this
    }

    private fun expired(): Boolean {
        if (timeout == 0L) {
            return false
        }
        val now = System.nanoTime()
        val dur = now - startTime
        return dur > timeout
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy