mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-26 23:18:17 +03:00
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:
parent
1fb04f3e21
commit
482425f7b2
6 changed files with 186 additions and 268 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue