Skip to content

Commit

Permalink
wip: rewrite patching process
Browse files Browse the repository at this point in the history
  • Loading branch information
rushiiMachine committed Jan 20, 2024
1 parent e5b0307 commit 8ed6e41
Show file tree
Hide file tree
Showing 26 changed files with 854 additions and 373 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class ManagerApplication : Application() {
modules(module {
single { providePreferences() }
single { provideDownloadManager() }
single { providePathManager() }
})
}
}
Expand Down
8 changes: 6 additions & 2 deletions app/src/main/kotlin/com/aliucord/manager/di/Managers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package com.aliucord.manager.di

import android.app.Application
import android.content.Context
import com.aliucord.manager.manager.DownloadManager
import com.aliucord.manager.manager.PreferencesManager
import com.aliucord.manager.manager.*
import org.koin.core.scope.Scope

fun Scope.providePreferences(): PreferencesManager {
Expand All @@ -15,3 +14,8 @@ fun Scope.provideDownloadManager(): DownloadManager {
val application: Application = get()
return DownloadManager(application)
}

fun Scope.providePathManager(): PathManager {
val ctx: Context = get()
return PathManager(ctx)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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.prepare.FetchInfoStep
import kotlinx.collections.immutable.persistentListOf

/**
* Used for installing the old Kotlin Discord app.
*/
class KtStepContainer : StepContainer() {
override val steps = persistentListOf<Step>(
FetchInfoStep(),
DownloadDiscordStep(),
DownloadInjectorStep(),
DownloadAliuhookStep(),
DownloadKotlinStep(),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.aliucord.manager.installer.steps

import com.aliucord.manager.installer.steps.base.Step
import com.aliucord.manager.installer.steps.base.StepState
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

abstract class StepContainer : 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> getCompletedStep(): T {
val step = steps.asSequence()
.filterIsInstance<T>()
.filter { it.state == StepState.Success }
.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@StepContainer)
if (error != null) return error

// Add delay for human psychology and
// better group visibility in UI (the active group can change way too fast)
if (!preferences.devMode && step.durationMs < 1000) {
delay(1000L - step.durationMs)
}

}

return null
}
}
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)
}
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.StepContainer
import com.aliucord.manager.installer.steps.StepGroup
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: StepContainer) {
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.Cancelled
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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.StepContainer
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: StepContainer)

/**
* 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: StepContainer): 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 {
execute(container)
state = StepState.Success
null
} catch (t: Throwable) {
state = StepState.Error
t
}
}

durationMs = executionTimeMs.roundToInt()
return error
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.aliucord.manager.installer.steps.base

enum class StepState {
Pending,
Running,
Success,
Error,
Skipped,
Cancelled, // TODO: something like the discord dnd sign except its not red, but gray maybe
}
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.StepContainer
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: StepContainer) {
targetVersion = maven.getAliuhookVersion().getOrThrow()

super.execute(container)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.aliucord.manager.installer.steps.download

import androidx.compose.runtime.Stable
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R
import com.aliucord.manager.installer.steps.base.DownloadStep
import com.aliucord.manager.manager.PathManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

/**
* If not already cached, then download the raw unmodified v126.21 (Kotlin) Discord APK
* from a redirect to an APK mirror site provided by the Aliucord backend.
*/
@Stable
class DownloadDiscordStep : DownloadStep(), KoinComponent {
private val paths: PathManager by inject()

override val localizedName = R.string.install_step_dl_kt_apk
override val targetUrl = getDiscordApkUrl(DISCORD_KT_VERSION)
override val targetFile = paths.discordApkVersionCache(DISCORD_KT_VERSION)
.resolve("discord.apk")

override suspend fun verify() {
super.verify()

// TODO: verify signature
}

private companion object {
/**
* Last version of Discord before the RN transition.
*/
const val DISCORD_KT_VERSION = 126021

fun getDiscordApkUrl(version: Int) =
"${BuildConfig.BACKEND_URL}/download/discord?v=$version"
}
}
Loading

0 comments on commit 8ed6e41

Please sign in to comment.