This repository has been archived by the owner on Jul 7, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(refactor) Separate out installation steps (#71)
* Move most download steps * Add most patching steps * Add logger to new step runner * Add last steps * Add debug information to new runner * Implement the new installer * Add documentation * Document installer components
- Loading branch information
Showing
25 changed files
with
1,089 additions
and
535 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
80 changes: 80 additions & 0 deletions
80
app/src/main/java/dev/beefers/vendetta/manager/installer/step/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,80 @@ | ||
package dev.beefers.vendetta.manager.installer.step | ||
|
||
import androidx.annotation.StringRes | ||
import androidx.compose.runtime.Stable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableIntStateOf | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.setValue | ||
import org.koin.core.component.KoinComponent | ||
import kotlin.time.measureTimedValue | ||
|
||
/** | ||
* A distinct step to be ran while patching | ||
*/ | ||
@Stable | ||
abstract class Step: KoinComponent { | ||
|
||
/** | ||
* Group this step belongs to | ||
*/ | ||
abstract val group: StepGroup | ||
|
||
/** | ||
* Label used in the installer ui | ||
*/ | ||
@get:StringRes | ||
abstract val nameRes: Int | ||
|
||
/** | ||
* Current status for this step | ||
*/ | ||
var status by mutableStateOf(StepStatus.QUEUED) | ||
protected set | ||
|
||
/** | ||
* How much progress this step has made, use null if unknown | ||
*/ | ||
var progress by mutableStateOf<Float?>(null) | ||
protected set | ||
|
||
/** | ||
* How long this step took to run, in milliseconds | ||
*/ | ||
var durationMs by mutableIntStateOf(0) | ||
private set | ||
|
||
/** | ||
* Runs this step | ||
* | ||
* @param runner The host runner, used to share information between steps | ||
*/ | ||
protected abstract suspend fun run(runner: StepRunner) | ||
|
||
/** | ||
* Safely runs this step, catching any errors and timing how long it runs. | ||
* | ||
* @param runner The host runner, used to share information between steps | ||
*/ | ||
suspend fun runCatching(runner: StepRunner): Throwable? { | ||
if (status != StepStatus.QUEUED) | ||
throw IllegalStateException("Cannot execute a step that has already started") | ||
|
||
status = StepStatus.ONGOING | ||
|
||
val (error, time) = measureTimedValue { | ||
try { | ||
run(runner) | ||
status = StepStatus.SUCCESSFUL | ||
null | ||
} catch (t: Throwable) { | ||
status = StepStatus.UNSUCCESSFUL | ||
t | ||
} | ||
} | ||
|
||
durationMs = time.inWholeMilliseconds.toInt() | ||
return error | ||
} | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
app/src/main/java/dev/beefers/vendetta/manager/installer/step/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,24 @@ | ||
package dev.beefers.vendetta.manager.installer.step | ||
|
||
import androidx.annotation.StringRes | ||
import dev.beefers.vendetta.manager.R | ||
|
||
/** | ||
* Represents a group of [Step]s | ||
*/ | ||
enum class StepGroup(@StringRes val nameRes: Int) { | ||
/** | ||
* All steps deal with downloading files remotely | ||
*/ | ||
DL(R.string.group_download), | ||
|
||
/** | ||
* Steps that modify the APKs | ||
*/ | ||
PATCHING(R.string.group_patch), | ||
|
||
/** | ||
* Only contains the [install step][dev.beefers.vendetta.manager.installer.step.installing.InstallStep] | ||
*/ | ||
INSTALLING(R.string.group_installing) | ||
} |
179 changes: 179 additions & 0 deletions
179
app/src/main/java/dev/beefers/vendetta/manager/installer/step/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,179 @@ | ||
package dev.beefers.vendetta.manager.installer.step | ||
|
||
import android.content.Context | ||
import android.os.Build | ||
import android.os.Environment | ||
import androidx.compose.runtime.Stable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.setValue | ||
import dev.beefers.vendetta.manager.BuildConfig | ||
import dev.beefers.vendetta.manager.domain.manager.PreferenceManager | ||
import dev.beefers.vendetta.manager.installer.step.download.DownloadBaseStep | ||
import dev.beefers.vendetta.manager.installer.step.download.DownloadLangStep | ||
import dev.beefers.vendetta.manager.installer.step.download.DownloadLibsStep | ||
import dev.beefers.vendetta.manager.installer.step.download.DownloadResourcesStep | ||
import dev.beefers.vendetta.manager.installer.step.download.DownloadVendettaStep | ||
import dev.beefers.vendetta.manager.installer.step.installing.InstallStep | ||
import dev.beefers.vendetta.manager.installer.step.patching.AddVendettaStep | ||
import dev.beefers.vendetta.manager.installer.step.patching.PatchManifestsStep | ||
import dev.beefers.vendetta.manager.installer.step.patching.PresignApksStep | ||
import dev.beefers.vendetta.manager.installer.step.patching.ReplaceIconStep | ||
import dev.beefers.vendetta.manager.installer.util.Logger | ||
import dev.beefers.vendetta.manager.utils.DiscordVersion | ||
import kotlinx.collections.immutable.ImmutableList | ||
import kotlinx.collections.immutable.persistentListOf | ||
import kotlinx.collections.immutable.toImmutableList | ||
import kotlinx.coroutines.delay | ||
import org.koin.core.component.KoinComponent | ||
import org.koin.core.component.inject | ||
import java.io.File | ||
|
||
/** | ||
* Runs all installation steps in order | ||
* | ||
* Credit to rushii (github.com/rushiiMachine) | ||
* | ||
* @param discordVersion Version of Discord to inject Vendetta into | ||
*/ | ||
@Stable | ||
class StepRunner( | ||
private val discordVersion: DiscordVersion | ||
): KoinComponent { | ||
|
||
private val preferenceManager: PreferenceManager by inject() | ||
private val context: Context by inject() | ||
private val debugInfo = """ | ||
Vendetta Manager v${BuildConfig.VERSION_NAME} | ||
Built from commit ${BuildConfig.GIT_COMMIT} on ${BuildConfig.GIT_BRANCH} ${if (BuildConfig.GIT_LOCAL_CHANGES || BuildConfig.GIT_LOCAL_COMMITS) "(Changes Present)" else ""} | ||
Running Android ${Build.VERSION.RELEASE}, API level ${Build.VERSION.SDK_INT} | ||
Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()} | ||
Device: ${Build.MANUFACTURER} - ${Build.MODEL} (${Build.DEVICE}) | ||
${if(Build.VERSION.SDK_INT > Build.VERSION_CODES.S) "SOC: ${Build.SOC_MANUFACTURER} ${Build.SOC_MODEL}\n" else "\n\n"} | ||
Adding Vendetta to Discord v$discordVersion | ||
""".trimIndent() | ||
|
||
/** | ||
* Logger associated with this runner | ||
*/ | ||
val logger = Logger("StepRunner").also { | ||
debugInfo.split("\n").forEach(it.logs::add) // Add debug information to logs but don't print to logcat | ||
} | ||
|
||
/** | ||
* Root directory for all downloaded files | ||
*/ | ||
private val cacheDir = | ||
context.externalCacheDir | ||
?: File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS) | ||
.resolve("VendettaManager") | ||
.also { it.mkdirs() } | ||
|
||
/** | ||
* Where version specific downloads are persisted | ||
*/ | ||
private val discordCacheDir = cacheDir.resolve(discordVersion.toVersionCode()) | ||
|
||
/** | ||
* Working directory where apks are directly modified (i.e. replacing the app icon) | ||
*/ | ||
private val patchedDir = discordCacheDir.resolve("patched").also { it.deleteRecursively() } | ||
|
||
/** | ||
* Where apks are moved to once signed | ||
*/ | ||
private val signedDir = discordCacheDir.resolve("signed").also { it.deleteRecursively() } | ||
|
||
/** | ||
* Output directory for LSPatch | ||
*/ | ||
private val lspatchedDir = patchedDir.resolve("lspatched").also { it.deleteRecursively() } | ||
|
||
var currentStep by mutableStateOf<Step?>(null) | ||
private set | ||
|
||
/** | ||
* Whether or not the patching/installation process has completed. | ||
* Note that this does not mean all steps were finished successfully | ||
*/ | ||
var completed by mutableStateOf<Boolean>(false) | ||
private set | ||
|
||
/** | ||
* List of steps to go through for this install | ||
* | ||
* ORDER MATTERS | ||
*/ | ||
val steps: ImmutableList<Step> = buildList { | ||
// Downloading | ||
add(DownloadBaseStep(discordCacheDir, patchedDir, discordVersion.toVersionCode())) | ||
add(DownloadLibsStep(discordCacheDir, patchedDir, discordVersion.toVersionCode())) | ||
add(DownloadLangStep(discordCacheDir, patchedDir, discordVersion.toVersionCode())) | ||
add(DownloadResourcesStep(discordCacheDir, patchedDir, discordVersion.toVersionCode())) | ||
add(DownloadVendettaStep(patchedDir)) | ||
|
||
// Patching | ||
if (preferenceManager.patchIcon) add(ReplaceIconStep()) | ||
add(PatchManifestsStep()) | ||
add(PresignApksStep(signedDir)) | ||
add(AddVendettaStep(signedDir, lspatchedDir)) | ||
|
||
// Installing | ||
add(InstallStep(lspatchedDir)) | ||
}.toImmutableList() | ||
|
||
/** | ||
* 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> getCompletedStep(): T { | ||
val step = steps.asSequence() | ||
.filterIsInstance<T>() | ||
.filter { it.status == StepStatus.SUCCESSFUL } | ||
.firstOrNull() | ||
|
||
if (step == null) { | ||
throw IllegalArgumentException("No completed step ${T::class.simpleName} exists in container") | ||
} | ||
|
||
return step | ||
} | ||
|
||
/** | ||
* Clears all cached files | ||
*/ | ||
fun clearCache() { | ||
cacheDir.deleteRecursively() | ||
} | ||
|
||
/** | ||
* Run all the [steps] in order | ||
*/ | ||
suspend fun runAll(): Throwable? { | ||
for (step in steps) { | ||
if (completed) return null // Failsafe in case runner is incorrectly marked as not completed too early | ||
|
||
currentStep = step | ||
val error = step.runCatching(this) | ||
if (error != null) { | ||
logger.e("Failed on ${step::class.simpleName}", error) | ||
|
||
completed = true | ||
return error | ||
} | ||
|
||
// Add delay for human psychology and | ||
// better group visibility in UI (the active group can change way too fast) | ||
if (!preferenceManager.isDeveloper && step.durationMs < 1000) { | ||
delay(1000L - step.durationMs) | ||
} | ||
} | ||
|
||
completed = true | ||
return null | ||
} | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
app/src/main/java/dev/beefers/vendetta/manager/installer/step/StepStatus.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 dev.beefers.vendetta.manager.installer.step | ||
|
||
enum class StepStatus { | ||
/** | ||
* Currently in progress | ||
*/ | ||
ONGOING, | ||
|
||
/** | ||
* Completed with no errors | ||
*/ | ||
SUCCESSFUL, | ||
|
||
/** | ||
* Completed with an error | ||
*/ | ||
UNSUCCESSFUL, | ||
|
||
/** | ||
* Has not yet been ran | ||
*/ | ||
QUEUED | ||
} |
24 changes: 24 additions & 0 deletions
24
app/src/main/java/dev/beefers/vendetta/manager/installer/step/download/DownloadBaseStep.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,24 @@ | ||
package dev.beefers.vendetta.manager.installer.step.download | ||
|
||
import androidx.compose.runtime.Stable | ||
import dev.beefers.vendetta.manager.R | ||
import dev.beefers.vendetta.manager.installer.step.download.base.DownloadStep | ||
import java.io.File | ||
|
||
/** | ||
* Downloads the base Discord APK | ||
*/ | ||
@Stable | ||
class DownloadBaseStep( | ||
dir: File, | ||
workingDir: File, | ||
version: String | ||
): DownloadStep() { | ||
|
||
override val nameRes = R.string.step_dl_base | ||
|
||
override val url: String = "$baseUrl/tracker/download/$version/base" | ||
override val destination = dir.resolve("base-$version.apk") | ||
override val workingCopy = workingDir.resolve("base-$version.apk") | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
app/src/main/java/dev/beefers/vendetta/manager/installer/step/download/DownloadLangStep.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,24 @@ | ||
package dev.beefers.vendetta.manager.installer.step.download | ||
|
||
import androidx.compose.runtime.Stable | ||
import dev.beefers.vendetta.manager.R | ||
import dev.beefers.vendetta.manager.installer.step.download.base.DownloadStep | ||
import java.io.File | ||
|
||
/** | ||
* Downloads the languages split, will always be English because Discord doesn't store their strings in this split | ||
*/ | ||
@Stable | ||
class DownloadLangStep( | ||
dir: File, | ||
workingDir: File, | ||
version: String | ||
): DownloadStep() { | ||
|
||
override val nameRes = R.string.step_dl_lang | ||
|
||
override val url: String = "$baseUrl/tracker/download/$version/config.en" | ||
override val destination = dir.resolve("config.en-$version.apk") | ||
override val workingCopy = workingDir.resolve("config.en-$version.apk") | ||
|
||
} |
Oops, something went wrong.