Skip to content

Commit 5da2861

Browse files
committed
better downloads mvp (works but minor bugs in ui are present)
1 parent 7ec02e5 commit 5da2861

File tree

9 files changed

+99
-74
lines changed

9 files changed

+99
-74
lines changed

app/src/main/java/org/andbootmgr/app/BackupRestoreFlow.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ private fun SelectDroidBoot(c: CreateBackupDataHolder) {
9393
else -> ""
9494
}
9595
)
96-
val next =
9796
Button(onClick = {
9897
if (c.action != 1) {
9998
c.vm.activity.chooseFile("*/*") {

app/src/main/java/org/andbootmgr/app/CreatePartFlow.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ private fun Flash(c: CreatePartDataHolder) {
654654
val vm = c.vm
655655
Terminal(logFile = "install_${System.currentTimeMillis()}.txt") { terminal ->
656656
c.vm.logic.extractToolkit(terminal)
657+
c.vm.downloadRemainingFiles(terminal)
657658
if (c.partitionName == null) { // OS install
658659
val createdParts = mutableListOf<Pair<Part, Int>>() // order is important
659660
val fn = c.romFolderName

app/src/main/java/org/andbootmgr/app/DroidBootFlow.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ private fun Flash(d: DroidBootFlowDataHolder) {
186186
val vm = d.vm
187187
Terminal(logFile = "blflash_${System.currentTimeMillis()}.txt") { terminal ->
188188
vm.logic.extractToolkit(terminal)
189+
vm.downloadRemainingFiles(terminal)
189190
terminal.add(vm.activity.getString(R.string.term_preparing_fs))
190191
if (vm.logic.checkMounted()) {
191192
terminal.add(vm.activity.getString(R.string.term_mount_state_bad))

app/src/main/java/org/andbootmgr/app/FixDroidBootFlow.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ private fun Start(vm: WizardState) {
6060
private fun Flash(vm: WizardState) {
6161
Terminal(logFile = "blfix_${System.currentTimeMillis()}.txt") { terminal ->
6262
vm.logic.extractToolkit(terminal)
63+
vm.downloadRemainingFiles(terminal)
6364
val tmpFile = if (vm.deviceInfo.postInstallScript) {
6465
vm.chosen["_install.sh_"]!!.toFile(vm).also {
6566
it.setExecutable(true)

app/src/main/java/org/andbootmgr/app/UpdateDroidBootFlow.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ private fun Start(vm: WizardState) {
6060
private fun Flash(vm: WizardState) {
6161
Terminal(logFile = "blup_${System.currentTimeMillis()}.txt") { terminal ->
6262
vm.logic.extractToolkit(terminal)
63+
vm.downloadRemainingFiles(terminal)
6364
val tmpFile = if (vm.deviceInfo.postInstallScript) {
6465
vm.chosen["_install.sh_"]!!.toFile(vm).also {
6566
it.setExecutable(true)

app/src/main/java/org/andbootmgr/app/UpdateFlow.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ private fun Local(u: UpdateFlowDataHolder) {
209209
private fun Flash(u: UpdateFlowDataHolder) {
210210
Terminal(logFile = "update_${System.currentTimeMillis()}.txt") { terminal ->
211211
u.vm.logic.extractToolkit(terminal)
212+
u.vm.downloadRemainingFiles(terminal)
212213
val sp = u.e!!["xpart"]!!.split(":")
213214
val meta = SDUtils.generateMeta(u.vm.deviceInfo)!!
214215
Shell.cmd(SDUtils.umsd(meta)).exec()

app/src/main/java/org/andbootmgr/app/Wizard.kt

Lines changed: 41 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package org.andbootmgr.app
22

33
import android.net.Uri
4-
import android.util.Log
4+
import android.os.CancellationSignal
55
import android.view.WindowManager
6-
import android.widget.Toast
76
import androidx.activity.compose.BackHandler
87
import androidx.compose.foundation.layout.Arrangement
98
import androidx.compose.foundation.layout.Box
@@ -12,7 +11,6 @@ import androidx.compose.foundation.layout.Row
1211
import androidx.compose.foundation.layout.fillMaxSize
1312
import androidx.compose.foundation.layout.fillMaxWidth
1413
import androidx.compose.foundation.layout.padding
15-
import androidx.compose.material3.AlertDialog
1614
import androidx.compose.material3.Button
1715
import androidx.compose.material3.Card
1816
import androidx.compose.material3.Icon
@@ -38,18 +36,18 @@ import androidx.navigation.compose.NavHost
3836
import androidx.navigation.compose.composable
3937
import androidx.navigation.compose.rememberNavController
4038
import com.topjohnwu.superuser.io.SuFileOutputStream
41-
import kotlinx.coroutines.CoroutineScope
42-
import kotlinx.coroutines.Dispatchers
4339
import kotlinx.coroutines.launch
44-
import kotlinx.coroutines.withContext
4540
import org.andbootmgr.app.util.AbmOkHttp
46-
import org.andbootmgr.app.util.SOUtils
41+
import org.andbootmgr.app.util.TerminalCancelException
42+
import org.andbootmgr.app.util.TerminalList
4743
import java.io.File
4844
import java.io.FileInputStream
45+
import java.io.IOException
4946
import java.io.InputStream
5047
import java.io.OutputStream
5148
import java.nio.file.Files
5249
import java.nio.file.StandardCopyOption
50+
import java.util.concurrent.CancellationException
5351

5452
abstract class WizardFlow {
5553
abstract fun get(vm: WizardState): List<IWizardPage>
@@ -140,7 +138,42 @@ class WizardState(val mvm: MainActivityState) {
140138
throw IllegalStateException("invalid DledFile OR safFile failure")
141139
}
142140
}
143-
//suspend fun downloadRemainingFiles
141+
suspend fun downloadRemainingFiles(terminal: TerminalList) {
142+
terminal.isCancelled.value = false
143+
for (id in idNeeded.filter { !chosen.containsKey(it) }) {
144+
if (!inetAvailable.containsKey(id))
145+
throw IllegalStateException("$id not chosen and not available from inet")
146+
terminal.add(activity.getString(R.string.downloading_s, id))
147+
val inet = inetAvailable[id]!!
148+
val f = File(logic.cacheDir, System.currentTimeMillis().toString())
149+
terminal.add(activity.getString(R.string.connecting_text))
150+
val client = AbmOkHttp(inet.url, f, inet.hash) { readBytes, total, done ->
151+
terminal[terminal.size - 1] = if (done) activity.getString(R.string.done) else
152+
activity.getString(
153+
R.string.download_progress,
154+
"${readBytes / (1024 * 1024)} MiB", "${total / (1024 * 1024)} MiB"
155+
)
156+
}
157+
terminal.cancel = { terminal.isCancelled.value = true; client.cancel() }
158+
try {
159+
client.run()
160+
} catch (e: IOException) {
161+
if (terminal.isCancelled.value == true) {
162+
throw TerminalCancelException()
163+
}
164+
throw e
165+
}
166+
if (terminal.isCancelled.value == true) {
167+
throw TerminalCancelException()
168+
}
169+
chosen[id] = DownloadedFile(null, f)
170+
}
171+
if (terminal.isCancelled.value == true) {
172+
throw TerminalCancelException()
173+
} else {
174+
terminal.isCancelled.value = null
175+
}
176+
}
144177

145178
fun navigate(next: String) {
146179
prevText = null
@@ -224,21 +257,6 @@ fun WizardDownloader(vm: WizardState, next: String) {
224257
Text(stringResource(id = R.string.provide_images))
225258
}
226259
}
227-
var cancelDownload by remember { mutableStateOf<(() -> Unit)?>(null) }
228-
var progressText by remember { mutableStateOf(vm.activity.getString(R.string.connecting_text)) }
229-
if (cancelDownload != null) {
230-
AlertDialog(
231-
onDismissRequest = {},
232-
confirmButton = {
233-
Button(onClick = { cancelDownload!!() }) {
234-
Text(stringResource(id = R.string.cancel))
235-
}
236-
},
237-
title = { Text(stringResource(R.string.downloading)) },
238-
text = {
239-
LoadingCircle(progressText, paddingBetween = 10.dp)
240-
})
241-
}
242260
for (i in vm.idNeeded) {
243261
Row(
244262
verticalAlignment = Alignment.CenterVertically,
@@ -261,41 +279,6 @@ fun WizardDownloader(vm: WizardState, next: String) {
261279
Text(stringResource(R.string.undo))
262280
}
263281
} else {
264-
/*if (vm.inetAvailable.containsKey(i)) {
265-
Button(onClick = {
266-
CoroutineScope(Dispatchers.Main).launch {
267-
val url = vm.inetAvailable[i]!!.url
268-
val downloadedFile = File(vm.logic.cacheDir, i)
269-
val h = vm.inetAvailable[i]!!.hash
270-
val client = AbmOkHttp(url, downloadedFile, h) { bytesRead, contentLength, _ ->
271-
progressText = vm.activity.getString(R.string.download_progress,
272-
SOUtils.humanReadableByteCountBin(bytesRead), SOUtils.humanReadableByteCountBin(contentLength))
273-
}
274-
try {
275-
progressText = vm.activity.getString(R.string.connecting_text)
276-
cancelDownload = {
277-
client.cancel()
278-
cancelDownload = null
279-
}
280-
if (client.run()) {
281-
vm.chosen[i] = WizardState.DownloadedFile(null, downloadedFile)
282-
}
283-
} catch (e: Exception) {
284-
Log.e("ABM", Log.getStackTraceString(e))
285-
withContext(Dispatchers.Main) {
286-
Toast.makeText(
287-
vm.activity,
288-
vm.activity.getString(R.string.dl_error),
289-
Toast.LENGTH_LONG
290-
).show()
291-
}
292-
}
293-
cancelDownload = null
294-
}
295-
}) {
296-
Text(stringResource(R.string.download))
297-
}
298-
}*/
299282
Button(onClick = {
300283
vm.activity.chooseFile("*/*") {
301284
vm.chosen[i] = WizardState.DownloadedFile(it, null)

app/src/main/java/org/andbootmgr/app/util/Terminal.kt

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package org.andbootmgr.app.util
22

33
import android.util.Log
44
import androidx.compose.foundation.horizontalScroll
5+
import androidx.compose.foundation.layout.Column
56
import androidx.compose.foundation.layout.fillMaxSize
67
import androidx.compose.foundation.layout.padding
78
import androidx.compose.foundation.rememberScrollState
89
import androidx.compose.foundation.verticalScroll
10+
import androidx.compose.material3.Button
911
import androidx.compose.material3.Text
1012
import androidx.compose.runtime.Composable
1113
import androidx.compose.runtime.LaunchedEffect
14+
import androidx.compose.runtime.MutableState
1215
import androidx.compose.runtime.getValue
1316
import androidx.compose.runtime.mutableStateOf
1417
import androidx.compose.runtime.remember
@@ -18,6 +21,7 @@ import androidx.compose.runtime.setValue
1821
import androidx.compose.ui.Modifier
1922
import androidx.compose.ui.platform.LocalContext
2023
import androidx.compose.ui.platform.LocalLifecycleOwner
24+
import androidx.compose.ui.res.stringResource
2125
import androidx.compose.ui.text.font.FontFamily
2226
import androidx.compose.ui.unit.dp
2327
import kotlinx.coroutines.CoroutineScope
@@ -31,9 +35,12 @@ import java.io.File
3135
import java.io.FileOutputStream
3236

3337
private class BudgetCallbackList(private val scope: CoroutineScope,
34-
private val log: FileOutputStream?) : MutableList<String> {
38+
private val log: FileOutputStream?)
39+
: MutableList<String>, TerminalList {
40+
override val isCancelled = mutableStateOf<Boolean?>(null)
41+
override var cancel: (() -> Unit)? = null
3542
val internalList = ArrayList<String>()
36-
var cb: ((String) -> Unit)? = null
43+
var cb: (() -> Unit)? = null
3744
override val size: Int
3845
get() = internalList.size
3946

@@ -118,7 +125,9 @@ private class BudgetCallbackList(private val scope: CoroutineScope,
118125
}
119126

120127
override fun set(index: Int, element: String): String {
121-
return internalList.set(index, element)
128+
return internalList.set(index, element).also {
129+
cb?.invoke()
130+
}
122131
}
123132

124133
override fun subList(fromIndex: Int, toIndex: Int): MutableList<String> {
@@ -129,18 +138,26 @@ private class BudgetCallbackList(private val scope: CoroutineScope,
129138
scope.launch {
130139
log?.write((element + "\n").encodeToByteArray())
131140
}
132-
cb?.invoke(element)
141+
cb?.invoke()
133142
}
134143
}
135144

145+
interface TerminalList : MutableList<String> {
146+
val isCancelled: MutableState<Boolean?>
147+
var cancel: (() -> Unit)?
148+
}
149+
class TerminalCancelException : RuntimeException()
150+
136151
/* Monospace auto-scrolling text view, fed using MutableList<String>, catching exceptions and running logic on a different thread */
137152
@OptIn(ExperimentalCoroutinesApi::class)
138153
@Composable
139154
fun Terminal(logFile: String? = null, doWhenDone: (() -> Unit)? = null,
140-
action: (suspend (MutableList<String>) -> Unit)?) {
155+
action: (suspend (TerminalList) -> Unit)?) {
141156
val scrollH = rememberScrollState()
142157
val scrollV = rememberScrollState()
143-
val scope = rememberCoroutineScope()
158+
val scope = rememberCoroutineScope { Dispatchers.Main }
159+
var isCancelledState by remember { mutableStateOf(mutableStateOf<Boolean?>(null)) }
160+
var doCancelState by remember { mutableStateOf<(() -> Unit)?>(null) }
144161
var didConnectAndFinish by rememberSaveable { mutableStateOf(false) }
145162
var text by rememberSaveable { mutableStateOf("") }
146163
val ctx = LocalContext.current.applicationContext
@@ -161,9 +178,12 @@ fun Terminal(logFile: String? = null, doWhenDone: (() -> Unit)? = null,
161178
val logDispatcher = Dispatchers.IO.limitedParallelism(1)
162179
val log = logFile?.let { FileOutputStream(File(ctx.externalCacheDir, it)) }
163180
val s = BudgetCallbackList(CoroutineScope(logDispatcher), log)
164-
s.cb = { element ->
181+
isCancelledState = s.isCancelled
182+
doCancelState = { s.cancel!!() }
183+
s.cb = {
184+
val l = s.toList()
165185
scope.launch {
166-
text += element + "\n"
186+
text = l.joinToString("\n").let { if (s.isNotEmpty()) it + "\n" else it }
167187
delay(200) // Give it time to re-measure
168188
scrollV.animateScrollTo(scrollV.maxValue)
169189
scrollH.animateScrollTo(0)
@@ -173,6 +193,8 @@ fun Terminal(logFile: String? = null, doWhenDone: (() -> Unit)? = null,
173193
withContext(Dispatchers.Default) {
174194
try {
175195
action(s)
196+
} catch (e: TerminalCancelException) {
197+
s.add(ctx.getString(R.string.install_canceled))
176198
} catch (e: Throwable) {
177199
s.add(ctx.getString(R.string.term_failure))
178200
s.add(ctx.getString(R.string.dev_details))
@@ -185,22 +207,37 @@ fun Terminal(logFile: String? = null, doWhenDone: (() -> Unit)? = null,
185207
}, s)
186208
} else {
187209
val s = service.workExtra as BudgetCallbackList
210+
isCancelledState = s.isCancelled
211+
doCancelState = { s.cancel!!() }
188212
text = s.joinToString("\n").let { if (s.isNotEmpty()) it + "\n" else it }
189-
s.cb = { element ->
213+
s.cb = {
214+
val l = s.toList()
190215
scope.launch {
191-
text += element + "\n"
216+
text = l.joinToString("\n").let { if (s.isNotEmpty()) it + "\n" else it }
192217
delay(200) // Give it time to re-measure
193218
scrollV.animateScrollTo(scrollV.maxValue)
194219
scrollH.animateScrollTo(0)
195220
}
196221
}
222+
197223
}
198224
}
199225
}
200226
}
201-
Text(text, modifier = Modifier
202-
.fillMaxSize()
203-
.horizontalScroll(scrollH)
204-
.verticalScroll(scrollV)
205-
.padding(10.dp), fontFamily = FontFamily.Monospace)
227+
Column(modifier = Modifier.fillMaxSize()) {
228+
Text(text, modifier = Modifier
229+
.fillMaxSize()
230+
.weight(1f)
231+
.horizontalScroll(scrollH)
232+
.verticalScroll(scrollV)
233+
.padding(10.dp), fontFamily = FontFamily.Monospace
234+
)
235+
if (isCancelledState.value == false) {
236+
Button({
237+
doCancelState?.invoke()
238+
}) {
239+
Text(stringResource(R.string.cancel))
240+
}
241+
}
242+
}
206243
}

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
<string name="install">Install</string>
5555
<string name="connecting_text">(Connecting…)</string>
5656
<string name="downloading">Downloading…</string>
57+
<string name="downloading_s">Downloading %s…</string>
5758
<string name="provide_images">Please now provide images for all required IDs. You can use the recommended ones using the \"Download\" button!</string>
5859
<string name="user_selected">User-selected</string>
5960
<string name="undo">Undo</string>

0 commit comments

Comments
 (0)