Server/File-Server/src/main/kotlin/js5server/FileServer.kt

111 lines
No EOL
3.7 KiB
Kotlin

package js5server
import com.displee.cache.CacheLibrary
import io.ktor.utils.io.*
import js5server.ext.readUMedium
import kotlin.math.min
class FileServer(
private val provider: DataProvider,
private val versionTable: ByteArray
) {
/**
* Fulfills a request by sending the requested files data to the requester
*/
suspend fun fulfill(read: ByteReadChannel, write: ByteWriteChannel, prefetch: Boolean) {
val value = read.readUMedium()
val index = value shr 16
val archive = value and 0xffff
val data = data(index, archive) ?: return println("Unable to fulfill request $index $archive $prefetch.")
if (index == 255 && archive == 255) {
serve255(write, data)
} else {
serve(write, index, archive, data, prefetch)
}
}
/**
* @return data for an [index]'s [archive] file or [versionTable] when index and archive are both 255
*/
fun data(index: Int, archive: Int): ByteArray? {
if (index == 255 && archive == 255) {
return versionTable
}
return provider.data(index, archive)
}
/**
* Writes [source] [offset] [size] to [write] and starting at [headerSize] inserting a [SEPARATOR] every [split] bytes
*/
private suspend fun serve255(write: ByteWriteChannel, source: ByteArray) {
write.writeByte(255)
write.writeShort(255)
write.writeByte(0)
write.writeInt(source.size)
write.writeFully(source)
}
/**
* Writes response header followed by the contents of [data] to [write]
*/
suspend fun serve(write: ByteWriteChannel, index: Int, archive: Int, data: ByteArray, prefetch: Boolean) {
val compression = data[0].toInt()
val size = getInt(data[1], data[2], data[3], data[4]) + if (compression != 0) 8 else 4
write.writeByte(index)
write.writeShort(archive)
write.writeByte(if (prefetch) compression or 0x80 else compression)
serve(write, HEADER, data, OFFSET, size, SPLIT)
}
/**
* Writes [source] [offset] [size] to [write] and starting at [headerSize] inserting a [SEPARATOR] every [split] bytes
*/
suspend fun serve(write: ByteWriteChannel, headerSize: Int, source: ByteArray, offset: Int, size: Int, split: Int) {
var length = min(size, split - headerSize)
write.writeFully(source, offset, length)
var written = length
while (written < size) {
write.writeByte(SEPARATOR)
length = if (size - written < split) size - written else split - 1
write.writeFully(source, written + offset, length)
written += length
}
}
companion object {
private fun getInt(b1: Byte, b2: Byte, b3: Byte, b4: Byte) = b1.toInt() shl 24 or (b2.toInt() and 0xff shl 16) or (b3.toInt() and 0xff shl 8) or (b4.toInt() and 0xff)
private const val SEPARATOR = 255
private const val HEADER = 4
private const val SPLIT = 512
private const val OFFSET = 1
}
/**
* Length of archive with [name] in [index]
*/
fun file(cacheLibrary: CacheLibrary, index: Int, name: String): Int {
val idx = cacheLibrary.index(index)
val archive = idx.archiveId(name)
if (archive == -1) {
return 0
}
return (idx.readArchiveSector(archive)?.size ?: 2) - 2
}
/**
* Length of all archives in [index]
*/
fun archive(cache: CacheLibrary, index: Int): Int {
var total = 0
val idx = cache.index(index)
idx.archiveIds().forEach { archive ->
total += idx.readArchiveSector(archive)?.size ?: 0
}
total += cache.index255?.readArchiveSector(index)?.size ?: 0
return total
}
}