From 077b673c0aaa78cf94356cdfc43a4730e5b1f787 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sat, 4 May 2024 16:08:38 +0600 Subject: [PATCH] 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 --- .../interactor/GetExtensionsByType.kt | 6 +- .../tachiyomi/extension/ExtensionManager.kt | 155 ++++++++---------- .../util/ExtensionInstallReceiver.kt | 53 +++--- 3 files changed, 88 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt index 6c67b676c..77d51409a 100644 --- a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt +++ b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionsByType.kt @@ -20,7 +20,7 @@ class GetExtensionsByType( extensionManager.installedExtensionsFlow, extensionManager.untrustedExtensionsFlow, extensionManager.availableExtensionsFlow, - ) { _activeLanguages, _installed, _untrusted, _available -> + ) { enabledLanguages, _installed, _untrusted, _available -> val (updates, installed) = _installed .filter { (showNsfwSources || !it.isNsfw) } .sortedWith( @@ -41,9 +41,9 @@ class GetExtensionsByType( } .flatMap { ext -> 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 { ext.copy( name = it.name, diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 588dd98c5..105d58211 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -20,9 +20,8 @@ import exh.source.BlacklistedSources import exh.source.EH_SOURCE_ID import exh.source.EXH_SOURCE_ID import exh.source.MERGED_SOURCE_ID -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -32,7 +31,6 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import logcat.LogPriority -import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.source.model.StubSource @@ -54,6 +52,8 @@ class ExtensionManager( private val trustExtension: TrustExtension = Injekt.get(), ) { + val scope = CoroutineScope(SupervisorJob()) + // SY --> private val _isInitialized = MutableStateFlow(false) val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -71,13 +71,28 @@ class ExtensionManager( private val iconMap = mutableMapOf() - private val _installedExtensionsFlow = MutableStateFlow(emptyList()) - val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow() + private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap()) + val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope) + + private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap()) + val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope) + + private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap()) + val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope) + + init { + initExtensions() + ExtensionInstallReceiver(InstallationListener()).register(context) + } private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() 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) { return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo @@ -95,15 +110,6 @@ class ExtensionManager( // SY <-- } - private val _availableExtensionsFlow = MutableStateFlow(emptyList()) - - // SY --> - @OptIn(DelicateCoroutinesApi::class) - val availableExtensionsFlow = _availableExtensionsFlow - .map { it.filterNotBlacklisted() } - .stateIn(GlobalScope, SharingStarted.Eagerly, emptyList()) - // SY <-- - private var availableExtensionsSourcesData: Map = emptyMap() private fun setupAvailableExtensionsSourcesDataMap(extensions: List) { @@ -115,27 +121,19 @@ class ExtensionManager( fun getSourceData(id: Long) = availableExtensionsSourcesData[id] - private val _untrustedExtensionsFlow = MutableStateFlow(emptyList()) - val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow() - - init { - initExtensions() - ExtensionInstallReceiver(InstallationListener()).register(context) - } - /** * Loads and registers the installed extensions. */ private fun initExtensions() { val extensions = ExtensionLoader.loadExtensions(context) - _installedExtensionsFlow.value = extensions + _installedExtensionsMapFlow.value = extensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } - _untrustedExtensionsFlow.value = extensions + _untrustedExtensionsMapFlow.value = extensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } // SY --> .filterNotBlacklisted() @@ -144,9 +142,9 @@ class ExtensionManager( } // EXH --> - private fun Iterable.filterNotBlacklisted(): List { + private fun Map.filterNotBlacklisted(): Map { val blacklistEnabled = preferences.enableSourceBlacklist().get() - return filterNot { extension -> + return filterNot { (_, extension) -> extension.isBlacklisted(blacklistEnabled) .also { if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName) @@ -160,7 +158,7 @@ class ExtensionManager( // EXH <-- /** - * Finds the available extensions in the [api] and updates [availableExtensions]. + * Finds the available extensions in the [api] and updates [_availableExtensionsMapFlow]. */ suspend fun findAvailableExtensions() { val extensions: List = try { @@ -173,7 +171,7 @@ class ExtensionManager( enableAdditionalSubLanguages(extensions) - _availableExtensionsFlow.value = extensions + _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName } updatedInstalledExtensionsStatuses(extensions) setupAvailableExtensionsSourcesDataMap(extensions) } @@ -219,42 +217,38 @@ class ExtensionManager( return } - val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() + val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap() var changed = false - - for ((index, installedExt) in mutInstalledExtensions.withIndex()) { - val pkgName = installedExt.pkgName + for ((pkgName, extension) in installedExtensionsMap) { // SY --> - val availableExt = _availableExtensionsFlow.value.find { it.pkgName == pkgName } + val availableExt = availableExtensionsFlow.value.find { it.pkgName == pkgName } // SY <-- - if (availableExt == null && !installedExt.isObsolete) { - mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) + if (availableExt == null && !extension.isObsolete) { + installedExtensionsMap[pkgName] = extension.copy(isObsolete = true) changed = true // SY --> - } else if (installedExt.isBlacklisted() && !installedExt.isRedundant) { - mutInstalledExtensions[index] = installedExt.copy(isRedundant = true) + } else if (extension.isBlacklisted() && !extension.isRedundant) { + installedExtensionsMap[pkgName] = extension.copy(isRedundant = true) changed = true // SY <-- } else if (availableExt != null) { - val hasUpdate = installedExt.updateExists(availableExt) - - if (installedExt.hasUpdate != hasUpdate) { - mutInstalledExtensions[index] = installedExt.copy( + val hasUpdate = extension.updateExists(availableExt) + if (extension.hasUpdate != hasUpdate) { + installedExtensionsMap[pkgName] = extension.copy( hasUpdate = hasUpdate, repoUrl = availableExt.repoUrl, ) - changed = true } else { - mutInstalledExtensions[index] = installedExt.copy( + installedExtensionsMap[pkgName] = extension.copy( repoUrl = availableExt.repoUrl, ) - changed = true } + changed = true } } if (changed) { - _installedExtensionsFlow.value = mutInstalledExtensions + _installedExtensionsMapFlow.value = installedExtensionsMap } updatePendingUpdatesCount() } @@ -278,8 +272,7 @@ class ExtensionManager( * @param extension The extension to be updated. */ fun updateExtension(extension: Extension.Installed): Flow { - val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } - ?: return emptyFlow() + val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow() return installExtension(availableExt) } @@ -316,23 +309,15 @@ class ExtensionManager( * @param extension the extension to trust */ fun trust(extension: Extension.Untrusted) { - val untrustedPkgNames = _untrustedExtensionsFlow.value.map { it.pkgName }.toSet() - if (extension.pkgName !in untrustedPkgNames) return + _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) - val nowTrustedExtensions = _untrustedExtensionsFlow.value - .filter { it.pkgName == extension.pkgName && it.versionCode == extension.versionCode } - _untrustedExtensionsFlow.value -= nowTrustedExtensions + _untrustedExtensionsMapFlow.value -= extension.pkgName - launchNow { - nowTrustedExtensions - .map { extension -> - async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await() - } - .filterIsInstance() - .forEach { registerNewExtension(it.extension) } - } + ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) + .let { it as? LoadResult.Success } + ?.let { registerNewExtension(it.extension) } } /** @@ -348,7 +333,7 @@ class ExtensionManager( } // SY <-- - _installedExtensionsFlow.value += extension + _installedExtensionsMapFlow.value += extension } /** @@ -365,13 +350,7 @@ class ExtensionManager( } // SY <-- - val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() - val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName } - if (oldExtension != null) { - mutInstalledExtensions -= oldExtension - } - mutInstalledExtensions += extension - _installedExtensionsFlow.value = mutInstalledExtensions + _installedExtensionsMapFlow.value += extension } /** @@ -381,14 +360,8 @@ class ExtensionManager( * @param pkgName The package name of the uninstalled application. */ private fun unregisterExtension(pkgName: String) { - val installedExtension = _installedExtensionsFlow.value.find { it.pkgName == pkgName } - if (installedExtension != null) { - _installedExtensionsFlow.value -= installedExtension - } - val untrustedExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName } - if (untrustedExtension != null) { - _untrustedExtensionsFlow.value -= untrustedExtension - } + _installedExtensionsMapFlow.value -= pkgName + _untrustedExtensionsMapFlow.value -= pkgName } /** @@ -407,14 +380,9 @@ class ExtensionManager( } override fun onExtensionUntrusted(extension: Extension.Untrusted) { - val installedExtension = _installedExtensionsFlow.value - .find { it.pkgName == extension.pkgName } - - if (installedExtension != null) { - _installedExtensionsFlow.value -= installedExtension - } else { - _untrustedExtensionsFlow.value += extension - } + _installedExtensionsMapFlow.value -= extension.pkgName + _untrustedExtensionsMapFlow.value += extension + updatePendingUpdatesCount() } override fun onPackageUninstalled(pkgName: String) { @@ -436,17 +404,24 @@ class ExtensionManager( } 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 (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) } private fun updatePendingUpdatesCount() { - val pendingUpdateCount = _installedExtensionsFlow.value.count { it.hasUpdate } + val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate } preferences.extensionUpdatesCount().set(pendingUpdateCount) if (pendingUpdateCount == 0) { ExtensionUpdateNotifier(context).dismiss() } } + + private operator fun Map.plus(extension: T) = plus(extension.pkgName to extension) + + private fun StateFlow>.mapExtensions(scope: CoroutineScope): StateFlow> { + return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList()) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt index e0a008e1c..be9a60a75 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt @@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.model.Extension 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 tachiyomi.core.common.util.lang.launchNow 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. */ -internal class ExtensionInstallReceiver(private val listener: Listener) : - BroadcastReceiver() { +internal class ExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { /** * Registers this broadcast receiver @@ -36,16 +30,15 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : /** * Returns the intent filter this receiver should subscribe to. */ - private val filter - get() = IntentFilter().apply { - addAction(Intent.ACTION_PACKAGE_ADDED) - addAction(Intent.ACTION_PACKAGE_REPLACED) - addAction(Intent.ACTION_PACKAGE_REMOVED) - addAction(ACTION_EXTENSION_ADDED) - addAction(ACTION_EXTENSION_REPLACED) - addAction(ACTION_EXTENSION_REMOVED) - addDataScheme("package") - } + private val filter = IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REPLACED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(ACTION_EXTENSION_ADDED) + addAction(ACTION_EXTENSION_REPLACED) + addAction(ACTION_EXTENSION_REMOVED) + addDataScheme("package") + } /** * 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 -> { if (isReplacing(intent)) return - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is LoadResult.Success -> listener.onExtensionInstalled(result.extension) - is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is LoadResult.Success -> listener.onExtensionInstalled(result.extension) + is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is LoadResult.Success -> listener.onExtensionUpdated(result.extension) - is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is LoadResult.Success -> listener.onExtensionUpdated(result.extension) + is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { @@ -101,15 +90,13 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : * @param context The application context. * @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) if (pkgName == null) { logcat(LogPriority.WARN) { "Package name not found" } return LoadResult.Error } - return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { - ExtensionLoader.loadExtensionFromPkgName(context, pkgName) - }.await() + return ExtensionLoader.loadExtensionFromPkgName(context, pkgName) } /**