From 482425f7b2ea19e0b1907b21a351e94cbf7b5102 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 22:02:56 +0200 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 Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../interactor/GetAnimeExtensionsByType.kt | 6 +- .../interactor/GetMangaExtensionsByType.kt | 6 +- .../extension/anime/AnimeExtensionManager.kt | 163 ++++++++---------- .../util/AnimeExtensionInstallReceiver.kt | 61 +++---- .../extension/manga/MangaExtensionManager.kt | 157 ++++++++--------- .../util/MangaExtensionInstallReceiver.kt | 61 +++---- 6 files changed, 186 insertions(+), 268 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt index 62d38df6e..86f898f26 100644 --- a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt +++ b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt @@ -20,7 +20,7 @@ class GetAnimeExtensionsByType( extensionManager.installedExtensionsFlow, extensionManager.untrustedExtensionsFlow, extensionManager.availableExtensionsFlow, - ) { _activeLanguages, _installed, _untrusted, _available -> + ) { enabledLanguages, _installed, _untrusted, _available -> val (updates, installed) = _installed .filter { (showNsfwSources || !it.isNsfw) } .sortedWith( @@ -40,9 +40,9 @@ class GetAnimeExtensionsByType( } .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/domain/extension/manga/interactor/GetMangaExtensionsByType.kt b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/GetMangaExtensionsByType.kt index f03533804..408b2fa64 100644 --- a/app/src/main/java/eu/kanade/domain/extension/manga/interactor/GetMangaExtensionsByType.kt +++ b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/GetMangaExtensionsByType.kt @@ -20,7 +20,7 @@ class GetMangaExtensionsByType( extensionManager.installedExtensionsFlow, extensionManager.untrustedExtensionsFlow, extensionManager.availableExtensionsFlow, - ) { _activeLanguages, _installed, _untrusted, _available -> + ) { enabledLanguages, _installed, _untrusted, _available -> val (updates, installed) = _installed .filter { (showNsfwSources || !it.isNsfw) } .sortedWith( @@ -40,9 +40,9 @@ class GetMangaExtensionsByType( } .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/anime/AnimeExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt index e2bf60793..ad51eda0a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt @@ -13,14 +13,17 @@ import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallReceiver import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader 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.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow 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.anime.model.StubAnimeSource @@ -45,6 +48,8 @@ class AnimeExtensionManager( private val trustExtension: TrustAnimeExtension = Injekt.get(), ) { + val scope = CoroutineScope(SupervisorJob()) + private val _isInitialized = MutableStateFlow(false) val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -60,28 +65,35 @@ class AnimeExtensionManager( private val iconMap = mutableMapOf() - private val _installedAnimeExtensionsFlow = MutableStateFlow( - emptyList(), - ) - val installedExtensionsFlow = _installedAnimeExtensionsFlow.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 { + initAnimeExtensions() + AnimeExtensionInstallReceiver(AnimeInstallationListener()).register(context) + } private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() fun getAppIconForSource(sourceId: Long): Drawable? { - val pkgName = _installedAnimeExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName - if (pkgName != null) { - return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { - AnimeExtensionLoader.getAnimeExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo - .loadIcon(context.packageManager) + val pkgName = _installedExtensionsMapFlow.value.values + .find { ext -> + ext.sources.any { it.id == sourceId } } - } - return null - } + ?.pkgName + ?: return null - private val _availableExtensionsFlow = MutableStateFlow( - emptyList(), - ) - val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow() + return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { + AnimeExtensionLoader.getAnimeExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo + .loadIcon(context.packageManager) + } + } private var availableAnimeExtensionsSourcesData: Map = emptyMap() @@ -96,35 +108,25 @@ class AnimeExtensionManager( fun getSourceData(id: Long) = availableAnimeExtensionsSourcesData[id] - private val _untrustedExtensionsFlow = MutableStateFlow( - emptyList(), - ) - val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow() - - init { - initAnimeExtensions() - AnimeExtensionInstallReceiver(AnimeInstallationListener()).register(context) - } - /** * Loads and registers the installed animeextensions. */ private fun initAnimeExtensions() { val animeextensions = AnimeExtensionLoader.loadExtensions(context) - _installedAnimeExtensionsFlow.value = animeextensions + _installedExtensionsMapFlow.value = animeextensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } - _untrustedExtensionsFlow.value = animeextensions + _untrustedExtensionsMapFlow.value = animeextensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } _isInitialized.value = true } /** - * Finds the available anime extensions in the [api] and updates [availableExtensions]. + * Finds the available anime extensions in the [api] and updates [_availableExtensionsMapFlow]. */ suspend fun findAvailableExtensions() { val extensions: List = try { @@ -137,7 +139,7 @@ class AnimeExtensionManager( enableAdditionalSubLanguages(extensions) - _availableExtensionsFlow.value = extensions + _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName } updatedInstalledAnimeExtensionsStatuses(extensions) setupAvailableAnimeExtensionsSourcesDataMap(extensions) } @@ -185,35 +187,32 @@ class AnimeExtensionManager( return } - val mutInstalledExtensions = _installedAnimeExtensionsFlow.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) { val availableExt = availableExtensions.find { it.pkgName == pkgName } - if (availableExt == null && !installedExt.isObsolete) { - mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) + if (availableExt == null && !extension.isObsolete) { + installedExtensionsMap[pkgName] = extension.copy(isObsolete = true) changed = true } 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) { - _installedAnimeExtensionsFlow.value = mutInstalledExtensions + _installedExtensionsMapFlow.value = installedExtensionsMap } updatePendingUpdatesCount() } @@ -237,8 +236,7 @@ class AnimeExtensionManager( * @param extension The anime extension to be updated. */ fun updateExtension(extension: AnimeExtension.Installed): Flow { - val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } - ?: return emptyFlow() + val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow() return installExtension(availableExt) } @@ -275,28 +273,15 @@ class AnimeExtensionManager( * @param extension the extension to trust */ fun trust(extension: AnimeExtension.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 { - AnimeExtensionLoader.loadExtensionFromPkgName( - context, - extension.pkgName, - ) - }.await() - } - .filterIsInstance() - .forEach { registerNewExtension(it.extension) } - } + AnimeExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) + .let { it as? AnimeLoadResult.Success } + ?.let { registerNewExtension(it.extension) } } /** @@ -305,7 +290,7 @@ class AnimeExtensionManager( * @param extension The anime extension to be registered. */ private fun registerNewExtension(extension: AnimeExtension.Installed) { - _installedAnimeExtensionsFlow.value += extension + _installedExtensionsMapFlow.value += extension } /** @@ -315,13 +300,7 @@ class AnimeExtensionManager( * @param extension The anime extension to be registered. */ private fun registerUpdatedExtension(extension: AnimeExtension.Installed) { - val mutInstalledAnimeExtensions = _installedAnimeExtensionsFlow.value.toMutableList() - val oldAnimeExtension = mutInstalledAnimeExtensions.find { it.pkgName == extension.pkgName } - if (oldAnimeExtension != null) { - mutInstalledAnimeExtensions -= oldAnimeExtension - } - mutInstalledAnimeExtensions += extension - _installedAnimeExtensionsFlow.value = mutInstalledAnimeExtensions + _installedExtensionsMapFlow.value += extension } /** @@ -331,14 +310,8 @@ class AnimeExtensionManager( * @param pkgName The package name of the uninstalled application. */ private fun unregisterAnimeExtension(pkgName: String) { - val installedAnimeExtension = _installedAnimeExtensionsFlow.value.find { it.pkgName == pkgName } - if (installedAnimeExtension != null) { - _installedAnimeExtensionsFlow.value -= installedAnimeExtension - } - val untrustedAnimeExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName } - if (untrustedAnimeExtension != null) { - _untrustedExtensionsFlow.value -= untrustedAnimeExtension - } + _installedExtensionsMapFlow.value -= pkgName + _untrustedExtensionsMapFlow.value -= pkgName } /** @@ -357,14 +330,9 @@ class AnimeExtensionManager( } override fun onExtensionUntrusted(extension: AnimeExtension.Untrusted) { - val installedExtension = _installedAnimeExtensionsFlow.value - .find { it.pkgName == extension.pkgName } - - if (installedExtension != null) { - _installedAnimeExtensionsFlow.value -= installedExtension - } else { - _untrustedExtensionsFlow.value += extension - } + _installedExtensionsMapFlow.value -= extension.pkgName + _untrustedExtensionsMapFlow.value += extension + updatePendingUpdatesCount() } override fun onPackageUninstalled(pkgName: String) { @@ -388,17 +356,26 @@ class AnimeExtensionManager( private fun AnimeExtension.Installed.updateExists( availableExtension: AnimeExtension.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 = _installedAnimeExtensionsFlow.value.count { it.hasUpdate } + val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate } preferences.animeExtensionUpdatesCount().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/anime/util/AnimeExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt index 76a2a9153..d8323868d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt @@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult -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 AnimeExtensionInstallReceiver(private val listener: Listener) : - BroadcastReceiver() { +internal class AnimeExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { /** * Registers this broadcast receiver @@ -36,16 +30,15 @@ internal class AnimeExtensionInstallReceiver(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,26 +51,17 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) : Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { if (isReplacing(intent)) return - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is AnimeLoadResult.Success -> listener.onExtensionInstalled( - result.extension, - ) - - is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted( - result.extension, - ) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is AnimeLoadResult.Success -> listener.onExtensionInstalled(result.extension) + is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is AnimeLoadResult.Success -> listener.onExtensionUpdated(result.extension) - is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is AnimeLoadResult.Success -> listener.onExtensionUpdated(result.extension) + is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { @@ -106,18 +90,13 @@ internal class AnimeExtensionInstallReceiver(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?): AnimeLoadResult { + private fun getExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult { val pkgName = getPackageNameFromIntent(intent) if (pkgName == null) { logcat(LogPriority.WARN) { "Package name not found" } return AnimeLoadResult.Error } - return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { - AnimeExtensionLoader.loadExtensionFromPkgName( - context, - pkgName, - ) - }.await() + return AnimeExtensionLoader.loadExtensionFromPkgName(context, pkgName) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt index e1bd67ff5..7c1b3235e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt @@ -13,14 +13,17 @@ import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader 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.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow 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.manga.model.StubMangaSource @@ -42,6 +45,8 @@ class MangaExtensionManager( private val trustExtension: TrustMangaExtension = Injekt.get(), ) { + val scope = CoroutineScope(SupervisorJob()) + private val _isInitialized = MutableStateFlow(false) val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -57,24 +62,35 @@ class MangaExtensionManager( 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() + MangaExtensionInstallReceiver(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 - if (pkgName != null) { - return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { - MangaExtensionLoader.getMangaExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo - .loadIcon(context.packageManager) + val pkgName = _installedExtensionsMapFlow.value.values + .find { ext -> + ext.sources.any { it.id == sourceId } } - } - return null - } + ?.pkgName + ?: return null - private val _availableExtensionsFlow = MutableStateFlow(emptyList()) - val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow() + return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { + MangaExtensionLoader.getMangaExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo + .loadIcon(context.packageManager) + } + } private var availableExtensionsSourcesData: Map = emptyMap() @@ -87,33 +103,25 @@ class MangaExtensionManager( fun getSourceData(id: Long) = availableExtensionsSourcesData[id] - private val _untrustedExtensionsFlow = MutableStateFlow(emptyList()) - val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow() - - init { - initExtensions() - MangaExtensionInstallReceiver(InstallationListener()).register(context) - } - /** * Loads and registers the installed extensions. */ private fun initExtensions() { val extensions = MangaExtensionLoader.loadMangaExtensions(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 } _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() { val extensions: List = try { @@ -126,7 +134,7 @@ class MangaExtensionManager( enableAdditionalSubLanguages(extensions) - _availableExtensionsFlow.value = extensions + _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName } updatedInstalledExtensionsStatuses(extensions) setupAvailableExtensionsSourcesDataMap(extensions) } @@ -174,35 +182,32 @@ class MangaExtensionManager( 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) { val availableExt = availableExtensions.find { it.pkgName == pkgName } - if (availableExt == null && !installedExt.isObsolete) { - mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) + if (availableExt == null && !extension.isObsolete) { + installedExtensionsMap[pkgName] = extension.copy(isObsolete = true) changed = true } 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() } @@ -226,8 +231,7 @@ class MangaExtensionManager( * @param extension The extension to be updated. */ fun updateExtension(extension: MangaExtension.Installed): Flow { - val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } - ?: return emptyFlow() + val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow() return installExtension(availableExt) } @@ -264,28 +268,15 @@ class MangaExtensionManager( * @param extension the extension to trust */ fun trust(extension: MangaExtension.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 { - MangaExtensionLoader.loadMangaExtensionFromPkgName( - context, - extension.pkgName, - ) - }.await() - } - .filterIsInstance() - .forEach { registerNewExtension(it.extension) } - } + MangaExtensionLoader.loadMangaExtensionFromPkgName(context, extension.pkgName) + .let { it as? MangaLoadResult.Success } + ?.let { registerNewExtension(it.extension) } } /** @@ -294,7 +285,7 @@ class MangaExtensionManager( * @param extension The extension to be registered. */ private fun registerNewExtension(extension: MangaExtension.Installed) { - _installedExtensionsFlow.value += extension + _installedExtensionsMapFlow.value += extension } /** @@ -304,13 +295,7 @@ class MangaExtensionManager( * @param extension The extension to be registered. */ private fun registerUpdatedExtension(extension: MangaExtension.Installed) { - 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 } /** @@ -320,14 +305,8 @@ class MangaExtensionManager( * @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 } /** @@ -346,14 +325,9 @@ class MangaExtensionManager( } override fun onExtensionUntrusted(extension: MangaExtension.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) { @@ -377,17 +351,26 @@ class MangaExtensionManager( private fun MangaExtension.Installed.updateExists( availableExtension: MangaExtension.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.mangaExtensionUpdatesCount().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/manga/util/MangaExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt index 2a94580bd..1bb300ba3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt @@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult -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 MangaExtensionInstallReceiver(private val listener: Listener) : - BroadcastReceiver() { +internal class MangaExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { /** * Registers this broadcast receiver @@ -36,16 +30,15 @@ internal class MangaExtensionInstallReceiver(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,26 +51,17 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) : Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { if (isReplacing(intent)) return - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is MangaLoadResult.Success -> listener.onExtensionInstalled( - result.extension, - ) - - is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted( - result.extension, - ) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionInstalled(result.extension) + is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is MangaLoadResult.Success -> listener.onExtensionUpdated(result.extension) - is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionUpdated(result.extension) + is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { @@ -106,18 +90,13 @@ internal class MangaExtensionInstallReceiver(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?): MangaLoadResult { + private fun getExtensionFromIntent(context: Context, intent: Intent?): MangaLoadResult { val pkgName = getPackageNameFromIntent(intent) if (pkgName == null) { logcat(LogPriority.WARN) { "Package name not found" } return MangaLoadResult.Error } - return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { - MangaExtensionLoader.loadMangaExtensionFromPkgName( - context, - pkgName, - ) - }.await() + return MangaExtensionLoader.loadMangaExtensionFromPkgName(context, pkgName) } /**