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

(cherry picked from commit 21145144cdf550aa775047603e06e261951ebc42)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
This commit is contained in:
AntsyLich 2024-05-04 16:08:38 +06:00 committed by Jobobby04
parent 49eacf5178
commit 077b673c0a
3 changed files with 88 additions and 126 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(
@ -41,9 +41,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

@ -20,9 +20,8 @@ import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
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.SharingStarted
@ -32,7 +31,6 @@ import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn 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
@ -54,6 +52,8 @@ class ExtensionManager(
private val trustExtension: TrustExtension = Injekt.get(), private val trustExtension: TrustExtension = Injekt.get(),
) { ) {
val scope = CoroutineScope(SupervisorJob())
// SY --> // SY -->
private val _isInitialized = MutableStateFlow(false) private val _isInitialized = MutableStateFlow(false)
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow() val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
@ -71,13 +71,28 @@ 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
.find { ext ->
ext.sources.any { it.id == sourceId }
}
?.pkgName
if (pkgName != null) { if (pkgName != null) {
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
@ -95,15 +110,6 @@ class ExtensionManager(
// SY <-- // SY <--
} }
private val _availableExtensionsFlow = MutableStateFlow(emptyList<Extension.Available>())
// SY -->
@OptIn(DelicateCoroutinesApi::class)
val availableExtensionsFlow = _availableExtensionsFlow
.map { it.filterNotBlacklisted() }
.stateIn(GlobalScope, SharingStarted.Eagerly, emptyList())
// SY <--
private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap() private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap()
private fun setupAvailableExtensionsSourcesDataMap(extensions: List<Extension.Available>) { private fun setupAvailableExtensionsSourcesDataMap(extensions: List<Extension.Available>) {
@ -115,27 +121,19 @@ 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 }
// SY --> // SY -->
.filterNotBlacklisted() .filterNotBlacklisted()
@ -144,9 +142,9 @@ class ExtensionManager(
} }
// EXH --> // EXH -->
private fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> { private fun <T : Extension> Map<String, T>.filterNotBlacklisted(): Map<String, T> {
val blacklistEnabled = preferences.enableSourceBlacklist().get() val blacklistEnabled = preferences.enableSourceBlacklist().get()
return filterNot { extension -> return filterNot { (_, extension) ->
extension.isBlacklisted(blacklistEnabled) extension.isBlacklisted(blacklistEnabled)
.also { .also {
if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName) if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName)
@ -160,7 +158,7 @@ class ExtensionManager(
// EXH <-- // EXH <--
/** /**
* 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 {
@ -173,7 +171,7 @@ class ExtensionManager(
enableAdditionalSubLanguages(extensions) enableAdditionalSubLanguages(extensions)
_availableExtensionsFlow.value = extensions _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName }
updatedInstalledExtensionsStatuses(extensions) updatedInstalledExtensionsStatuses(extensions)
setupAvailableExtensionsSourcesDataMap(extensions) setupAvailableExtensionsSourcesDataMap(extensions)
} }
@ -219,42 +217,38 @@ 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
// SY --> // SY -->
val availableExt = _availableExtensionsFlow.value.find { it.pkgName == pkgName } val availableExt = availableExtensionsFlow.value.find { it.pkgName == pkgName }
// SY <-- // SY <--
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
// SY --> // SY -->
} else if (installedExt.isBlacklisted() && !installedExt.isRedundant) { } else if (extension.isBlacklisted() && !extension.isRedundant) {
mutInstalledExtensions[index] = installedExt.copy(isRedundant = true) installedExtensionsMap[pkgName] = extension.copy(isRedundant = true)
changed = true changed = true
// SY <-- // SY <--
} 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()
} }
@ -278,8 +272,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)
} }
@ -316,23 +309,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) }
}
} }
/** /**
@ -348,7 +333,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
_installedExtensionsFlow.value += extension _installedExtensionsMapFlow.value += extension
} }
/** /**
@ -365,13 +350,7 @@ class ExtensionManager(
} }
// SY <-- // SY <--
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
} }
/** /**
@ -381,14 +360,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
}
} }
/** /**
@ -407,14 +380,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) {
@ -436,17 +404,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()
} }
/** /**