diff --git a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt index ac8dde46..d4f96c31 100644 --- a/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt @@ -22,7 +22,7 @@ import org.andbootmgr.app.util.SDUtils import java.io.File import java.io.IOException -class BackupRestoreFlow(private val partitionId: Int, private val partFile: File?): WizardFlow() { +class BackupRestoreFlow(private val partitionId: Int?, private val partFile: File?): WizardFlow() { override fun get(vm: WizardState): List { val c = CreateBackupDataHolder(vm, partitionId, partFile) return listOf(WizardPage("start", diff --git a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt index 7a34e41f..de3bacd2 100644 --- a/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt @@ -812,7 +812,6 @@ private fun Flash(c: CreatePartDataHolder) { val id = i++ val img = File(imgFolder, id.toString()) val map = File(entryFolder, "$id.map") - val mappedName = "abm_${fn}_$id" val bytes = part.resolveBytesSize(c, space) space -= bytes if (space < 0) @@ -825,14 +824,11 @@ private fun Flash(c: CreatePartDataHolder) { terminal.add(vm.activity.getString(R.string.term_failed_uncrypt)) return@WizardTerminalWork } - if (!SDLessUtils.unmap(vm.logic, mappedName, false, terminal)) - throw IllegalStateException("failed to unmap $mappedName which shouldn't even exist?") - if (!SDLessUtils.map(vm.logic, mappedName, map, terminal)) { + if (!vm.logic.map(fn, id, terminal)) { terminal.add(vm.activity.getString(R.string.term_failed_map_other)) return@WizardTerminalWork } - val mapped = File(vm.logic.dmBase, mappedName) - createdParts.add(part to (i to mapped)) + createdParts.add(part to (i to vm.logic.getDmFile(fn, id))) terminal.add(vm.activity.getString(R.string.term_created_part)) } } diff --git a/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt b/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt index 694546b0..1a043daa 100644 --- a/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt +++ b/app/src/main/java/org/andbootmgr/app/DeviceLogic.kt @@ -77,12 +77,23 @@ class DeviceLogic(private val ctx: Context) { } return mounted } - fun mapBootset(): Boolean { - return SDLessUtils.map(this, dmName, metadataMap) + fun mapBootset(terminal: MutableList? = null): Boolean { + return SDLessUtils.map(this, dmName, metadataMap, terminal) } private fun unmapBootset(): Boolean { return SDLessUtils.unmap(this, dmName, true) } + fun getDmName(fn: String, id: Int) = "abm_${fn}_$id" + fun getDmFile(fn: String, id: Int) = File(dmBase, getDmName(fn, id)) + fun map(fn: String, id: Int, terminal: MutableList? = null): Boolean { + if (!unmap(fn, id, terminal)) + throw IllegalStateException("failed to unmap part which shouldn't even exist?") + val map = File(File(abmBootset, fn), "$id.map") + return SDLessUtils.map(this, getDmName(fn, id), map, terminal) + } + fun unmap(fn: String, id: Int, terminal: MutableList? = null): Boolean { + return SDLessUtils.unmap(this, getDmName(fn, id), false, terminal) + } fun mount(p: SDUtils.Partition): Shell.Job { return Shell.cmd(p.mount()) } diff --git a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt index 7920503f..9e40bb95 100644 --- a/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt @@ -274,11 +274,11 @@ private fun Flash(d: DroidBootFlowDataHolder) { terminal.add(vm.activity.getString(R.string.term_failed_unmap)) return@WizardTerminalWork } - if (!SDLessUtils.map(vm.logic, vm.logic.dmName, vm.logic.metadataMap, terminal)) { + if (!vm.logic.mapBootset(terminal)) { terminal.add(vm.activity.getString(R.string.term_failed_map)) return@WizardTerminalWork } - if (!Shell.cmd("mkfs.ext4 ${vm.logic.dmName}").to(terminal).exec().isSuccess) { + if (!Shell.cmd("mkfs.ext4 $ast").to(terminal).exec().isSuccess) { terminal.add(vm.activity.getString(R.string.term_failed_bootset_mkfs)) return@WizardTerminalWork } diff --git a/app/src/main/java/org/andbootmgr/app/Start.kt b/app/src/main/java/org/andbootmgr/app/Start.kt index e88f06dd..c33b03b4 100644 --- a/app/src/main/java/org/andbootmgr/app/Start.kt +++ b/app/src/main/java/org/andbootmgr/app/Start.kt @@ -17,6 +17,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -45,7 +46,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.andbootmgr.app.util.ConfigFile +import org.andbootmgr.app.util.SDLessUtils import org.andbootmgr.app.util.SDUtils +import org.andbootmgr.app.util.SOUtils import java.io.File import kotlin.collections.set import kotlin.io.nameWithoutExtension @@ -363,6 +366,7 @@ private fun OsEditor(vm: MainActivityState, parts: SDUtils.SDPartitionMeta?, e: onClose: (newPt: Boolean) -> Unit, onOpenUpdater: () -> Unit) { var processing by remember { mutableStateOf(false) } var delete by remember { mutableStateOf(false) } + var backupAskPart by remember { mutableStateOf(false) } var result by remember { mutableStateOf Unit)?>?>(null) } AlertDialog( onDismissRequest = { @@ -380,8 +384,11 @@ private fun OsEditor(vm: MainActivityState, parts: SDUtils.SDPartitionMeta?, e: enabled = e.has("xupdate") && !e["xupdate"].isNullOrBlank()) { Text(stringResource(R.string.update)) } - // TODO add button to open backup & restore tool (by asking which partition should - // be backed up / restored) + Button(onClick = { + backupAskPart = true + }, enabled = e.has("xparts") && !e["xparts"].isNullOrBlank() && e["xparts"] != "real") { + Text(stringResource(R.string.backupnrestore)) + } Button( onClick = { delete = true @@ -400,6 +407,45 @@ private fun OsEditor(vm: MainActivityState, parts: SDUtils.SDPartitionMeta?, e: } ) + if (backupAskPart) { + AlertDialog( + onDismissRequest = { + backupAskPart = false + }, + title = { + Text(stringResource(R.string.choose_part_to_backup)) + }, + text = { + for (i in e["xparts"]!!.split(":")) { + val part = parts?.dumpKernelPartition(i.toInt()) + val file = if (!vm.deviceInfo!!.metaonsd) + File(File(vm.logic!!.abmSdLessBootset, f.nameWithoutExtension), i) else null + val size = part?.let { it.size * it.meta.logicalSectorSizeBytes } + ?: SuFile.open(file!!.toURI()).length() + ListItem(headlineContent = { + Text(stringResource(R.string.entry_space_usage, + if (part != null) + stringResource(R.string.part_item, i, part.name) + else + stringResource(R.string.part_title, i), + SOUtils.humanReadableByteCountBin(size) + )) + }, modifier = Modifier.clickable { + vm.currentWizardFlow = if (file != null) + BackupRestoreFlow(null, file) + else + BackupRestoreFlow(i.toInt(), null) + }) + } + }, + confirmButton = { + Button(onClick = { backupAskPart = false }) { + Text(stringResource(id = R.string.cancel)) + } + } + ) + } + if (delete) { AlertDialog( onDismissRequest = { @@ -724,6 +770,9 @@ private fun EntryEditor(vm: MainActivityState, e: ConfigFile, f: File?, onClose: val initrdE by remember { derivedStateOf { !initrdT.matches(asciiRegex) } } var dtbT by remember { mutableStateOf(e["dtb"] ?: "") } val dtbE by remember { derivedStateOf { !dtbT.matches(asciiRegex) } } + var dtboT by remember { mutableStateOf(e["dtbo"] ?: "") } + val dtboE by remember { derivedStateOf { if (vm.deviceInfo!!.havedtbo) + !dtboT.matches(asciiRegex) else false } } var optionsT by remember { mutableStateOf(e["options"] ?: "") } val optionsE by remember { derivedStateOf { !optionsT.matches(asciiRegex) } } var xtypeT by remember { mutableStateOf(e["xtype"] ?: "") } @@ -731,8 +780,7 @@ private fun EntryEditor(vm: MainActivityState, e: ConfigFile, f: File?, onClose: var xpartT by remember { mutableStateOf(e["xpart"] ?: "") } val xpartE by remember { derivedStateOf { !xpartT.matches(xpartValidValues) } } var xupdateT by remember { mutableStateOf(e["xupdate"] ?: "") } - // TODO dtbo editing if havedtbo - val isOk = !(newFileNameErr || titleE || linuxE || initrdE || dtbE || optionsE || xtypeE || xpartE) + val isOk = !(newFileNameErr || titleE || linuxE || initrdE || dtbE || dtboE || optionsE || xtypeE || xpartE) AlertDialog( onDismissRequest = { onClose() @@ -776,6 +824,13 @@ private fun EntryEditor(vm: MainActivityState, e: ConfigFile, f: File?, onClose: Text(stringResource(R.string.dtb)) }) + if (vm.deviceInfo!!.havedtbo) + TextField(value = dtbT, onValueChange = { + dtbT = it + }, isError = dtbE, label = { + Text(stringResource(R.string.dtbo)) + }) + TextField(value = optionsT, onValueChange = { optionsT = it }, isError = optionsE, label = { @@ -828,6 +883,8 @@ private fun EntryEditor(vm: MainActivityState, e: ConfigFile, f: File?, onClose: e["linux"] = linuxT e["initrd"] = initrdT e["dtb"] = dtbT + if (vm.deviceInfo!!.havedtbo) + e["dtbo"] = dtboT e["options"] = optionsT e["xtype"] = xtypeT e["xpart"] = xpartT @@ -879,7 +936,8 @@ private fun BootsetTool(vm: MainActivityState) { MyInfoCard(stringResource(R.string.click2inspect), padding = 5.dp) if (entries != null) { for (e in entries!!.keys) { - val spaceUsage = null // TODO compute space usage of installed OS + val spaceUsage = SDLessUtils.getSpaceUsageBytes(vm.logic!!, + entries!![e]!!.nameWithoutExtension) Row(horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -926,7 +984,6 @@ private fun BootsetTool(vm: MainActivityState) { } } } - // TODO we eventually want portable partitions for !metaonsd, but not supported yet if (editEntryID != null && filterEntryView) { EntryEditor( vm, editEntryID!!, entries!![editEntryID!!], diff --git a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt index 92720b61..d382d6be 100644 --- a/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt +++ b/app/src/main/java/org/andbootmgr/app/UpdateFlow.kt @@ -27,6 +27,7 @@ import com.topjohnwu.superuser.io.SuFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.andbootmgr.app.util.ConfigFile +import org.andbootmgr.app.util.SDLessUtils import org.andbootmgr.app.util.SDUtils import org.json.JSONObject import org.json.JSONTokener @@ -210,8 +211,9 @@ private fun Flash(u: UpdateFlowDataHolder) { u.vm.logic.extractToolkit(terminal) u.vm.downloadRemainingFiles(terminal) val sp = u.e!!["xpart"]!!.split(":") - val meta = SDUtils.generateMeta(u.vm.deviceInfo.asMetaOnSdDeviceInfo())!! // TODO !metaonsd - Shell.cmd(SDUtils.umsd(meta)).exec() + val meta = if (u.vm.deviceInfo.metaonsd) + SDUtils.generateMeta(u.vm.deviceInfo.asMetaOnSdDeviceInfo())!! else null + meta?.let { Shell.cmd(SDUtils.umsd(it)).exec() } val tmpFile = if (u.vm.idNeeded.contains("_install.sh_")) { u.vm.chosen["_install.sh_"]!!.toFile(u.vm).also { it.setExecutable(true) @@ -221,7 +223,13 @@ private fun Flash(u: UpdateFlowDataHolder) { val physicalId = sp[p.toInt()].toInt() terminal.add(u.vm.activity.getString(R.string.term_flashing_p, p)) val f2 = u.vm.chosen["part$p"]!! - val tp = File(meta.dumpKernelPartition(physicalId).path) + val tp = if (u.vm.deviceInfo.metaonsd) + File(meta!!.dumpKernelPartition(physicalId).path) + else { + if (!u.vm.logic.map(u.ef!!.nameWithoutExtension, physicalId, terminal)) + throw IllegalStateException("failed to map $physicalId") + u.vm.logic.getDmFile(u.ef!!.nameWithoutExtension, physicalId) + } if (u.sparse.contains(p.toInt())) { val result2 = Shell.cmd( File( diff --git a/app/src/main/java/org/andbootmgr/app/util/SDLessUtils.kt b/app/src/main/java/org/andbootmgr/app/util/SDLessUtils.kt index 31276b32..713be593 100644 --- a/app/src/main/java/org/andbootmgr/app/util/SDLessUtils.kt +++ b/app/src/main/java/org/andbootmgr/app/util/SDLessUtils.kt @@ -10,6 +10,12 @@ object SDLessUtils { return 4L * 1024L * 1024L * 1024L // TODO } + fun getSpaceUsageBytes(logic: DeviceLogic, fn: String): Long? { + return SuFile.open(logic.abmSdLessBootset, fn).listFiles()?.let { + it.sumOf { it.length() } + } + } + fun map(logic: DeviceLogic, name: String, mapFile: File, terminal: MutableList? = null): Boolean { val dmPath = File(logic.dmBase, name) if (SuFile.open(dmPath.toURI()).exists()) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c84a75b5..2e148251 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -273,4 +273,6 @@ -- failed to create file system in bootset -- failed to unmap bootset.img You need to install the bootloader manually first. Please check the ABM wiki for more infos. + Dtbo + Choose partition to backup