Skip to content

Commit

Permalink
Fix some extension related issue and cleanups
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
AntsyLich committed May 4, 2024
1 parent 134e464 commit 2114514
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class GetExtensionsByType(
extensionManager.installedExtensionsFlow,
extensionManager.untrustedExtensionsFlow,
extensionManager.availableExtensionsFlow,
) { _activeLanguages, _installed, _untrusted, _available ->
) { enabledLanguages, _installed, _untrusted, _available ->
val (updates, installed) = _installed
.filter { (showNsfwSources || !it.isNsfw) }
.sortedWith(
Expand All @@ -40,9 +40,9 @@ class GetExtensionsByType(
}
.flatMap { ext ->
if (ext.sources.isEmpty()) {
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
}
ext.sources.filter { it.lang in _activeLanguages }
ext.sources.filter { it.lang in enabledLanguages }
.map {
ext.copy(
name = it.name,
Expand Down
149 changes: 67 additions & 82 deletions app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
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.model.StubSource
Expand All @@ -42,6 +45,8 @@ class ExtensionManager(
private val trustExtension: TrustExtension = Injekt.get(),
) {

val scope = CoroutineScope(SupervisorJob())

private val _isInitialized = MutableStateFlow(false)
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()

Expand All @@ -57,25 +62,36 @@ class ExtensionManager(

private val iconMap = mutableMapOf<String, Drawable>()

private val _installedExtensionsFlow = MutableStateFlow(emptyList<Extension.Installed>())
val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow()
private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope)

private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Available>())
val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope)

private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap<String, Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope)

init {
initExtensions()
ExtensionInstallReceiver(InstallationListener()).register(context)
}

private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()

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) {
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
.loadIcon(context.packageManager)
val pkgName = _installedExtensionsMapFlow.value.values
.find { ext ->
ext.sources.any { it.id == sourceId }
}
?.pkgName
?: return null

return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) {
ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
.loadIcon(context.packageManager)
}
return null
}

private val _availableExtensionsFlow = MutableStateFlow(emptyList<Extension.Available>())
val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow()

private var availableExtensionsSourcesData: Map<Long, StubSource> = emptyMap()

private fun setupAvailableExtensionsSourcesDataMap(extensions: List<Extension.Available>) {
Expand All @@ -87,33 +103,25 @@ class ExtensionManager(

fun getSourceData(id: Long) = availableExtensionsSourcesData[id]

private val _untrustedExtensionsFlow = MutableStateFlow(emptyList<Extension.Untrusted>())
val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow()

init {
initExtensions()
ExtensionInstallReceiver(InstallationListener()).register(context)
}

/**
* Loads and registers the installed extensions.
*/
private fun initExtensions() {
val extensions = ExtensionLoader.loadExtensions(context)

_installedExtensionsFlow.value = extensions
_installedExtensionsMapFlow.value = extensions
.filterIsInstance<LoadResult.Success>()
.map { it.extension }
.associate { it.extension.pkgName to it.extension }

_untrustedExtensionsFlow.value = extensions
_untrustedExtensionsMapFlow.value = extensions
.filterIsInstance<LoadResult.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<Extension.Available> = try {
Expand All @@ -126,7 +134,7 @@ class ExtensionManager(

enableAdditionalSubLanguages(extensions)

_availableExtensionsFlow.value = extensions
_availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName }
updatedInstalledExtensionsStatuses(extensions)
setupAvailableExtensionsSourcesDataMap(extensions)
}
Expand Down Expand Up @@ -172,35 +180,31 @@ class ExtensionManager(
return
}

val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList()
val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap()
var changed = false

for ((index, installedExt) in mutInstalledExtensions.withIndex()) {
val pkgName = installedExt.pkgName
for ((pkgName, extension) in installedExtensionsMap) {
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()
}
Expand All @@ -224,8 +228,7 @@ class ExtensionManager(
* @param extension The extension to be updated.
*/
fun updateExtension(extension: Extension.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)
}

Expand Down Expand Up @@ -262,23 +265,15 @@ class ExtensionManager(
* @param extension the extension to trust
*/
fun trust(extension: Extension.Untrusted) {
val untrustedPkgNames = _untrustedExtensionsFlow.value.map { it.pkgName }.toSet()
if (extension.pkgName !in untrustedPkgNames) return
_untrustedExtensionsMapFlow.value[extension.pkgName] ?: return

trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash)

val nowTrustedExtensions = _untrustedExtensionsFlow.value
.filter { it.pkgName == extension.pkgName && it.versionCode == extension.versionCode }
_untrustedExtensionsFlow.value -= nowTrustedExtensions
_untrustedExtensionsMapFlow.value -= extension.pkgName

launchNow {
nowTrustedExtensions
.map { extension ->
async { ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) }.await()
}
.filterIsInstance<LoadResult.Success>()
.forEach { registerNewExtension(it.extension) }
}
ExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName)
.let { it as? LoadResult.Success }
?.let { registerNewExtension(it.extension) }
}

/**
Expand All @@ -287,7 +282,7 @@ class ExtensionManager(
* @param extension The extension to be registered.
*/
private fun registerNewExtension(extension: Extension.Installed) {
_installedExtensionsFlow.value += extension
_installedExtensionsMapFlow.value += extension
}

/**
Expand All @@ -297,13 +292,7 @@ class ExtensionManager(
* @param extension The extension to be registered.
*/
private fun registerUpdatedExtension(extension: Extension.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
}

/**
Expand All @@ -313,14 +302,8 @@ class ExtensionManager(
* @param pkgName The package name of the uninstalled application.
*/
private fun unregisterExtension(pkgName: String) {
val installedExtension = _installedExtensionsFlow.value.find { it.pkgName == pkgName }
if (installedExtension != null) {
_installedExtensionsFlow.value -= installedExtension
}
val untrustedExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName }
if (untrustedExtension != null) {
_untrustedExtensionsFlow.value -= untrustedExtension
}
_installedExtensionsMapFlow.value -= pkgName
_untrustedExtensionsMapFlow.value -= pkgName
}

/**
Expand All @@ -339,14 +322,9 @@ class ExtensionManager(
}

override fun onExtensionUntrusted(extension: Extension.Untrusted) {
val installedExtension = _installedExtensionsFlow.value
.find { it.pkgName == extension.pkgName }

if (installedExtension != null) {
_installedExtensionsFlow.value -= installedExtension
} else {
_untrustedExtensionsFlow.value += extension
}
_installedExtensionsMapFlow.value -= extension.pkgName
_untrustedExtensionsMapFlow.value += extension
updatePendingUpdatesCount()
}

override fun onPackageUninstalled(pkgName: String) {
Expand All @@ -368,17 +346,24 @@ class ExtensionManager(
}

private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean {
val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
val availableExt = availableExtension
?: _availableExtensionsMapFlow.value[pkgName]
?: return false

return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
}

private fun updatePendingUpdatesCount() {
val pendingUpdateCount = _installedExtensionsFlow.value.count { it.hasUpdate }
val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate }
preferences.extensionUpdatesCount().set(pendingUpdateCount)
if (pendingUpdateCount == 0) {
ExtensionUpdateNotifier(context).dismiss()
}
}

private operator fun <T : Extension> Map<String, T>.plus(extension: T) = plus(extension.pkgName to extension)

private fun <T : Extension> StateFlow<Map<String, T>>.mapExtensions(scope: CoroutineScope): StateFlow<List<T>> {
return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList())
}
}
Loading

0 comments on commit 2114514

Please sign in to comment.