Add block device Input/Output Stream wrappers

This commit is contained in:
Davide Depau 2018-12-24 02:59:40 +01:00
parent 53c21a814e
commit 9c68672310
Signed by: depau
GPG key ID: C7D999B6A55EFE86
2 changed files with 201 additions and 0 deletions

View file

@ -0,0 +1,116 @@
package eu.depau.etchdroid.utils.blockdevice
import com.github.mjdev.libaums.driver.BlockDeviceDriver
import java.io.InputStream
import java.nio.ByteBuffer
class BlockDeviceInputStream(
private val blockDev: BlockDeviceDriver,
private val prefetchBlocks: Int = 2048
) : InputStream() {
private val byteBuffer = ByteBuffer.allocate(blockDev.blockSize * prefetchBlocks)
private var currentBlockOffset: Long = 0
private val currentByteOffset: Long
get() = currentBlockOffset * blockDev.blockSize + byteBuffer.position()
private var markedBlock: Long = 0
private var markedBufferPosition: Int = 0
private val sizeBytes: Long
get() = blockDev.size.toLong() * blockDev.blockSize
private fun isNextByteAfterEOF(): Boolean {
if (byteBuffer.hasRemaining())
return false
return currentBlockOffset + 1 >= blockDev.size
}
private fun fetch() {
byteBuffer.clear()
if (blockDev.size - currentBlockOffset < prefetchBlocks)
byteBuffer.limit(
(currentBlockOffset - blockDev.size).toInt() * blockDev.blockSize
)
blockDev.read(currentBlockOffset, byteBuffer)
}
private fun fetchNextIfNeeded() {
if (byteBuffer.hasRemaining())
return
currentBlockOffset++
fetch()
}
override fun read(): Int {
if (isNextByteAfterEOF())
return -1
fetchNextIfNeeded()
return byteBuffer.get().toInt() and 0xFF
}
override fun read(b: ByteArray): Int {
return read(b, 0, b.size)
}
override fun read(b: ByteArray, off: Int, len: Int): Int {
if (isNextByteAfterEOF())
return -1
val maxPos = (off + len) % (b.size + 1)
if (len <= 0 || off > b.size)
return 0
var bytesRead = 0
for (i in off until maxPos) {
val readByte = read()
if (readByte == -1)
break
b[i] = readByte.toByte()
}
return bytesRead
}
override fun skip(n: Long): Long {
val actualSkipDistance = when {
currentByteOffset + n > sizeBytes -> sizeBytes - currentByteOffset
currentByteOffset + n < 0 -> -currentByteOffset
else -> n
}
val newByteOffset = currentByteOffset + actualSkipDistance
currentBlockOffset = newByteOffset / blockDev.blockSize
fetch()
byteBuffer.position((newByteOffset - currentBlockOffset * blockDev.blockSize).toInt())
return actualSkipDistance
}
override fun available(): Int {
return byteBuffer.remaining()
}
override fun mark(readlimit: Int) {
markedBlock = currentBlockOffset
markedBufferPosition = byteBuffer.position()
}
override fun markSupported(): Boolean {
return true
}
override fun reset() {
currentBlockOffset = markedBlock
fetch()
byteBuffer.position(markedBufferPosition)
}
}

View file

@ -0,0 +1,85 @@
package eu.depau.etchdroid.utils.blockdevice
import com.github.mjdev.libaums.driver.BlockDeviceDriver
import java.io.IOException
import java.io.OutputStream
import java.nio.ByteBuffer
class BlockDeviceOutputStream(
private val blockDev: BlockDeviceDriver,
bufferBlocks: Int = 2048
) : OutputStream() {
private val byteBuffer = ByteBuffer.allocate(blockDev.blockSize * bufferBlocks)
private var currentBlockOffset: Long = 0
private val currentByteOffset: Long
get() = currentBlockOffset * blockDev.blockSize + byteBuffer.position()
private val sizeBytes: Long
get() = blockDev.size.toLong() * blockDev.blockSize
private val bytesUntilEOF: Long
get() = blockDev.size.toLong() * blockDev.size - currentByteOffset
override fun write(b: Int) {
if (bytesUntilEOF < 1)
throw IOException("No space left on device")
byteBuffer.put(b.toByte())
if (byteBuffer.remaining() == 0)
flush()
}
override fun write(b: ByteArray) {
write(b, 0, b.size)
}
override fun write(b: ByteArray, off: Int, len: Int) {
val maxPos = (off + len) % (b.size + 1)
if (len <= 0 || off > b.size)
return
for (i in off until maxPos)
write(b[i].toInt())
}
override fun flush() {
byteBuffer.flip()
val toWrite = byteBuffer.limit()
val incompleteBlockFullBytes = toWrite % blockDev.blockSize
val fullBlocks = (toWrite - incompleteBlockFullBytes) / blockDev.blockSize
// Check if we're trying to flush while the last written block isn't full
if (incompleteBlockFullBytes > 0) {
val incompleteBlockBuffer = ByteBuffer.allocate(blockDev.blockSize)
// Load last block from device
blockDev.read(currentBlockOffset + fullBlocks, incompleteBlockBuffer)
// Add it to the incomplete block
byteBuffer.limit(fullBlocks * blockDev.blockSize)
byteBuffer.put(
incompleteBlockBuffer.array(),
toWrite, blockDev.blockSize - incompleteBlockFullBytes
)
byteBuffer.position(0)
}
// Flush to device
blockDev.write(currentBlockOffset, byteBuffer)
// Copy the incomplete block at the beginning, then push back the position
byteBuffer.apply {
position(fullBlocks * blockDev.blockSize)
limit(toWrite)
compact()
clear()
position(incompleteBlockFullBytes)
}
currentBlockOffset += fullBlocks;
}
}