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>
This commit is contained in:
Secozzi 2024-07-11 22:02:56 +02:00
parent 1fb04f3e21
commit 482425f7b2
No known key found for this signature in database
GPG key ID: 71E9C97D8DDC2662
6 changed files with 186 additions and 268 deletions

View file

@ -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,

View file

@ -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,

View file

@ -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<Boolean> = _isInitialized.asStateFlow()
@ -60,28 +65,35 @@ class AnimeExtensionManager(
private val iconMap = mutableMapOf<String, Drawable>()
private val _installedAnimeExtensionsFlow = MutableStateFlow(
emptyList<AnimeExtension.Installed>(),
)
val installedExtensionsFlow = _installedAnimeExtensionsFlow.asStateFlow()
private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, AnimeExtension.Installed>())
val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope)
private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, AnimeExtension.Available>())
val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope)
private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, AnimeExtension.Untrusted>())
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<AnimeExtension.Available>(),
)
val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow()
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
AnimeExtensionLoader.getAnimeExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
.loadIcon(context.packageManager)
}
}
private var availableAnimeExtensionsSourcesData: Map<Long, StubAnimeSource> = emptyMap()
@ -96,35 +108,25 @@ class AnimeExtensionManager(
fun getSourceData(id: Long) = availableAnimeExtensionsSourcesData[id]
private val _untrustedExtensionsFlow = MutableStateFlow(
emptyList<AnimeExtension.Untrusted>(),
)
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<AnimeLoadResult.Success>()
.map { it.extension }
.associate { it.extension.pkgName to it.extension }
_untrustedExtensionsFlow.value = animeextensions
_untrustedExtensionsMapFlow.value = animeextensions
.filterIsInstance<AnimeLoadResult.Untrusted>()
.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<AnimeExtension.Available> = 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<InstallStep> {
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<AnimeLoadResult.Success>()
.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 <T : AnimeExtension> Map<String, T>.plus(extension: T) = plus(extension.pkgName to extension)
private fun <T : AnimeExtension> 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.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)
}
/**

View file

@ -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<Boolean> = _isInitialized.asStateFlow()
@ -57,24 +62,35 @@ class MangaExtensionManager(
private val iconMap = mutableMapOf<String, Drawable>()
private val _installedExtensionsFlow = MutableStateFlow(emptyList<MangaExtension.Installed>())
val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow()
private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, MangaExtension.Installed>())
val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope)
private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, MangaExtension.Available>())
val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope)
private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, MangaExtension.Untrusted>())
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<MangaExtension.Available>())
val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow()
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
MangaExtensionLoader.getMangaExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
.loadIcon(context.packageManager)
}
}
private var availableExtensionsSourcesData: Map<Long, StubMangaSource> = emptyMap()
@ -87,33 +103,25 @@ class MangaExtensionManager(
fun getSourceData(id: Long) = availableExtensionsSourcesData[id]
private val _untrustedExtensionsFlow = MutableStateFlow(emptyList<MangaExtension.Untrusted>())
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<MangaLoadResult.Success>()
.map { it.extension }
.associate { it.extension.pkgName to it.extension }
_untrustedExtensionsFlow.value = extensions
_untrustedExtensionsMapFlow.value = extensions
.filterIsInstance<MangaLoadResult.Untrusted>()
.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<MangaExtension.Available> = 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<InstallStep> {
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<MangaLoadResult.Success>()
.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 <T : MangaExtension> Map<String, T>.plus(extension: T) = plus(extension.pkgName to extension)
private fun <T : MangaExtension> 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.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)
}
/**