Skip to content

Commit c78c04a

Browse files
committed
Merge branch 'main' into feat/new-home-page
2 parents a72fe41 + d4c575f commit c78c04a

File tree

71 files changed

+2026
-1066
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2026
-1066
lines changed

app/src/main/kotlin/com/aliucord/manager/ManagerApplication.kt

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class ManagerApplication : Application() {
5959
modules(module {
6060
single { providePreferences() }
6161
single { provideDownloadManager() }
62+
single { providePathManager() }
6263
})
6364
}
6465
}

app/src/main/kotlin/com/aliucord/manager/di/Managers.kt

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package com.aliucord.manager.di
22

33
import android.app.Application
44
import android.content.Context
5-
import com.aliucord.manager.manager.DownloadManager
6-
import com.aliucord.manager.manager.PreferencesManager
5+
import com.aliucord.manager.manager.*
76
import org.koin.core.scope.Scope
87

98
fun Scope.providePreferences(): PreferencesManager {
@@ -15,3 +14,8 @@ fun Scope.provideDownloadManager(): DownloadManager {
1514
val application: Application = get()
1615
return DownloadManager(application)
1716
}
17+
18+
fun Scope.providePathManager(): PathManager {
19+
val ctx: Context = get()
20+
return PathManager(ctx)
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.aliucord.manager.installer.steps
2+
3+
import com.aliucord.manager.installer.steps.base.Step
4+
import com.aliucord.manager.installer.steps.download.*
5+
import com.aliucord.manager.installer.steps.install.*
6+
import com.aliucord.manager.installer.steps.patch.*
7+
import com.aliucord.manager.installer.steps.prepare.DowngradeCheckStep
8+
import com.aliucord.manager.installer.steps.prepare.FetchInfoStep
9+
import kotlinx.collections.immutable.persistentListOf
10+
11+
/**
12+
* Used for installing the old Kotlin Discord app.
13+
*/
14+
class KotlinInstallRunner : StepRunner() {
15+
override val steps = persistentListOf<Step>(
16+
// Prepare
17+
FetchInfoStep(),
18+
DowngradeCheckStep(),
19+
20+
// Download
21+
DownloadDiscordStep(),
22+
DownloadInjectorStep(),
23+
DownloadAliuhookStep(),
24+
DownloadKotlinStep(),
25+
26+
// Patch
27+
CopyDependenciesStep(),
28+
ReplaceIconStep(),
29+
PatchManifestStep(),
30+
AddInjectorStep(),
31+
AddAliuhookStep(),
32+
33+
// Install
34+
AlignmentStep(),
35+
SigningStep(),
36+
InstallStep(),
37+
CleanupStep(),
38+
)
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.aliucord.manager.installer.steps
2+
3+
import androidx.annotation.StringRes
4+
import androidx.compose.runtime.Immutable
5+
import com.aliucord.manager.R
6+
7+
/**
8+
* A group of steps that is shown under one section in the install UI.
9+
* This has no functional impact.
10+
*/
11+
@Immutable
12+
enum class StepGroup(
13+
/**
14+
* The UI name to display this group as
15+
*/
16+
@get:StringRes
17+
val localizedName: Int,
18+
) {
19+
Prepare(R.string.install_group_prepare),
20+
Download(R.string.install_group_download),
21+
Patch(R.string.install_group_patch),
22+
Install(R.string.install_group_install)
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.aliucord.manager.installer.steps
2+
3+
import com.aliucord.manager.installer.steps.base.Step
4+
import com.aliucord.manager.manager.PreferencesManager
5+
import kotlinx.collections.immutable.ImmutableList
6+
import kotlinx.coroutines.delay
7+
import org.koin.core.component.KoinComponent
8+
import org.koin.core.component.inject
9+
10+
/**
11+
* The minimum time that is required to occur between step switches, to avoid
12+
* quickly switching the step groups in the UI. (very disorienting)
13+
* Larger delay leads to a perception that it's doing more work than it actually is.
14+
*/
15+
const val MINIMUM_STEP_DELAY: Long = 600L
16+
17+
abstract class StepRunner : KoinComponent {
18+
private val preferences: PreferencesManager by inject()
19+
20+
abstract val steps: ImmutableList<Step>
21+
22+
/**
23+
* Get a step that has already been successfully executed.
24+
* This is used to retrieve previously executed dependency steps from a later step.
25+
*/
26+
inline fun <reified T : Step> getStep(): T {
27+
val step = steps.asSequence()
28+
.filterIsInstance<T>()
29+
.filter { it.state.isFinished }
30+
.firstOrNull()
31+
32+
if (step == null) {
33+
throw IllegalArgumentException("No completed step ${T::class.simpleName} exists in container")
34+
}
35+
36+
return step
37+
}
38+
39+
suspend fun executeAll(): Throwable? {
40+
for (step in steps) {
41+
val error = step.executeCatching(this@StepRunner)
42+
if (error != null) return error
43+
44+
// Skip minimum run time when in dev mode
45+
if (!preferences.devMode && step.durationMs < MINIMUM_STEP_DELAY) {
46+
delay(MINIMUM_STEP_DELAY - step.durationMs)
47+
}
48+
}
49+
50+
return null
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.aliucord.manager.installer.steps.base
2+
3+
import android.content.Context
4+
import androidx.compose.runtime.Stable
5+
import com.aliucord.manager.R
6+
import com.aliucord.manager.installer.steps.StepGroup
7+
import com.aliucord.manager.installer.steps.StepRunner
8+
import com.aliucord.manager.manager.DownloadManager
9+
import com.aliucord.manager.util.showToast
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.withContext
12+
import org.koin.core.component.KoinComponent
13+
import org.koin.core.component.inject
14+
import java.io.File
15+
16+
@Stable
17+
abstract class DownloadStep : Step(), KoinComponent {
18+
private val context: Context by inject()
19+
private val downloads: DownloadManager by inject()
20+
21+
/**
22+
* The remote url to download
23+
*/
24+
abstract val targetUrl: String
25+
26+
/**
27+
* Target path to store the download in. If this file already exists,
28+
* then the cached version is used and the step is marked as cancelled/skipped.
29+
*/
30+
abstract val targetFile: File
31+
32+
/**
33+
* Verify that the download completely successfully without errors.
34+
* @throws Throwable If verification fails.
35+
*/
36+
open suspend fun verify() {
37+
if (!targetFile.exists())
38+
throw Error("Downloaded file is missing!")
39+
40+
if (targetFile.length() <= 0)
41+
throw Error("Downloaded file is empty!")
42+
}
43+
44+
override val group = StepGroup.Download
45+
46+
override suspend fun execute(container: StepRunner) {
47+
if (targetFile.exists()) {
48+
if (targetFile.length() > 0) {
49+
state = StepState.Skipped
50+
return
51+
}
52+
53+
targetFile.delete()
54+
}
55+
56+
val result = downloads.download(targetUrl, targetFile) { newProgress ->
57+
progress = newProgress ?: -1f
58+
}
59+
60+
when (result) {
61+
is DownloadManager.Result.Success -> {
62+
try {
63+
verify()
64+
} catch (t: Throwable) {
65+
withContext(Dispatchers.Main) {
66+
context.showToast(R.string.installer_dl_verify_fail)
67+
}
68+
69+
throw t
70+
}
71+
}
72+
73+
is DownloadManager.Result.Error -> {
74+
withContext(Dispatchers.Main) {
75+
context.showToast(result.localizedReason)
76+
}
77+
78+
throw Error("Failed to download: ${result.debugReason}")
79+
}
80+
81+
is DownloadManager.Result.Cancelled ->
82+
state = StepState.Error
83+
}
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.aliucord.manager.installer.steps.base
2+
3+
import androidx.annotation.StringRes
4+
import androidx.compose.runtime.*
5+
import com.aliucord.manager.installer.steps.StepGroup
6+
import com.aliucord.manager.installer.steps.StepRunner
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.withContext
9+
import org.koin.core.time.measureTimedValue
10+
import kotlin.math.roundToInt
11+
12+
/**
13+
* A base install process step. Steps are single-use
14+
*/
15+
@Stable
16+
abstract class Step {
17+
/**
18+
* The group this step belongs to.
19+
*/
20+
abstract val group: StepGroup
21+
22+
/**
23+
* The UI name to display this step as
24+
*/
25+
@get:StringRes
26+
abstract val localizedName: Int
27+
28+
/**
29+
* Run the step's logic.
30+
* It can be assumed that this is executed in the correct order after other steps.
31+
*/
32+
protected abstract suspend fun execute(container: StepRunner)
33+
34+
/**
35+
* The current state of this step in the installation process.
36+
*/
37+
var state by mutableStateOf(StepState.Pending)
38+
protected set
39+
40+
/**
41+
* If the current state is [StepState.Running], then the progress of this step.
42+
* If the progress isn't currently measurable, then this should be set to `-1`.
43+
*/
44+
var progress by mutableFloatStateOf(-1f)
45+
protected set
46+
47+
/**
48+
* The total execution time once this step has finished execution.
49+
*/
50+
// TODO: make this a live value
51+
var durationMs by mutableIntStateOf(0)
52+
private set
53+
54+
/**
55+
* Thin wrapper over [execute] but handling errors.
56+
* @return An exception if the step failed to execute.
57+
*/
58+
suspend fun executeCatching(container: StepRunner): Throwable? {
59+
if (state != StepState.Pending)
60+
throw IllegalStateException("Cannot execute a step that has already started")
61+
62+
state = StepState.Running
63+
64+
// Execute this steps logic while timing it
65+
val (error, executionTimeMs) = measureTimedValue {
66+
try {
67+
withContext(Dispatchers.Default) {
68+
execute(container)
69+
}
70+
71+
if (state != StepState.Skipped)
72+
state = StepState.Success
73+
74+
null
75+
} catch (t: Throwable) {
76+
state = StepState.Error
77+
t
78+
}
79+
}
80+
81+
durationMs = executionTimeMs.roundToInt()
82+
return error
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.aliucord.manager.installer.steps.base
2+
3+
enum class StepState {
4+
Pending,
5+
Running,
6+
Success,
7+
Error,
8+
Skipped;
9+
10+
val isFinished: Boolean
11+
get() = this == Success || this == Error || this == Skipped
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.aliucord.manager.installer.steps.download
2+
3+
import androidx.compose.runtime.Stable
4+
import com.aliucord.manager.R
5+
import com.aliucord.manager.domain.repository.AliucordMavenRepository
6+
import com.aliucord.manager.installer.steps.StepRunner
7+
import com.aliucord.manager.installer.steps.base.DownloadStep
8+
import com.aliucord.manager.manager.PathManager
9+
import com.aliucord.manager.network.utils.getOrThrow
10+
import org.koin.core.component.KoinComponent
11+
import org.koin.core.component.inject
12+
13+
/**
14+
* Download a packaged AAR of the latest Aliuhook build from the Aliucord maven.
15+
*/
16+
@Stable
17+
class DownloadAliuhookStep : DownloadStep(), KoinComponent {
18+
private val paths: PathManager by inject()
19+
private val maven: AliucordMavenRepository by inject()
20+
21+
/**
22+
* This is populated right before the download starts (ref: [execute])
23+
*/
24+
private lateinit var targetVersion: String
25+
26+
override val localizedName = R.string.install_step_dl_aliuhook
27+
override val targetUrl get() = AliucordMavenRepository.getAliuhookUrl(targetVersion)
28+
override val targetFile get() = paths.cachedAliuhookAAR(targetVersion)
29+
30+
override suspend fun execute(container: StepRunner) {
31+
targetVersion = maven.getAliuhookVersion().getOrThrow()
32+
33+
super.execute(container)
34+
}
35+
}
36+

0 commit comments

Comments
 (0)