mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-14 13:08:56 +01:00
Maintain source info in the database. (#6389)
* Maintain Source Info in database * Review changes and cleanups * Review changes 2 * Review Changes 3
This commit is contained in:
@@ -43,7 +43,12 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
||||
val sources = backup.backupSources.associate { it.sourceId to it.name }
|
||||
val missingSources = sources
|
||||
.filter { sourceManager.get(it.key) == null }
|
||||
.values
|
||||
.values.map {
|
||||
val id = it.toLongOrNull()
|
||||
if (id == null) it
|
||||
else sourceManager.getOrStub(id).toString()
|
||||
}
|
||||
.distinct()
|
||||
.sorted()
|
||||
|
||||
val trackers = backup.backupManga
|
||||
|
||||
@@ -71,7 +71,7 @@ class DownloadCache(
|
||||
*/
|
||||
fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean): Boolean {
|
||||
if (skipCache) {
|
||||
val source = sourceManager.get(manga.source) ?: return false
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
return provider.findChapterDir(chapter, manga, source) != null
|
||||
}
|
||||
|
||||
@@ -124,11 +124,15 @@ class DownloadCache(
|
||||
private fun renew() {
|
||||
val onlineSources = sourceManager.getOnlineSources()
|
||||
|
||||
val stubSources = sourceManager.getStubSources()
|
||||
|
||||
val allSource = onlineSources + stubSources
|
||||
|
||||
val sourceDirs = rootDir.dir.listFiles()
|
||||
.orEmpty()
|
||||
.associate { it.name to SourceDirectory(it) }
|
||||
.mapNotNullKeys { entry ->
|
||||
onlineSources.find { provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) }?.id
|
||||
allSource.find { provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) }?.id
|
||||
}
|
||||
|
||||
rootDir.files = sourceDirs
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.domain.source.model.SourceData
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||
@@ -90,8 +91,20 @@ class ExtensionManager(
|
||||
field = value
|
||||
availableExtensionsRelay.call(value)
|
||||
updatedInstalledExtensionsStatuses(value)
|
||||
setupAvailableExtensionsSourcesDataMap(value)
|
||||
}
|
||||
|
||||
private var availableExtensionsSourcesData: Map<Long, SourceData> = mapOf()
|
||||
|
||||
private fun setupAvailableExtensionsSourcesDataMap(extensions: List<Extension.Available>) {
|
||||
if (extensions.isEmpty()) return
|
||||
availableExtensionsSourcesData = extensions
|
||||
.flatMap { ext -> ext.sources.map { it.toSourceData() } }
|
||||
.associateBy { it.id }
|
||||
}
|
||||
|
||||
fun getSourceData(id: Long) = availableExtensionsSourcesData[id]
|
||||
|
||||
/**
|
||||
* Relay used to notify the untrusted extensions.
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.extension.api
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.model.AvailableExtensionSources
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.model.AvailableSources
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
@@ -22,6 +23,7 @@ internal class ExtensionGithubApi {
|
||||
|
||||
private val networkService: NetworkHelper by injectLazy()
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
private val extensionManager: ExtensionManager by injectLazy()
|
||||
|
||||
private var requiresFallbackSource = false
|
||||
|
||||
@@ -54,15 +56,17 @@ internal class ExtensionGithubApi {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkForUpdates(context: Context): List<Extension.Installed>? {
|
||||
suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List<Extension.Installed>? {
|
||||
// Limit checks to once a day at most
|
||||
if (Date().time < preferences.lastExtCheck().get() + TimeUnit.DAYS.toMillis(1)) {
|
||||
if (fromAvailableExtensionList.not() && Date().time < preferences.lastExtCheck().get() + TimeUnit.DAYS.toMillis(1)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val extensions = findExtensions()
|
||||
|
||||
preferences.lastExtCheck().set(Date().time)
|
||||
val extensions = if (fromAvailableExtensionList) {
|
||||
extensionManager.availableExtensions
|
||||
} else {
|
||||
findExtensions().also { preferences.lastExtCheck().set(Date().time) }
|
||||
}
|
||||
|
||||
val installedExtensions = ExtensionLoader.loadExtensions(context)
|
||||
.filterIsInstance<LoadResult.Success>()
|
||||
@@ -105,11 +109,12 @@ internal class ExtensionGithubApi {
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<ExtensionSourceJsonObject>.toExtensionSources(): List<AvailableExtensionSources> {
|
||||
private fun List<ExtensionSourceJsonObject>.toExtensionSources(): List<AvailableSources> {
|
||||
return this.map {
|
||||
AvailableExtensionSources(
|
||||
name = it.name,
|
||||
AvailableSources(
|
||||
id = it.id,
|
||||
lang = it.lang,
|
||||
name = it.name,
|
||||
baseUrl = it.baseUrl,
|
||||
)
|
||||
}
|
||||
@@ -147,7 +152,8 @@ private data class ExtensionJsonObject(
|
||||
|
||||
@Serializable
|
||||
private data class ExtensionSourceJsonObject(
|
||||
val name: String,
|
||||
val id: Long,
|
||||
val lang: String,
|
||||
val name: String,
|
||||
val baseUrl: String,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.extension.model
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import eu.kanade.domain.source.model.SourceData
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
|
||||
sealed class Extension {
|
||||
@@ -40,7 +41,7 @@ sealed class Extension {
|
||||
override val isNsfw: Boolean,
|
||||
override val hasReadme: Boolean,
|
||||
override val hasChangelog: Boolean,
|
||||
val sources: List<AvailableExtensionSources>,
|
||||
val sources: List<AvailableSources>,
|
||||
val apkName: String,
|
||||
val iconUrl: String,
|
||||
) : Extension()
|
||||
@@ -58,8 +59,17 @@ sealed class Extension {
|
||||
) : Extension()
|
||||
}
|
||||
|
||||
data class AvailableExtensionSources(
|
||||
val name: String,
|
||||
data class AvailableSources(
|
||||
val id: Long,
|
||||
val lang: String,
|
||||
val name: String,
|
||||
val baseUrl: String,
|
||||
)
|
||||
) {
|
||||
fun toSourceData(): SourceData {
|
||||
return SourceData(
|
||||
id = this.id,
|
||||
lang = this.lang,
|
||||
name = this.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.source
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import eu.kanade.domain.source.model.SourceData
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
@@ -102,3 +103,5 @@ interface Source : tachiyomi.source.Source {
|
||||
fun Source.icon(): Drawable? = Injekt.get<ExtensionManager>().getAppIconForSource(this)
|
||||
|
||||
fun Source.getPreferenceKey(): String = "source_$id"
|
||||
|
||||
fun Source.toSourceData(): SourceData = SourceData(id = id, lang = lang, name = name)
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
package eu.kanade.tachiyomi.source
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.domain.source.interactor.GetSourceData
|
||||
import eu.kanade.domain.source.interactor.UpsertSourceData
|
||||
import eu.kanade.domain.source.model.SourceData
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import rx.Observable
|
||||
import tachiyomi.source.model.ChapterInfo
|
||||
import tachiyomi.source.model.MangaInfo
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class SourceManager(private val context: Context) {
|
||||
|
||||
private val extensionManager: ExtensionManager by injectLazy()
|
||||
private val getSourceData: GetSourceData by injectLazy()
|
||||
private val upsertSourceData: UpsertSourceData by injectLazy()
|
||||
|
||||
private val sourcesMap = mutableMapOf<Long, Source>()
|
||||
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
||||
|
||||
@@ -34,7 +45,7 @@ class SourceManager(private val context: Context) {
|
||||
|
||||
fun getOrStub(sourceKey: Long): Source {
|
||||
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
||||
StubSource(sourceKey)
|
||||
runBlocking { createStubSource(sourceKey) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,16 +53,32 @@ class SourceManager(private val context: Context) {
|
||||
|
||||
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>()
|
||||
|
||||
fun getStubSources(): List<StubSource> {
|
||||
val onlineSourceIds = getOnlineSources().map { it.id }
|
||||
return stubSourcesMap.values.filterNot { it.id in onlineSourceIds }
|
||||
}
|
||||
|
||||
internal fun registerSource(source: Source) {
|
||||
if (!sourcesMap.containsKey(source.id)) {
|
||||
sourcesMap[source.id] = source
|
||||
}
|
||||
if (!stubSourcesMap.containsKey(source.id)) {
|
||||
stubSourcesMap[source.id] = StubSource(source.id)
|
||||
}
|
||||
registerStubSource(source.toSourceData())
|
||||
triggerCatalogueSources()
|
||||
}
|
||||
|
||||
private fun registerStubSource(sourceData: SourceData) {
|
||||
launchIO {
|
||||
val dbSourceData = getSourceData.await(sourceData.id)
|
||||
|
||||
if (dbSourceData != sourceData) {
|
||||
upsertSourceData.await(sourceData)
|
||||
}
|
||||
if (stubSourcesMap[sourceData.id]?.toSourceData() != sourceData) {
|
||||
stubSourcesMap[sourceData.id] = StubSource(sourceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun unregisterSource(source: Source) {
|
||||
sourcesMap.remove(source.id)
|
||||
triggerCatalogueSources()
|
||||
@@ -67,11 +94,24 @@ class SourceManager(private val context: Context) {
|
||||
LocalSource(context),
|
||||
)
|
||||
|
||||
private suspend fun createStubSource(id: Long): StubSource {
|
||||
getSourceData.await(id)?.let {
|
||||
return StubSource(it)
|
||||
}
|
||||
extensionManager.getSourceData(id)?.let {
|
||||
registerStubSource(it)
|
||||
return StubSource(it)
|
||||
}
|
||||
return StubSource(SourceData(id, "", ""))
|
||||
}
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
inner class StubSource(override val id: Long) : Source {
|
||||
open inner class StubSource(val sourceData: SourceData) : Source {
|
||||
|
||||
override val name: String
|
||||
get() = id.toString()
|
||||
override val name: String = sourceData.name
|
||||
|
||||
override val lang: String = sourceData.lang
|
||||
|
||||
override val id: Long = sourceData.id
|
||||
|
||||
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
|
||||
throw getSourceNotInstalledException()
|
||||
@@ -98,14 +138,17 @@ class SourceManager(private val context: Context) {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return name
|
||||
if (name.isNotBlank() && lang.isNotBlank()) {
|
||||
return "$name (${lang.uppercase()})"
|
||||
}
|
||||
return id.toString()
|
||||
}
|
||||
|
||||
private fun getSourceNotInstalledException(): SourceNotInstalledException {
|
||||
return SourceNotInstalledException(id)
|
||||
fun getSourceNotInstalledException(): SourceNotInstalledException {
|
||||
return SourceNotInstalledException(toString())
|
||||
}
|
||||
}
|
||||
|
||||
inner class SourceNotInstalledException(val id: Long) :
|
||||
Exception(context.getString(R.string.source_not_installed, id.toString()))
|
||||
inner class SourceNotInstalledException(val sourceString: String) :
|
||||
Exception(context.getString(R.string.source_not_installed, sourceString))
|
||||
}
|
||||
|
||||
@@ -366,7 +366,10 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
// Extension updates
|
||||
try {
|
||||
ExtensionGithubApi().checkForUpdates(this@MainActivity)?.let { pendingUpdates ->
|
||||
ExtensionGithubApi().checkForUpdates(
|
||||
this@MainActivity,
|
||||
fromAvailableExtensionList = true
|
||||
)?.let { pendingUpdates ->
|
||||
preferences.extensionUpdatesCount().set(pendingUpdates.size)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -1140,7 +1140,9 @@ class MangaController :
|
||||
|
||||
private fun downloadChapters(chapters: List<ChapterItem>) {
|
||||
if (source is SourceManager.StubSource) {
|
||||
activity?.toast(R.string.loader_not_implemented_error)
|
||||
activity?.let {
|
||||
it.toast(it.getString(R.string.source_not_installed, source?.toString().orEmpty()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -228,11 +228,7 @@ class MangaInfoHeaderAdapter(
|
||||
*/
|
||||
private fun setMangaInfo() {
|
||||
// Update full title TextView.
|
||||
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
|
||||
view.context.getString(R.string.unknown)
|
||||
} else {
|
||||
manga.title
|
||||
}
|
||||
binding.mangaFullTitle.text = manga.title.ifBlank { view.context.getString(R.string.unknown) }
|
||||
|
||||
// Update author TextView.
|
||||
binding.mangaAuthor.text = if (manga.author.isNullOrBlank()) {
|
||||
@@ -249,6 +245,8 @@ class MangaInfoHeaderAdapter(
|
||||
}
|
||||
|
||||
// If manga source is known update source TextView.
|
||||
binding.mangaMissingSourceIcon.isVisible = source is SourceManager.StubSource
|
||||
|
||||
val mangaSource = source.toString()
|
||||
with(binding.mangaSource) {
|
||||
val enabledLanguages = preferences.enabledLanguages().get()
|
||||
|
||||
@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
@@ -87,6 +88,7 @@ class ChapterLoader(
|
||||
is LocalSource.Format.Epub -> EpubPageLoader(format.file)
|
||||
}
|
||||
}
|
||||
source is SourceManager.StubSource -> throw source.getSourceNotInstalledException()
|
||||
else -> error(context.getString(R.string.loader_not_implemented_error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.ClearDatabaseSourceItemBinding
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.icon
|
||||
|
||||
data class ClearDatabaseSourceItem(val source: Source, private val mangaCount: Long) : AbstractFlexibleItem<ClearDatabaseSourceItem.Holder>() {
|
||||
@@ -37,9 +36,9 @@ data class ClearDatabaseSourceItem(val source: Source, private val mangaCount: L
|
||||
|
||||
itemView.post {
|
||||
when {
|
||||
source.id == LocalSource.ID -> binding.thumbnail.setImageResource(R.mipmap.ic_local_source)
|
||||
source is SourceManager.StubSource -> binding.thumbnail.setImageDrawable(null)
|
||||
source.icon() != null -> binding.thumbnail.setImageDrawable(source.icon())
|
||||
source.icon() != null && source.id != LocalSource.ID ->
|
||||
binding.thumbnail.setImageDrawable(source.icon())
|
||||
else -> binding.thumbnail.setImageResource(R.mipmap.ic_local_source)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user