feat: always use Ktor downloader
Android's DownloadManager has too many issues at this point, it's become unbearable. SecurityException here, random failure there, horrible logging, etc. It's much more reliable to directly use an HTTP client to download our stuff. Maybe I'll reimplement the download progress notification separately. Also turns out you can't use Android's DownloadManager to download to the app's internal data directory. very funny.
This commit is contained in:
parent
b2e5353abc
commit
a1e7594698
11 changed files with 29 additions and 180 deletions
|
|
@ -70,7 +70,6 @@ class ManagerApplication : Application() {
|
|||
singleOf(::InstallerManager)
|
||||
singleOf(::OverlayManager)
|
||||
|
||||
singleOf(::DownloadManagerProvider)
|
||||
singleOf(::AndroidDownloadManager)
|
||||
singleOf(::KtorDownloadManager)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package com.aliucord.manager.di
|
||||
|
||||
import com.aliucord.manager.manager.PreferencesManager
|
||||
import com.aliucord.manager.manager.download.*
|
||||
import com.aliucord.manager.patcher.util.Signer.getKoin
|
||||
import com.aliucord.manager.util.IS_PROBABLY_EMULATOR
|
||||
import org.koin.core.annotation.KoinInternalApi
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Handle providing the correct install manager based on preferences and device type.
|
||||
*/
|
||||
class DownloadManagerProvider(private val prefs: PreferencesManager) {
|
||||
fun getActiveDownloader(): IDownloadManager =
|
||||
getDownloader(prefs.downloader)
|
||||
|
||||
@OptIn(KoinInternalApi::class)
|
||||
fun getDownloader(type: DownloaderSetting): IDownloadManager =
|
||||
getKoin().scopeRegistry.rootScope.get(clazz = type.downloaderClass)
|
||||
|
||||
companion object {
|
||||
fun getDefaultDownloader(): DownloaderSetting {
|
||||
// Ktor downloader has specific fix for emulator
|
||||
return if (IS_PROBABLY_EMULATOR) {
|
||||
DownloaderSetting.Ktor
|
||||
} else {
|
||||
DownloaderSetting.Android
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DownloaderSetting(
|
||||
// @StringRes
|
||||
// private val localizedName: Int,
|
||||
val downloaderClass: KClass<out IDownloadManager>,
|
||||
) {
|
||||
Android(AndroidDownloadManager::class),
|
||||
Ktor(KtorDownloadManager::class),
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ class PathManager(
|
|||
* Use the external cache directory (`/storage/emulated/0/Android/data/com.aliucord.manager/cache`)
|
||||
* when dev mode or preserving APKs is enabled. Otherwise, default to internal app cache.
|
||||
*/
|
||||
private val externalCacheDir
|
||||
private val cacheDir
|
||||
get() = when (prefs.devMode || prefs.keepPatchedApks) {
|
||||
false -> context.cacheDir
|
||||
true -> context.externalCacheDir
|
||||
|
|
@ -48,16 +48,16 @@ class PathManager(
|
|||
* Delete the entire cache dir and recreate it.
|
||||
*/
|
||||
fun clearCache() {
|
||||
if (!externalCacheDir.deleteRecursively())
|
||||
if (!cacheDir.deleteRecursively())
|
||||
throw IllegalStateException("Failed to delete cache")
|
||||
|
||||
externalCacheDir.mkdirs()
|
||||
cacheDir.mkdirs()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new subfolder in the Discord APK cache for a specific version.
|
||||
*/
|
||||
fun discordApkVersionCache(version: Int): File = externalCacheDir
|
||||
fun discordApkVersionCache(version: Int): File = cacheDir
|
||||
.resolve("discord")
|
||||
.resolve(version.toString())
|
||||
.apply { mkdirs() }
|
||||
|
|
@ -65,45 +65,45 @@ class PathManager(
|
|||
/**
|
||||
* Resolve a specific path for a cached injector.
|
||||
*/
|
||||
fun cachedInjectorDex(version: SemVer, custom: Boolean = false) = externalCacheDir
|
||||
fun cachedInjectorDex(version: SemVer, custom: Boolean = false) = cacheDir
|
||||
.resolve("injector").apply { mkdirs() }
|
||||
.resolve("$version${if (custom) ".custom" else ""}.dex")
|
||||
|
||||
/**
|
||||
* Get all the versions of custom injector builds.
|
||||
*/
|
||||
fun customInjectorDexs() = listCustomFiles(externalCacheDir.resolve("injector"))
|
||||
fun customInjectorDexs() = listCustomFiles(cacheDir.resolve("injector"))
|
||||
|
||||
/**
|
||||
* Resolve a specific path for a versioned cached Aliuhook build
|
||||
*/
|
||||
fun cachedAliuhookAAR(version: SemVer) = externalCacheDir
|
||||
fun cachedAliuhookAAR(version: SemVer) = cacheDir
|
||||
.resolve("aliuhook").apply { mkdirs() }
|
||||
.resolve("$version.aar")
|
||||
|
||||
/**
|
||||
* Resolve a specific path for a versioned smali patches archive.
|
||||
*/
|
||||
fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = externalCacheDir
|
||||
fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = cacheDir
|
||||
.resolve("patches").apply { mkdirs() }
|
||||
.resolve("$version${if (custom) ".custom" else ""}.zip")
|
||||
|
||||
/**
|
||||
* Get all the versions of custom smali bundles.
|
||||
*/
|
||||
fun customSmaliPatches() = listCustomFiles(externalCacheDir.resolve("patches"))
|
||||
fun customSmaliPatches() = listCustomFiles(cacheDir.resolve("patches"))
|
||||
|
||||
/**
|
||||
* Singular Kotlin file of the most up-to-date version
|
||||
* since the stdlib is backwards compatible.
|
||||
*/
|
||||
fun cachedKotlinDex() = externalCacheDir
|
||||
fun cachedKotlinDex() = cacheDir
|
||||
.resolve("kotlin.dex")
|
||||
|
||||
/**
|
||||
* The temporary working directory of a currently executing patching process.
|
||||
*/
|
||||
fun patchingWorkingDir() = externalCacheDir
|
||||
fun patchingWorkingDir() = cacheDir
|
||||
.resolve("patched")
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.aliucord.manager.manager
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.aliucord.manager.di.DownloadManagerProvider
|
||||
import com.aliucord.manager.di.DownloaderSetting
|
||||
import com.aliucord.manager.manager.base.BasePreferenceManager
|
||||
import com.aliucord.manager.ui.components.Theme
|
||||
|
||||
|
|
@ -10,7 +8,6 @@ class PreferencesManager(preferences: SharedPreferences) : BasePreferenceManager
|
|||
var theme by enumPreference("theme", Theme.DARK)
|
||||
var dynamicColor by booleanPreference("dynamic_color", false)
|
||||
var devMode by booleanPreference("dev_mode", false)
|
||||
var downloader by enumPreference<DownloaderSetting>("downloader", DownloadManagerProvider.getDefaultDownloader())
|
||||
var installer by enumPreference<InstallerSetting>("installer", InstallerSetting.PM)
|
||||
var keepPatchedApks by booleanPreference("keep_patched_apks", false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.content.Context
|
|||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.manager.download.IDownloadManager.ProgressListener
|
||||
|
|
@ -34,7 +35,7 @@ class AndroidDownloadManager(application: Application) : IDownloadManager {
|
|||
out.parentFile?.mkdirs()
|
||||
|
||||
// Create and start a download in the system DownloadManager
|
||||
val downloadId = DownloadManager.Request(Uri.parse(url))
|
||||
val downloadId = DownloadManager.Request(url.toUri())
|
||||
.setTitle("Aliucord Manager")
|
||||
.setDescription("Downloading ${out.name}...")
|
||||
.setDestinationUri(Uri.fromFile(out))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.aliucord.manager.manager.download
|
||||
|
||||
import com.aliucord.manager.manager.download.IDownloadManager.Result
|
||||
import com.aliucord.manager.util.IS_PROBABLY_EMULATOR
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.prepareGet
|
||||
|
|
@ -28,7 +29,9 @@ class KtorDownloadManager(private val http: HttpClient) : IDownloadManager {
|
|||
val httpStmt = http.prepareGet(url) {
|
||||
// Disable compression due to bug on emulators
|
||||
// This header cannot be set with Android's DownloadManager
|
||||
header(HttpHeaders.AcceptEncoding, null)
|
||||
if (IS_PROBABLY_EMULATOR) {
|
||||
header(HttpHeaders.AcceptEncoding, null)
|
||||
}
|
||||
}
|
||||
|
||||
httpStmt.execute { resp ->
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
package com.aliucord.manager.patcher.steps.base
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.di.DownloadManagerProvider
|
||||
import com.aliucord.manager.di.DownloaderSetting
|
||||
import com.aliucord.manager.manager.OverlayManager
|
||||
import com.aliucord.manager.manager.PreferencesManager
|
||||
import com.aliucord.manager.manager.download.AndroidDownloadManager
|
||||
import com.aliucord.manager.manager.download.IDownloadManager
|
||||
import com.aliucord.manager.manager.download.KtorDownloadManager
|
||||
import com.aliucord.manager.patcher.StepRunner
|
||||
import com.aliucord.manager.patcher.steps.StepGroup
|
||||
import com.aliucord.manager.ui.components.dialogs.AlternativeDownloaderDialog
|
||||
import com.aliucord.manager.util.showToast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -27,9 +19,7 @@ import java.io.File
|
|||
@Stable
|
||||
abstract class DownloadStep : Step(), KoinComponent {
|
||||
private val context: Context by inject()
|
||||
private val overlays: OverlayManager by inject()
|
||||
private val prefs: PreferencesManager by inject()
|
||||
private val downloaders: DownloadManagerProvider by inject()
|
||||
private val downloader: KtorDownloadManager by inject()
|
||||
|
||||
/**
|
||||
* The remote url to download
|
||||
|
|
@ -67,7 +57,7 @@ abstract class DownloadStep : Step(), KoinComponent {
|
|||
targetFile.delete()
|
||||
}
|
||||
|
||||
val result = downloaders.getActiveDownloader().download(targetUrl, targetFile) { newProgress ->
|
||||
val result = downloader.download(targetUrl, targetFile) { newProgress ->
|
||||
progress = newProgress ?: -1f
|
||||
}
|
||||
|
||||
|
|
@ -96,29 +86,6 @@ abstract class DownloadStep : Step(), KoinComponent {
|
|||
Toast.makeText(context, toastText, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
// If this is a specific Android DownloadManager error, then prompt to use a different downloader
|
||||
if (result is AndroidDownloadManager.Error
|
||||
&& result.reason in arrayOf(DownloadManager.ERROR_HTTP_DATA_ERROR, DownloadManager.ERROR_CANNOT_RESUME)
|
||||
&& prefs.downloader != DownloaderSetting.Ktor
|
||||
) {
|
||||
state = StepState.Error
|
||||
|
||||
val confirmed = overlays.startComposableForResult { callback ->
|
||||
AlternativeDownloaderDialog(
|
||||
onConfirm = { callback(true) },
|
||||
onDismiss = { callback(false) },
|
||||
)
|
||||
}
|
||||
|
||||
if (confirmed) {
|
||||
Log.i(BuildConfig.TAG, "Changing to alternative downloader after failure")
|
||||
prefs.downloader = DownloaderSetting.Ktor
|
||||
state = StepState.Running
|
||||
|
||||
return execute(container)
|
||||
}
|
||||
}
|
||||
|
||||
throw Error("Failed to download: $result")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
package com.aliucord.manager.ui.components.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.R
|
||||
|
||||
@Composable
|
||||
fun AlternativeDownloaderDialog(
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
FilledTonalButton(
|
||||
onClick = onConfirm,
|
||||
colors = ButtonDefaults.filledTonalButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(R.string.action_retry))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
},
|
||||
title = { Text(stringResource(R.string.alt_downloader_dialog_title)) },
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.alt_downloader_dialog_desc),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_download),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ import androidx.compose.ui.unit.dp
|
|||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.di.DownloaderSetting
|
||||
import com.aliucord.manager.ui.components.BackButton
|
||||
import com.aliucord.manager.ui.components.settings.*
|
||||
import com.aliucord.manager.ui.screens.settings.components.ThemeDialog
|
||||
|
|
@ -102,20 +101,6 @@ class SettingsScreen : Screen, Parcelable {
|
|||
onPrefChange = { preferences.keepPatchedApks = it },
|
||||
)
|
||||
|
||||
SettingsSwitch(
|
||||
label = stringResource(R.string.setting_alt_downloader),
|
||||
secondaryLabel = stringResource(R.string.setting_alt_downloader_desc),
|
||||
icon = { Icon(painterResource(R.drawable.ic_download), null) },
|
||||
pref = preferences.downloader == DownloaderSetting.Ktor,
|
||||
onPrefChange = {
|
||||
preferences.downloader = if (it) {
|
||||
DownloaderSetting.Ktor
|
||||
} else {
|
||||
DownloaderSetting.Android
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
var clearedCache by rememberSaveable { mutableStateOf(false) }
|
||||
Button(
|
||||
shape = ShapeDefaults.Large,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import androidx.compose.runtime.*
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.di.DownloadManagerProvider
|
||||
import com.aliucord.manager.domain.repository.GithubRepository
|
||||
import com.aliucord.manager.manager.InstallerManager
|
||||
import com.aliucord.manager.manager.InstallerSetting
|
||||
import com.aliucord.manager.manager.download.KtorDownloadManager
|
||||
import com.aliucord.manager.network.utils.SemVer
|
||||
import com.aliucord.manager.network.utils.getOrNull
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -16,7 +16,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class UpdaterViewModel(
|
||||
private val github: GithubRepository,
|
||||
private val downloaders: DownloadManagerProvider,
|
||||
private val downloader: KtorDownloadManager,
|
||||
private val installers: InstallerManager,
|
||||
private val application: Application,
|
||||
) : ViewModel() {
|
||||
|
|
@ -40,17 +40,15 @@ class UpdaterViewModel(
|
|||
viewModelScope.launch {
|
||||
isWorking = true
|
||||
|
||||
application.externalCacheDir!!.resolve("manager.apk").also {
|
||||
it.delete()
|
||||
val apkFile = application.cacheDir.resolve("manager.apk").apply { delete() }
|
||||
|
||||
downloaders.getActiveDownloader().download(url, it)
|
||||
installers.getInstaller(InstallerSetting.PM).install(
|
||||
apks = listOf(it),
|
||||
silent = true,
|
||||
)
|
||||
downloader.download(url, apkFile)
|
||||
installers.getInstaller(InstallerSetting.PM).install(
|
||||
apks = listOf(apkFile),
|
||||
silent = true,
|
||||
)
|
||||
|
||||
it.delete()
|
||||
}
|
||||
apkFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@
|
|||
<string name="setting_dynamic_color_desc">Enables Material You theming on Android 12+</string>
|
||||
<string name="setting_keep_patched_apks">Keep patched APKs</string>
|
||||
<string name="setting_keep_patched_apks_desc">Keep all patched files and APKs after installing for debugging.</string>
|
||||
<string name="setting_alt_downloader">Alternative downloader</string>
|
||||
<string name="setting_alt_downloader_desc">Use a custom HTTP client instead of the system DownloadManager. May fix download errors on specific devices.</string>
|
||||
|
||||
<string name="theme_system">System</string>
|
||||
<string name="theme_dark">Dark</string>
|
||||
|
|
@ -171,9 +169,6 @@
|
|||
<string name="play_protect_warning_ok">Continue</string>
|
||||
<string name="play_protect_warning_open_gpp">Open Play Protect</string>
|
||||
|
||||
<string name="alt_downloader_dialog_title">Alternative Downloader</string>
|
||||
<string name="alt_downloader_dialog_desc">This download failed with a specific error that may indicate issues with the system\'s DownloadManager. Would you like to retry with the the alternative downloader implementation?</string>
|
||||
|
||||
<string name="fun_fact_prefix">Fun Fact: %s</string>
|
||||
<string name="fun_fact_1">Did you know that Google Play Protect is useless?</string>
|
||||
<string name="fun_fact_2">Did you know Aliucord with 150+ plugins somehow runs better than Discord\'s new app?</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue