Fix some extension related issue and cleanups

- Extension being marked as not installed instead of untrusted after updating with private installer
- Extension update counter not updating due to extension being marked as untrusted
- Minimize `Key "extension-XXX-YYY" was already used` crash
This commit is contained in:
AntsyLich 2024-05-04 16:08:38 +06:00
parent 134e4648a9
commit 21145144cd
No known key found for this signature in database
3 changed files with 91 additions and 119 deletions

View File

@ -20,7 +20,7 @@ class GetExtensionsByType(
extensionManager.installedExtensionsFlow, extensionManager.installedExtensionsFlow,
extensionManager.untrustedExtensionsFlow, extensionManager.untrustedExtensionsFlow,
extensionManager.availableExtensionsFlow, extensionManager.availableExtensionsFlow,
) { _activeLanguages, _installed, _untrusted, _available -> ) { enabledLanguages, _installed, _untrusted, _available ->
val (updates, installed) = _installed val (updates, installed) = _installed
.filter { (showNsfwSources || !it.isNsfw) } .filter { (showNsfwSources || !it.isNsfw) }
.sortedWith( .sortedWith(
@ -40,9 +40,9 @@ class GetExtensionsByType(
} }
.flatMap { ext -> .flatMap { ext ->
if (ext.sources.isEmpty()) { if (ext.sources.isEmpty()) {
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList() return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
} }
ext.sources.filter { it.lang in _activeLanguages } ext.sources.filter { it.lang in enabledLanguages }
.map { .map {
ext.copy( ext.copy(
name = it.name, name = it.name,

View File

@ -13,14 +13,17 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.launchNow
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.model.StubSource
@ -42,6 +45,8 @@ class ExtensionManager(
private val trustExtension: TrustExtension = Injekt.get(), private val trustExtension: TrustExtension = Injekt.get(),
) { ) {
val scope = CoroutineScope(SupervisorJob())
private val _isInitialized = MutableStateFlow(false) private val _isInitialized = MutableStateFlow(false)
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow() val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
@ -57,24 +62,35 @@ class ExtensionManager(
private val iconMap = mutableMapOf<String, Drawable>() private val iconMap = mutableMapOf<String, Drawable>()
private val _installedExtensionsFlow = MutableStateFlow(emptyList<Extension.Installed>()) private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow() val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope)
private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope)
private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope)
init {
initExtensions()
ExtensionInstallReceiver(InstallationListener()).register(context)
}
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
fun getAppIconForSource(sourceId: Long): Drawable? { fun getAppIconForSource(sourceId: Long): Drawable? {
val pkgName = _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName val pkgName = _installedExtensionsMapFlow.value.values
if (pkgName != null) { .find { ext ->
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { ext.sources.any { it.id == sourceId }
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
.loadIcon(context.packageManager)
} }
} ?.pkgName
return null ?: return null
}
private val _availableExtensionsFlow = MutableStateFlow(emptyList<Extension.Available>()) return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow() ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
.loadIcon(context.packageManager)
}
}
private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap() private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap()
@ -87,33 +103,25 @@ class ExtensionManager(
fun getSourceData(id: Long) = availableExtensionsSourcesData[id] fun getSourceData(id: Long) = availableExtensionsSourcesData[id]
private val _untrustedExtensionsFlow = MutableStateFlow(emptyList<Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow()
init {
initExtensions()
ExtensionInstallReceiver(InstallationListener()).register(context)
}
/** /**
* Loads and registers the installed extensions. * Loads and registers the installed extensions.
*/ */
private fun initExtensions() { private fun initExtensions() {
val extensions = ExtensionLoader.loadExtensions(context) val extensions = ExtensionLoader.loadExtensions(context)
_installedExtensionsFlow.value = extensions _installedExtensionsMapFlow.value = extensions
.filterIsInstance<LoadResult.Success>() .filterIsInstance<LoadResult.Success>()
.map { it.extension } .associate { it.extension.pkgName to it.extension }
_untrustedExtensionsFlow.value = extensions _untrustedExtensionsMapFlow.value = extensions
.filterIsInstance<LoadResult.Untrusted>() .filterIsInstance<LoadResult.Untrusted>()
.map { it.extension } .associate { it.extension.pkgName to it.extension }
_isInitialized.value = true _isInitialized.value = true
} }
/** /**
* Finds the available extensions in the [api] and updates [availableExtensions]. * Finds the available extensions in the [api] and updates [_availableExtensionsMapFlow].
*/ */
suspend fun findAvailableExtensions() { suspend fun findAvailableExtensions() {
val extensions: List<Extension.Available> = try { val extensions: List<Extension.Available> = try {
@ -126,7 +134,7 @@ class ExtensionManager(
enableAdditionalSubLanguages(extensions) enableAdditionalSubLanguages(extensions)
_availableExtensionsFlow.value = extensions _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName }
updatedInstalledExtensionsStatuses(extensions) updatedInstalledExtensionsStatuses(extensions)
setupAvailableExtensionsSourcesDataMap(extensions) setupAvailableExtensionsSourcesDataMap(extensions)
} }
@ -172,35 +180,31 @@ class ExtensionManager(
return return
} }
val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap()
var changed = false var changed = false
for ((pkgName, extension) in installedExtensionsMap) {
for ((index, installedExt) in mutInstalledExtensions.withIndex()) {
val pkgName = installedExt.pkgName
val availableExt = availableExtensions.find { it.pkgName == pkgName } val availableExt = availableExtensions.find { it.pkgName == pkgName }
if (availableExt == null && !installedExt.isObsolete) { if (availableExt == null && !extension.isObsolete) {
mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) installedExtensionsMap[pkgName] = extension.copy(isObsolete = true)
changed = true changed = true
} else if (availableExt != null) { } else if (availableExt != null) {
val hasUpdate = installedExt.updateExists(availableExt) val hasUpdate = extension.updateExists(availableExt)
if (extension.hasUpdate != hasUpdate) {
if (installedExt.hasUpdate != hasUpdate) { installedExtensionsMap[pkgName] = extension.copy(
mutInstalledExtensions[index] = installedExt.copy(
hasUpdate = hasUpdate, hasUpdate = hasUpdate,
repoUrl = availableExt.repoUrl, repoUrl = availableExt.repoUrl,
) )
changed = true
} else { } else {
mutInstalledExtensions[index] = installedExt.copy( installedExtensionsMap[pkgName] = extension.copy(
repoUrl = availableExt.repoUrl, repoUrl = availableExt.repoUrl,
) )
changed = true
} }
changed = true
} }
} }
if (changed) { if (changed) {
_installedExtensionsFlow.value = mutInstalledExtensions _installedExtensionsMapFlow.value = installedExtensionsMap
} }
updatePendingUpdatesCount() updatePendingUpdatesCount()
} }
@ -224,8 +228,7 @@ class ExtensionManager(
* @param extension The extension to be updated. * @param extension The extension to be updated.
*/ */
fun updateExtension(extension: Extension.Installed): Flow<InstallStep> { fun updateExtension(extension: Extension.Installed): Flow<InstallStep> {
val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow()
?: return emptyFlow()
return installExtension(availableExt) return installExtension(availableExt)
} }
@ -262,23 +265,15 @@ class ExtensionManager(
* @param extension the extension to trust * @param extension the extension to trust
*/ */
fun trust(extension: Extension.Untrusted) { fun trust(extension: Extension.Untrusted) {
val untrustedPkgNames = _untrustedExtensionsFlow.value.map { it.pkgName }.toSet() _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return
if (extension.pkgName !in untrustedPkgNames) return
trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash)
val nowTrustedExtensions = _untrustedExtensionsFlow.value _untrustedExtensionsMapFlow.value -= extension.pkgName
.filter { it.pkgName == extension.pkgName && it.versionCode == extension.versionCode }
_untrustedExtensionsFlow.value -= nowTrustedExtensions
launchNow { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName)
nowTrustedExtensions .let { it as? LoadResult.Success }
.map { extension -> ?.let { registerNewExtension(it.extension) }
async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
}
.filterIsInstance<LoadResult.Success>()
.forEach { registerNewExtension(it.extension) }
}
} }
/** /**
@ -287,7 +282,7 @@ class ExtensionManager(
* @param extension The extension to be registered. * @param extension The extension to be registered.
*/ */
private fun registerNewExtension(extension: Extension.Installed) { private fun registerNewExtension(extension: Extension.Installed) {
_installedExtensionsFlow.value += extension _installedExtensionsMapFlow.value += extension
} }
/** /**
@ -297,13 +292,7 @@ class ExtensionManager(
* @param extension The extension to be registered. * @param extension The extension to be registered.
*/ */
private fun registerUpdatedExtension(extension: Extension.Installed) { private fun registerUpdatedExtension(extension: Extension.Installed) {
val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() _installedExtensionsMapFlow.value += extension
val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName }
if (oldExtension != null) {
mutInstalledExtensions -= oldExtension
}
mutInstalledExtensions += extension
_installedExtensionsFlow.value = mutInstalledExtensions
} }
/** /**
@ -313,14 +302,8 @@ class ExtensionManager(
* @param pkgName The package name of the uninstalled application. * @param pkgName The package name of the uninstalled application.
*/ */
private fun unregisterExtension(pkgName: String) { private fun unregisterExtension(pkgName: String) {
val installedExtension = _installedExtensionsFlow.value.find { it.pkgName == pkgName } _installedExtensionsMapFlow.value -= pkgName
if (installedExtension != null) { _untrustedExtensionsMapFlow.value -= pkgName
_installedExtensionsFlow.value -= installedExtension
}
val untrustedExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName }
if (untrustedExtension != null) {
_untrustedExtensionsFlow.value -= untrustedExtension
}
} }
/** /**
@ -339,14 +322,9 @@ class ExtensionManager(
} }
override fun onExtensionUntrusted(extension: Extension.Untrusted) { override fun onExtensionUntrusted(extension: Extension.Untrusted) {
val installedExtension = _installedExtensionsFlow.value _installedExtensionsMapFlow.value -= extension.pkgName
.find { it.pkgName == extension.pkgName } _untrustedExtensionsMapFlow.value += extension
updatePendingUpdatesCount()
if (installedExtension != null) {
_installedExtensionsFlow.value -= installedExtension
} else {
_untrustedExtensionsFlow.value += extension
}
} }
override fun onPackageUninstalled(pkgName: String) { override fun onPackageUninstalled(pkgName: String) {
@ -368,17 +346,24 @@ class ExtensionManager(
} }
private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean { private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean {
val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName } val availableExt = availableExtension
?: _availableExtensionsMapFlow.value[pkgName]
?: return false ?: return false
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
} }
private fun updatePendingUpdatesCount() { private fun updatePendingUpdatesCount() {
val pendingUpdateCount = _installedExtensionsFlow.value.count { it.hasUpdate } val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate }
preferences.extensionUpdatesCount().set(pendingUpdateCount) preferences.extensionUpdatesCount().set(pendingUpdateCount)
if (pendingUpdateCount == 0) { if (pendingUpdateCount == 0) {
ExtensionUpdateNotifier(context).dismiss() ExtensionUpdateNotifier(context).dismiss()
} }
} }
private operator fun <T : Extension> Map<String, T>.plus(extension: T) = plus(extension.pkgName to extension)
private fun <T : Extension> StateFlow<Map<String, T>>.mapExtensions(scope: CoroutineScope): StateFlow<List<T>> {
return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList())
}
} }

View File

@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.util.lang.launchNow
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
/** /**
@ -23,8 +18,7 @@ import tachiyomi.core.common.util.system.logcat
* *
* @param listener The listener that should be notified of extension installation events. * @param listener The listener that should be notified of extension installation events.
*/ */
internal class ExtensionInstallReceiver(private val listener: Listener) : internal class ExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() {
BroadcastReceiver() {
/** /**
* Registers this broadcast receiver * Registers this broadcast receiver
@ -36,16 +30,15 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
/** /**
* Returns the intent filter this receiver should subscribe to. * Returns the intent filter this receiver should subscribe to.
*/ */
private val filter private val filter = IntentFilter().apply {
get() = IntentFilter().apply { addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_ADDED) addAction(Intent.ACTION_PACKAGE_REPLACED)
addAction(Intent.ACTION_PACKAGE_REPLACED) addAction(Intent.ACTION_PACKAGE_REMOVED)
addAction(Intent.ACTION_PACKAGE_REMOVED) addAction(ACTION_EXTENSION_ADDED)
addAction(ACTION_EXTENSION_ADDED) addAction(ACTION_EXTENSION_REPLACED)
addAction(ACTION_EXTENSION_REPLACED) addAction(ACTION_EXTENSION_REMOVED)
addAction(ACTION_EXTENSION_REMOVED) addDataScheme("package")
addDataScheme("package") }
}
/** /**
* Called when one of the events of the [filter] is received. When the package is an extension, * Called when one of the events of the [filter] is received. When the package is an extension,
@ -58,21 +51,17 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> {
if (isReplacing(intent)) return if (isReplacing(intent)) return
launchNow { when (val result = getExtensionFromIntent(context, intent)) {
when (val result = getExtensionFromIntent(context, intent)) { is LoadResult.Success -> listener.onExtensionInstalled(result.extension)
is LoadResult.Success -> listener.onExtensionInstalled(result.extension) is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) else -> {}
else -> {}
}
} }
} }
Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> {
launchNow { when (val result = getExtensionFromIntent(context, intent)) {
when (val result = getExtensionFromIntent(context, intent)) { is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
is LoadResult.Success -> listener.onExtensionUpdated(result.extension) is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) else -> {}
else -> {}
}
} }
} }
Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> {
@ -101,15 +90,13 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
* @param context The application context. * @param context The application context.
* @param intent The intent containing the package name of the extension. * @param intent The intent containing the package name of the extension.
*/ */
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { private fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) val pkgName = getPackageNameFromIntent(intent)
if (pkgName == null) { if (pkgName == null) {
logcat(LogPriority.WARN) { "Package name not found" } logcat(LogPriority.WARN) { "Package name not found" }
return LoadResult.Error return LoadResult.Error
} }
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { return ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
}.await()
} }
/** /**