-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: rewrite install steps & steps UI (#63)
- Loading branch information
1 parent
a53e21f
commit 09e1ca2
Showing
40 changed files
with
1,421 additions
and
685 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
app/src/main/kotlin/com/aliucord/manager/installer/steps/KotlinInstallRunner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.aliucord.manager.installer.steps | ||
|
||
import com.aliucord.manager.installer.steps.base.Step | ||
import com.aliucord.manager.installer.steps.download.* | ||
import com.aliucord.manager.installer.steps.install.* | ||
import com.aliucord.manager.installer.steps.patch.* | ||
import com.aliucord.manager.installer.steps.prepare.DowngradeCheckStep | ||
import com.aliucord.manager.installer.steps.prepare.FetchInfoStep | ||
import kotlinx.collections.immutable.persistentListOf | ||
|
||
/** | ||
* Used for installing the old Kotlin Discord app. | ||
*/ | ||
class KotlinInstallRunner : StepRunner() { | ||
override val steps = persistentListOf<Step>( | ||
// Prepare | ||
FetchInfoStep(), | ||
DowngradeCheckStep(), | ||
|
||
// Download | ||
DownloadDiscordStep(), | ||
DownloadInjectorStep(), | ||
DownloadAliuhookStep(), | ||
DownloadKotlinStep(), | ||
|
||
// Patch | ||
CopyDependenciesStep(), | ||
ReplaceIconStep(), | ||
PatchManifestStep(), | ||
AddInjectorStep(), | ||
AddAliuhookStep(), | ||
|
||
// Install | ||
AlignmentStep(), | ||
SigningStep(), | ||
InstallStep(), | ||
CleanupStep(), | ||
) | ||
} |
23 changes: 23 additions & 0 deletions
23
app/src/main/kotlin/com/aliucord/manager/installer/steps/StepGroup.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.aliucord.manager.installer.steps | ||
|
||
import androidx.annotation.StringRes | ||
import androidx.compose.runtime.Immutable | ||
import com.aliucord.manager.R | ||
|
||
/** | ||
* A group of steps that is shown under one section in the install UI. | ||
* This has no functional impact. | ||
*/ | ||
@Immutable | ||
enum class StepGroup( | ||
/** | ||
* The UI name to display this group as | ||
*/ | ||
@get:StringRes | ||
val localizedName: Int, | ||
) { | ||
Prepare(R.string.install_group_prepare), | ||
Download(R.string.install_group_download), | ||
Patch(R.string.install_group_patch), | ||
Install(R.string.install_group_install) | ||
} |
52 changes: 52 additions & 0 deletions
52
app/src/main/kotlin/com/aliucord/manager/installer/steps/StepRunner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.aliucord.manager.installer.steps | ||
|
||
import com.aliucord.manager.installer.steps.base.Step | ||
import com.aliucord.manager.manager.PreferencesManager | ||
import kotlinx.collections.immutable.ImmutableList | ||
import kotlinx.coroutines.delay | ||
import org.koin.core.component.KoinComponent | ||
import org.koin.core.component.inject | ||
|
||
/** | ||
* The minimum time that is required to occur between step switches, to avoid | ||
* quickly switching the step groups in the UI. (very disorienting) | ||
* Larger delay leads to a perception that it's doing more work than it actually is. | ||
*/ | ||
const val MINIMUM_STEP_DELAY: Long = 600L | ||
|
||
abstract class StepRunner : KoinComponent { | ||
private val preferences: PreferencesManager by inject() | ||
|
||
abstract val steps: ImmutableList<Step> | ||
|
||
/** | ||
* Get a step that has already been successfully executed. | ||
* This is used to retrieve previously executed dependency steps from a later step. | ||
*/ | ||
inline fun <reified T : Step> getStep(): T { | ||
val step = steps.asSequence() | ||
.filterIsInstance<T>() | ||
.filter { it.state.isFinished } | ||
.firstOrNull() | ||
|
||
if (step == null) { | ||
throw IllegalArgumentException("No completed step ${T::class.simpleName} exists in container") | ||
} | ||
|
||
return step | ||
} | ||
|
||
suspend fun executeAll(): Throwable? { | ||
for (step in steps) { | ||
val error = step.executeCatching(this@StepRunner) | ||
if (error != null) return error | ||
|
||
// Skip minimum run time when in dev mode | ||
if (!preferences.devMode && step.durationMs < MINIMUM_STEP_DELAY) { | ||
delay(MINIMUM_STEP_DELAY - step.durationMs) | ||
} | ||
} | ||
|
||
return null | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
app/src/main/kotlin/com/aliucord/manager/installer/steps/base/DownloadStep.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package com.aliucord.manager.installer.steps.base | ||
|
||
import android.content.Context | ||
import androidx.compose.runtime.Stable | ||
import com.aliucord.manager.R | ||
import com.aliucord.manager.installer.steps.StepGroup | ||
import com.aliucord.manager.installer.steps.StepRunner | ||
import com.aliucord.manager.manager.DownloadManager | ||
import com.aliucord.manager.util.showToast | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import org.koin.core.component.KoinComponent | ||
import org.koin.core.component.inject | ||
import java.io.File | ||
|
||
@Stable | ||
abstract class DownloadStep : Step(), KoinComponent { | ||
private val context: Context by inject() | ||
private val downloads: DownloadManager by inject() | ||
|
||
/** | ||
* The remote url to download | ||
*/ | ||
abstract val targetUrl: String | ||
|
||
/** | ||
* Target path to store the download in. If this file already exists, | ||
* then the cached version is used and the step is marked as cancelled/skipped. | ||
*/ | ||
abstract val targetFile: File | ||
|
||
/** | ||
* Verify that the download completely successfully without errors. | ||
* @throws Throwable If verification fails. | ||
*/ | ||
open suspend fun verify() { | ||
if (!targetFile.exists()) | ||
throw Error("Downloaded file is missing!") | ||
|
||
if (targetFile.length() <= 0) | ||
throw Error("Downloaded file is empty!") | ||
} | ||
|
||
override val group = StepGroup.Download | ||
|
||
override suspend fun execute(container: StepRunner) { | ||
if (targetFile.exists()) { | ||
if (targetFile.length() > 0) { | ||
state = StepState.Skipped | ||
return | ||
} | ||
|
||
targetFile.delete() | ||
} | ||
|
||
val result = downloads.download(targetUrl, targetFile) { newProgress -> | ||
progress = newProgress ?: -1f | ||
} | ||
|
||
when (result) { | ||
is DownloadManager.Result.Success -> { | ||
try { | ||
verify() | ||
} catch (t: Throwable) { | ||
withContext(Dispatchers.Main) { | ||
context.showToast(R.string.installer_dl_verify_fail) | ||
} | ||
|
||
throw t | ||
} | ||
} | ||
|
||
is DownloadManager.Result.Error -> { | ||
withContext(Dispatchers.Main) { | ||
context.showToast(result.localizedReason) | ||
} | ||
|
||
throw Error("Failed to download: ${result.debugReason}") | ||
} | ||
|
||
is DownloadManager.Result.Cancelled -> | ||
state = StepState.Error | ||
} | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
app/src/main/kotlin/com/aliucord/manager/installer/steps/base/Step.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.aliucord.manager.installer.steps.base | ||
|
||
import androidx.annotation.StringRes | ||
import androidx.compose.runtime.* | ||
import com.aliucord.manager.installer.steps.StepGroup | ||
import com.aliucord.manager.installer.steps.StepRunner | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import org.koin.core.time.measureTimedValue | ||
import kotlin.math.roundToInt | ||
|
||
/** | ||
* A base install process step. Steps are single-use | ||
*/ | ||
@Stable | ||
abstract class Step { | ||
/** | ||
* The group this step belongs to. | ||
*/ | ||
abstract val group: StepGroup | ||
|
||
/** | ||
* The UI name to display this step as | ||
*/ | ||
@get:StringRes | ||
abstract val localizedName: Int | ||
|
||
/** | ||
* Run the step's logic. | ||
* It can be assumed that this is executed in the correct order after other steps. | ||
*/ | ||
protected abstract suspend fun execute(container: StepRunner) | ||
|
||
/** | ||
* The current state of this step in the installation process. | ||
*/ | ||
var state by mutableStateOf(StepState.Pending) | ||
protected set | ||
|
||
/** | ||
* If the current state is [StepState.Running], then the progress of this step. | ||
* If the progress isn't currently measurable, then this should be set to `-1`. | ||
*/ | ||
var progress by mutableFloatStateOf(-1f) | ||
protected set | ||
|
||
/** | ||
* The total execution time once this step has finished execution. | ||
*/ | ||
// TODO: make this a live value | ||
var durationMs by mutableIntStateOf(0) | ||
private set | ||
|
||
/** | ||
* Thin wrapper over [execute] but handling errors. | ||
* @return An exception if the step failed to execute. | ||
*/ | ||
suspend fun executeCatching(container: StepRunner): Throwable? { | ||
if (state != StepState.Pending) | ||
throw IllegalStateException("Cannot execute a step that has already started") | ||
|
||
state = StepState.Running | ||
|
||
// Execute this steps logic while timing it | ||
val (error, executionTimeMs) = measureTimedValue { | ||
try { | ||
withContext(Dispatchers.Default) { | ||
execute(container) | ||
} | ||
|
||
if (state != StepState.Skipped) | ||
state = StepState.Success | ||
|
||
null | ||
} catch (t: Throwable) { | ||
state = StepState.Error | ||
t | ||
} | ||
} | ||
|
||
durationMs = executionTimeMs.roundToInt() | ||
return error | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
app/src/main/kotlin/com/aliucord/manager/installer/steps/base/StepState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.aliucord.manager.installer.steps.base | ||
|
||
enum class StepState { | ||
Pending, | ||
Running, | ||
Success, | ||
Error, | ||
Skipped; | ||
|
||
val isFinished: Boolean | ||
get() = this == Success || this == Error || this == Skipped | ||
} |
36 changes: 36 additions & 0 deletions
36
app/src/main/kotlin/com/aliucord/manager/installer/steps/download/DownloadAliuhookStep.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.aliucord.manager.installer.steps.download | ||
|
||
import androidx.compose.runtime.Stable | ||
import com.aliucord.manager.R | ||
import com.aliucord.manager.domain.repository.AliucordMavenRepository | ||
import com.aliucord.manager.installer.steps.StepRunner | ||
import com.aliucord.manager.installer.steps.base.DownloadStep | ||
import com.aliucord.manager.manager.PathManager | ||
import com.aliucord.manager.network.utils.getOrThrow | ||
import org.koin.core.component.KoinComponent | ||
import org.koin.core.component.inject | ||
|
||
/** | ||
* Download a packaged AAR of the latest Aliuhook build from the Aliucord maven. | ||
*/ | ||
@Stable | ||
class DownloadAliuhookStep : DownloadStep(), KoinComponent { | ||
private val paths: PathManager by inject() | ||
private val maven: AliucordMavenRepository by inject() | ||
|
||
/** | ||
* This is populated right before the download starts (ref: [execute]) | ||
*/ | ||
private lateinit var targetVersion: String | ||
|
||
override val localizedName = R.string.install_step_dl_aliuhook | ||
override val targetUrl get() = AliucordMavenRepository.getAliuhookUrl(targetVersion) | ||
override val targetFile get() = paths.cachedAliuhookAAR(targetVersion) | ||
|
||
override suspend fun execute(container: StepRunner) { | ||
targetVersion = maven.getAliuhookVersion().getOrThrow() | ||
|
||
super.execute(container) | ||
} | ||
} | ||
|
Oops, something went wrong.