Backup of work in progress so I can pick it up later

This commit is contained in:
Davide Depau 2019-01-05 17:15:16 +01:00
parent e987268ad6
commit f130e3a1d4
Signed by: depau
GPG key ID: C7D999B6A55EFE86
24 changed files with 439 additions and 64 deletions

View file

@ -0,0 +1,19 @@
package eu.depau.etchdroid.ui.misc
import java.io.File
import java.io.FileDescriptor
import java.io.RandomAccessFile
class SparseFile : RandomAccessFile {
constructor(file: File, mode: String) : super(file, mode)
constructor(path: String, mode: String) : super(path, mode)
fun lseek(fd: FileDescriptor, offset: Long, whence: Int): Long {
val libcore = Class.forName("libcore.io.Libcore")
val os = libcore.getField("os").get(null)
val lseek = os.javaClass.getMethod(
"lseek", FileDescriptor::class.java, Long::class.java, Int::class.java
)
return lseek.invoke(null, fd, offset, whence) as Long
}
}

View file

@ -2,78 +2,21 @@ package eu.depau.etchdroid.utils.imagetypes
import android.content.Context
import android.net.Uri
import eu.depau.etchdroid.utils.Partition
import eu.depau.etchdroid.utils.PartitionBuilder
import eu.depau.etchdroid.utils.enums.FilesystemType
import eu.depau.etchdroid.utils.enums.PartitionTableType
import eu.depau.etchdroid.utils.ktexts.getBinary
import eu.depau.etchdroid.utils.ktexts.readString
import eu.depau.etchdroid.utils.Partition
import eu.depau.etchdroid.utils.PartitionBuilder
import eu.depau.etchdroid.utils.streams.AbstractSizedInputStream
import eu.depau.etchdroid.utils.streams.SizedInputStream
import java.io.File
val SECTOR_SIZE = 512
private val partRegex = Regex("partition (\\d+): begin=(\\d+), size=(\\d+), decoded=(\\d+), firstsector=(\\d+), sectorcount=(\\d+), blocksruncount=(\\d+)\\s+(.*) \\((.+) : \\d+\\)", RegexOption.MULTILINE)
private fun readPartitionTable(dmg2img: File, libDir: String, file: File): Triple<PartitionTableType?, List<Partition>?, Long?> {
val pt = ArrayList<Partition>()
var ptType: PartitionTableType? = null
var imgSize = 0L
val pb = ProcessBuilder(dmg2img.path, "-v", "-l", file.path)
pb.environment()["LD_LIBRARY_PATH"] = libDir
pb.redirectErrorStream(true)
val p = pb.start()
val out = p.inputStream.readString()
System.err.println(out)
p.waitFor()
val matches = partRegex.findAll(out)
matchloop@ for (m in matches) {
val (
number, begin, size,
decoded, firstsector,
sectorcount, blocksruncount,
label, type
) = m.destructured
val pb = PartitionBuilder()
pb.number = number.toInt()
pb.size = SECTOR_SIZE * sectorcount.toLong()
imgSize += pb.size!!
if (label.isNotEmpty())
pb.fsLabel = label
pb.partLabel = type
when (type) {
"Apple_partition_map" -> {
ptType = PartitionTableType.MAC
}
"MBR" -> {
ptType = PartitionTableType.MSDOS
continue@matchloop
}
}
pb.fsType = when {
type == "Apple_Empty" || type == "Apple_Scratch" || type == "Apple_Free" -> FilesystemType.FREE
type == "Apple_HFS" -> FilesystemType.HFSPLUS
type == "Apple_HFSX" -> FilesystemType.HFSPLUS
type == "Windows_FAT_32" -> FilesystemType.FAT32
type.startsWith("Apple_") || type == "DDM" -> FilesystemType.APT_DATA
else -> FilesystemType.UNKNOWN
}
pt.add(pb.build())
}
return Triple(ptType, pt, imgSize)
}
private val partitionListRegex = Regex("\\s*partition (\\d+): begin=(\\d+), size=(\\d+), decoded=(\\d+), firstsector=(\\d+), sectorcount=(\\d+), blocksruncount=(\\d+)\\s*")
class DMGImage(private val uri: Uri, private val context: Context) : Image {
private val dmg2img: File = context.getBinary("dmg2img")
private val libDir: String = context.applicationInfo.nativeLibraryDir
private var loaded: Boolean = false
private var partTable: List<Partition>? = null
@ -83,7 +26,7 @@ class DMGImage(private val uri: Uri, private val context: Context) : Image {
private fun readInfo() {
if (loaded)
return
val triple = readPartitionTable(dmg2img, libDir, File(uri.path))
val triple = readPartitionTable(context, libDir, File(uri.path))
loaded = true
partTableType = triple.first
partTable = triple.second
@ -109,4 +52,102 @@ class DMGImage(private val uri: Uri, private val context: Context) : Image {
return imgSize
}
override val inputStream: AbstractSizedInputStream
get() = getRawImageInputStream(context, uri)
companion object {
private fun getDmg2ImgProcessBuilder(context: Context, vararg args: String): ProcessBuilder =
ProcessBuilder(context.getBinary("dmg2img").path, *args)
.apply {
environment()["LD_LIBRARY_PATH"] = context.applicationInfo.nativeLibraryDir
}
private fun readPartitionTable(context: Context, libDir: String, file: File): Triple<PartitionTableType?, List<Partition>?, Long?> {
val pt = ArrayList<Partition>()
var ptType: PartitionTableType? = null
var imgSize = 0L
val pb = getDmg2ImgProcessBuilder(context, "-v", "-l", file.path)
pb.redirectErrorStream(true)
val p = pb.start()
val out = p.inputStream.readString()
System.err.println(out)
p.waitFor()
val matches = partRegex.findAll(out)
matchloop@ for (m in matches) {
val (
number, begin, size,
decoded, firstsector,
sectorcount, blocksruncount,
label, type
) = m.destructured
val pb = PartitionBuilder()
pb.number = number.toInt()
pb.size = SECTOR_SIZE * sectorcount.toLong()
imgSize += pb.size!!
if (label.isNotEmpty())
pb.fsLabel = label
pb.partLabel = type
when (type) {
"Apple_partition_map" -> {
ptType = PartitionTableType.MAC
}
"MBR" -> {
ptType = PartitionTableType.MSDOS
continue@matchloop
}
}
pb.fsType = when {
type == "Apple_Empty" || type == "Apple_Scratch" || type == "Apple_Free" -> FilesystemType.FREE
type == "Apple_HFS" -> FilesystemType.HFSPLUS
type == "Apple_HFSX" -> FilesystemType.HFSPLUS
type == "Windows_FAT_32" -> FilesystemType.FAT32
type.startsWith("Apple_") || type == "DDM" -> FilesystemType.APT_DATA
else -> FilesystemType.UNKNOWN
}
pt.add(pb.build())
}
return Triple(ptType, pt, imgSize)
}
fun getRawImageInputStream(
context: Context,
uri: Uri,
sectorSize: Int = 512
): AbstractSizedInputStream {
val pb = getDmg2ImgProcessBuilder(context, "-v", uri.path!!, "-")
val process = pb.start()
val errReader = process.errorStream.bufferedReader()
// Read blocksruncount
var matched = false
var lastSector = 0L
while (true) {
val line = errReader.readLine() ?: break
val match = partitionListRegex.find(line) ?: if (matched) break else continue
matched = true
val (begin, size, decoded, firstsector, sectorcount, blocksruncount) = match.destructured
val partLastSector = firstsector.toLong() + sectorcount.toLong()
if (partLastSector > lastSector)
lastSector = partLastSector
}
val bytesTotal = lastSector * sectorSize
return SizedInputStream(bytesTotal, process.inputStream)
}
}
}

View file

@ -1,10 +1,13 @@
package eu.depau.etchdroid.utils.imagetypes
import eu.depau.etchdroid.utils.enums.PartitionTableType
import android.content.Context
import eu.depau.etchdroid.utils.Partition
import eu.depau.etchdroid.utils.enums.PartitionTableType
import eu.depau.etchdroid.utils.streams.AbstractSizedInputStream
interface Image {
val partitionTable: List<Partition>?
val tableType: PartitionTableType?
val size: Long?
val inputStream: AbstractSizedInputStream
}

View file

@ -0,0 +1,16 @@
package eu.depau.etchdroid.utils.imagetypes
import eu.depau.etchdroid.utils.Partition
import eu.depau.etchdroid.utils.enums.PartitionTableType
import eu.depau.etchdroid.utils.streams.AbstractSizedInputStream
class RawImage: Image {
override val partitionTable: List<Partition>?
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val tableType: PartitionTableType?
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val size: Long?
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override val inputStream: AbstractSizedInputStream
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
}

View file

@ -0,0 +1,7 @@
package eu.depau.etchdroid.utils.streams
import java.io.InputStream
abstract class AbstractSizedInputStream: InputStream() {
abstract val size: Long
}

View file

@ -0,0 +1,7 @@
package eu.depau.etchdroid.utils.streams
import java.io.OutputStream
abstract class AbstractSizedOutputStream: OutputStream() {
abstract val size: Long
}

View file

@ -0,0 +1,48 @@
package eu.depau.etchdroid.utils.streams
import java.io.InputStream
/**
* This class simply wraps an existing InputStream adding some provided size field.
*/
class SizedInputStream(
override val size: Long,
private val inputStream: InputStream
) : AbstractSizedInputStream() {
override fun skip(n: Long): Long {
return inputStream.skip(n)
}
override fun available(): Int {
return inputStream.available()
}
override fun reset() {
inputStream.reset()
}
override fun close() {
inputStream.close()
}
override fun mark(readlimit: Int) {
inputStream.mark(readlimit)
}
override fun markSupported(): Boolean {
return inputStream.markSupported()
}
override fun read(): Int {
return inputStream.read()
}
override fun read(b: ByteArray?): Int {
return inputStream.read(b)
}
override fun read(b: ByteArray?, off: Int, len: Int): Int {
return inputStream.read(b, off, len)
}
}

View file

@ -0,0 +1,31 @@
package eu.depau.etchdroid.utils.streams
import java.io.OutputStream
/**
* This class simply wraps an existing OutputStream adding some provided size field.
*/
class SizedOutputStream(
override val size: Long,
private val outputStream: OutputStream
) : AbstractSizedOutputStream() {
override fun write(b: Int) {
outputStream.write(b)
}
override fun write(b: ByteArray?) {
outputStream.write(b)
}
override fun write(b: ByteArray?, off: Int, len: Int) {
outputStream.write(b, off, len)
}
override fun flush() {
outputStream.flush()
}
override fun close() {
outputStream.close()
}
}

View file

@ -0,0 +1,8 @@
package eu.depau.etchdroid.worker
abstract class AbstractAsyncWorker : IAsyncWorker, AbstractProgressSender() {
override suspend fun run() {
while (runStep()) {
}
}
}

View file

@ -0,0 +1,41 @@
package eu.depau.etchdroid.worker
import eu.depau.etchdroid.worker.dto.ProgressUpdateDTO
const val UPDATE_INTERVAL = 500
abstract class AbstractAutoProgressAsyncWorker(private val totalToDo: Long) : AbstractAsyncWorker() {
abstract val progressUpdateDTO: ProgressUpdateDTO
private var doneAccumulator = 0L
private var doneSinceLastUpdate = 0L
private var lastUpdateTime = 0L
fun progressUpdate(lastDone: Long) {
val currentTime = System.currentTimeMillis()
if (lastUpdateTime == 0L)
lastUpdateTime = currentTime
doneSinceLastUpdate += lastDone
doneAccumulator += lastDone
if (currentTime > lastUpdateTime + UPDATE_INTERVAL) {
val progress = doneAccumulator.toDouble() / totalToDo
val speedUnitPerMillis = doneSinceLastUpdate.toDouble() / (currentTime - lastUpdateTime)
val timeRemainingMillis: Long = ((totalToDo - doneAccumulator) / speedUnitPerMillis).toLong()
val dto = progressUpdateDTO.copy(
operationProgress = progress,
stepProgress = progress,
timeRemaining = timeRemainingMillis,
currentRate = speedUnitPerMillis * 1000
)
notifyProgress(dto)
doneSinceLastUpdate = 0
lastUpdateTime = currentTime
}
}
}

View file

@ -0,0 +1,24 @@
package eu.depau.etchdroid.worker
import eu.depau.etchdroid.worker.dto.ProgressDoneDTO
import eu.depau.etchdroid.worker.dto.ProgressStartDTO
import eu.depau.etchdroid.worker.dto.ProgressUpdateDTO
abstract class AbstractProgressSender : IProgressSender {
private val listeners = ArrayList<IProgressListener>()
override fun attachProgressListener(listener: IProgressListener) =
listeners.add(listener)
override fun detachProgressListener(listener: IProgressListener) =
listeners.remove(listener)
protected fun notifyStart(dto: ProgressStartDTO) =
listeners.forEach { it.notifyStart(dto) }
protected fun notifyProgress(dto: ProgressUpdateDTO) =
listeners.forEach { it.notifyProgress(dto) }
protected fun notifyDone(dto: ProgressDoneDTO) =
listeners.forEach { it.notifyDone(dto) }
}

View file

@ -0,0 +1,6 @@
package eu.depau.etchdroid.worker
interface IAsyncWorker: IProgressSender {
suspend fun run()
suspend fun runStep(): Boolean
}

View file

@ -0,0 +1,11 @@
package eu.depau.etchdroid.worker
import eu.depau.etchdroid.worker.dto.ProgressDoneDTO
import eu.depau.etchdroid.worker.dto.ProgressStartDTO
import eu.depau.etchdroid.worker.dto.ProgressUpdateDTO
interface IProgressListener {
fun notifyStart(dto: ProgressStartDTO)
fun notifyProgress(dto: ProgressUpdateDTO)
fun notifyDone(dto: ProgressDoneDTO)
}

View file

@ -0,0 +1,6 @@
package eu.depau.etchdroid.worker
interface IProgressSender {
fun attachProgressListener(listener: IProgressListener): Boolean
fun detachProgressListener(listener: IProgressListener): Boolean
}

View file

@ -0,0 +1,10 @@
package eu.depau.etchdroid.worker.dto
import android.os.Parcelable
import eu.depau.etchdroid.worker.enums.ErrorType
data class ProgressDoneDTO(
val operationId: Int,
val error: ErrorType?,
val errorData: Parcelable?
)

View file

@ -0,0 +1,13 @@
package eu.depau.etchdroid.worker.dto
import eu.depau.etchdroid.worker.enums.OperationType
import eu.depau.etchdroid.worker.enums.StepType
import java.util.*
data class ProgressStartDTO(
val operationId: Int,
val operationType: OperationType,
val inputName: String,
val outputName: String,
val steps: List<Pair<String, StepType>>
)

View file

@ -0,0 +1,13 @@
package eu.depau.etchdroid.worker.dto
import eu.depau.etchdroid.worker.enums.RateUnit
data class ProgressUpdateDTO(
val operationId: Int,
val currentStep: Int,
val operationProgress: Double,
val stepProgress: Double?,
val timeRemaining: Long?,
val currentRate: Double?,
val rateUnit: RateUnit?
)

View file

@ -0,0 +1,11 @@
package eu.depau.etchdroid.worker.enums
enum class ErrorType {
UNPLUGGED,
INPUT_FORMAT_ERROR,
ENOSPC_ON_USB,
ENOSPC_ON_DEVICE,
PERMISSION_DENIED_ANDROID,
PERMISSION_DENIED_FS,
PERMISSION_DENIED_USB
}

View file

@ -0,0 +1,9 @@
package eu.depau.etchdroid.worker.enums
enum class OperationType {
FLASH_RAW_IMAGE,
FLASH_DMG,
FLASH_ELTORITO,
DUMP_RAW_IMAGE,
CREATE_WINDOWS_INSTALLER
}

View file

@ -0,0 +1,6 @@
package eu.depau.etchdroid.worker.enums
enum class RateUnit {
BYTES_PER_SECOND,
FURLONGS_PER_FORTNIGHT
}

View file

@ -0,0 +1,8 @@
package eu.depau.etchdroid.worker.enums
enum class StepType {
STREAM_COPY,
FORMAT_DRIVE,
COPY_FILES,
INSTALL_BOOTLOADER
}

View file

@ -0,0 +1,6 @@
package eu.depau.etchdroid.worker.impl
import eu.depau.etchdroid.worker.AbstractAutoProgressAsyncWorker
class Dmg2OutputStreamConvertAsyncService: AbstractAutoProgressAsyncWorker() {
}

View file

@ -0,0 +1,30 @@
package eu.depau.etchdroid.worker.impl
import eu.depau.etchdroid.worker.AbstractAutoProgressAsyncWorker
import eu.depau.etchdroid.worker.dto.ProgressUpdateDTO
import java.io.InputStream
import java.io.OutputStream
open class Input2OutputStreamCopyAsyncWorker(
private val source: InputStream,
private val dest: OutputStream,
chunkSize: Int,
override val progressUpdateDTO: ProgressUpdateDTO,
size: Long
) : AbstractAutoProgressAsyncWorker(size) {
private val buffer = ByteArray(chunkSize)
override suspend fun runStep(): Boolean {
val readBytes = source.read(buffer)
if (readBytes < 0)
return false
dest.write(buffer, 0, readBytes)
progressUpdate(readBytes.toLong())
return true
}
}

View file

@ -1,6 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.0.0'
ext.kotlin_version = '1.3.11'
repositories {
jcenter()