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.content.Context
import android.net.Uri 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.FilesystemType
import eu.depau.etchdroid.utils.enums.PartitionTableType import eu.depau.etchdroid.utils.enums.PartitionTableType
import eu.depau.etchdroid.utils.ktexts.getBinary import eu.depau.etchdroid.utils.ktexts.getBinary
import eu.depau.etchdroid.utils.ktexts.readString import eu.depau.etchdroid.utils.ktexts.readString
import eu.depau.etchdroid.utils.Partition import eu.depau.etchdroid.utils.streams.AbstractSizedInputStream
import eu.depau.etchdroid.utils.PartitionBuilder import eu.depau.etchdroid.utils.streams.SizedInputStream
import java.io.File import java.io.File
val SECTOR_SIZE = 512 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 val partRegex = Regex("partition (\\d+): begin=(\\d+), size=(\\d+), decoded=(\\d+), firstsector=(\\d+), sectorcount=(\\d+), blocksruncount=(\\d+)\\s+(.*) \\((.+) : \\d+\\)", RegexOption.MULTILINE)
private val partitionListRegex = Regex("\\s*partition (\\d+): begin=(\\d+), size=(\\d+), decoded=(\\d+), firstsector=(\\d+), sectorcount=(\\d+), blocksruncount=(\\d+)\\s*")
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)
}
class DMGImage(private val uri: Uri, private val context: Context) : Image { 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 val libDir: String = context.applicationInfo.nativeLibraryDir
private var loaded: Boolean = false private var loaded: Boolean = false
private var partTable: List<Partition>? = null private var partTable: List<Partition>? = null
@ -83,7 +26,7 @@ class DMGImage(private val uri: Uri, private val context: Context) : Image {
private fun readInfo() { private fun readInfo() {
if (loaded) if (loaded)
return return
val triple = readPartitionTable(dmg2img, libDir, File(uri.path)) val triple = readPartitionTable(context, libDir, File(uri.path))
loaded = true loaded = true
partTableType = triple.first partTableType = triple.first
partTable = triple.second partTable = triple.second
@ -109,4 +52,102 @@ class DMGImage(private val uri: Uri, private val context: Context) : Image {
return imgSize 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 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.Partition
import eu.depau.etchdroid.utils.enums.PartitionTableType
import eu.depau.etchdroid.utils.streams.AbstractSizedInputStream
interface Image { interface Image {
val partitionTable: List<Partition>? val partitionTable: List<Partition>?
val tableType: PartitionTableType? val tableType: PartitionTableType?
val size: Long? 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. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.0.0'
ext.kotlin_version = '1.3.11' ext.kotlin_version = '1.3.11'
repositories { repositories {
jcenter() jcenter()