Add block device Input/Output Stream wrappers
This commit is contained in:
parent
53c21a814e
commit
9c68672310
2 changed files with 201 additions and 0 deletions
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue