package org.meshbay.client.photos import java.io.FilterInputStream import java.io.InputStream /** * One `Range: bytes=a-b` of a file the page fetches a piece at a time, so a * video of gigabytes never sits whole in the page's memory: the upload reads * one chunk, sends it, and asks for the next. */ object ByteRange { /** The first and last byte asked for, inside `size`; null for no range or one this does not serve. */ fun parse(header: String?, size: Long): LongRange? { val m = Regex("^bytes=(\\d+)-(\\d*)$").matchEntire(header?.trim() ?: return null) ?: return null val first = m.groupValues[1].toLongOrNull() ?: return null val last = m.groupValues[2].takeIf { it.isNotEmpty() }?.toLongOrNull() ?: (size - 1) if (first >= size || last < first) return null return first..minOf(last, size - 1) } /** `stream` from `range.first`, ending after `range.last`. */ fun slice(stream: InputStream, range: LongRange): InputStream { var toSkip = range.first while (toSkip > 0) { val n = stream.skip(toSkip) if (n <= 0) { if (stream.read() < 0) break; toSkip -= 1 } else toSkip -= n } return object : FilterInputStream(stream) { private var left = range.last - range.first + 1 override fun read(): Int { if (left <= 0) return -1 return super.read().also { if (it >= 0) left -= 1 } } override fun read(b: ByteArray, off: Int, len: Int): Int { if (left <= 0) return -1 return super.read(b, off, minOf(len.toLong(), left).toInt()).also { if (it > 0) left -= it } } } } }