Backup of work in progress so I can pick it up later
This commit is contained in:
parent
e987268ad6
commit
f130e3a1d4
24 changed files with 439 additions and 64 deletions
19
app/src/main/java/eu/depau/etchdroid/ui/misc/SparseFile.kt
Normal file
19
app/src/main/java/eu/depau/etchdroid/ui/misc/SparseFile.kt
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package eu.depau.etchdroid.utils.streams
|
||||
|
||||
import java.io.InputStream
|
||||
|
||||
abstract class AbstractSizedInputStream: InputStream() {
|
||||
abstract val size: Long
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package eu.depau.etchdroid.utils.streams
|
||||
|
||||
import java.io.OutputStream
|
||||
|
||||
abstract class AbstractSizedOutputStream: OutputStream() {
|
||||
abstract val size: Long
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package eu.depau.etchdroid.worker
|
||||
|
||||
abstract class AbstractAsyncWorker : IAsyncWorker, AbstractProgressSender() {
|
||||
override suspend fun run() {
|
||||
while (runStep()) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) }
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package eu.depau.etchdroid.worker
|
||||
|
||||
interface IAsyncWorker: IProgressSender {
|
||||
suspend fun run()
|
||||
suspend fun runStep(): Boolean
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package eu.depau.etchdroid.worker
|
||||
|
||||
interface IProgressSender {
|
||||
fun attachProgressListener(listener: IProgressListener): Boolean
|
||||
fun detachProgressListener(listener: IProgressListener): Boolean
|
||||
}
|
|
@ -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?
|
||||
)
|
|
@ -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>>
|
||||
)
|
|
@ -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?
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package eu.depau.etchdroid.worker.enums
|
||||
|
||||
enum class RateUnit {
|
||||
BYTES_PER_SECOND,
|
||||
FURLONGS_PER_FORTNIGHT
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package eu.depau.etchdroid.worker.enums
|
||||
|
||||
enum class StepType {
|
||||
STREAM_COPY,
|
||||
FORMAT_DRIVE,
|
||||
COPY_FILES,
|
||||
INSTALL_BOOTLOADER
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package eu.depau.etchdroid.worker.impl
|
||||
|
||||
import eu.depau.etchdroid.worker.AbstractAutoProgressAsyncWorker
|
||||
|
||||
class Dmg2OutputStreamConvertAsyncService: AbstractAutoProgressAsyncWorker() {
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue