aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/photos/ByteRange.kt
blob: 001bea2f8c08ae6edf6f34777f5541dea5eb4a60 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 }
            }
        }
    }
}