mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 19:41:50 +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:
parent
a01c370d63
commit
9d5b7de1d8
@ -1,17 +1,24 @@
|
|||||||
package eu.kanade.data.source
|
package eu.kanade.data.source
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
|
||||||
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
val sourceMapper: (eu.kanade.tachiyomi.source.Source) -> Source = { source ->
|
||||||
Source(
|
Source(
|
||||||
source.id,
|
source.id,
|
||||||
source.lang,
|
source.lang,
|
||||||
source.name,
|
source.name,
|
||||||
false,
|
supportsLatest = false,
|
||||||
|
isStub = source is SourceManager.StubSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
val catalogueSourceMapper: (CatalogueSource) -> Source = { source ->
|
||||||
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
sourceMapper(source).copy(supportsLatest = source.supportsLatest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sourceDataMapper: (Long, String, String) -> SourceData = { id, lang, name ->
|
||||||
|
SourceData(id, lang, name)
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.data.source
|
|||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
import eu.kanade.data.DatabaseHandler
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
@ -49,4 +50,12 @@ class SourceRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getSourceData(id: Long): SourceData? {
|
||||||
|
return handler.awaitOneOrNull { sourcesQueries.getSourceData(id, sourceDataMapper) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun upsertSourceData(id: Long, lang: String, name: String) {
|
||||||
|
handler.await { sourcesQueries.upsert(id, lang, name) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,14 @@ import eu.kanade.domain.manga.interactor.UpdateManga
|
|||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
import eu.kanade.domain.manga.repository.MangaRepository
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||||
|
import eu.kanade.domain.source.interactor.GetSourceData
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
|
import eu.kanade.domain.source.interactor.UpsertSourceData
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import uy.kohesive.injekt.api.InjektModule
|
import uy.kohesive.injekt.api.InjektModule
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
@ -71,11 +73,13 @@ class DomainModule : InjektModule {
|
|||||||
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
|
||||||
addFactory { GetEnabledSources(get(), get()) }
|
addFactory { GetEnabledSources(get(), get()) }
|
||||||
addFactory { GetLanguagesWithSources(get(), get()) }
|
addFactory { GetLanguagesWithSources(get(), get()) }
|
||||||
|
addFactory { GetSourceData(get()) }
|
||||||
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
|
||||||
addFactory { GetSourcesWithNonLibraryManga(get()) }
|
addFactory { GetSourcesWithNonLibraryManga(get()) }
|
||||||
addFactory { SetMigrateSorting(get()) }
|
addFactory { SetMigrateSorting(get()) }
|
||||||
addFactory { ToggleLanguage(get()) }
|
addFactory { ToggleLanguage(get()) }
|
||||||
addFactory { ToggleSource(get()) }
|
addFactory { ToggleSource(get()) }
|
||||||
addFactory { ToggleSourcePin(get()) }
|
addFactory { ToggleSourcePin(get()) }
|
||||||
|
addFactory { UpsertSourceData(get()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import logcat.LogPriority
|
||||||
|
|
||||||
|
class GetSourceData(
|
||||||
|
private val repository: SourceRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(id: Long): SourceData? {
|
||||||
|
return try {
|
||||||
|
repository.getSourceData(id)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,20 +33,18 @@ class GetSourcesWithFavoriteCount(
|
|||||||
strength = Collator.PRIMARY
|
strength = Collator.PRIMARY
|
||||||
}
|
}
|
||||||
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||||
val id1 = a.first.name.toLongOrNull()
|
|
||||||
val id2 = b.first.name.toLongOrNull()
|
|
||||||
when (sorting) {
|
when (sorting) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
when {
|
when {
|
||||||
id1 != null && id2 == null -> -1
|
a.first.isStub && b.first.isStub.not() -> -1
|
||||||
id2 != null && id1 == null -> 1
|
b.first.isStub && a.first.isStub.not() -> 1
|
||||||
else -> collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
else -> collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetMigrateSorting.Mode.TOTAL -> {
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
when {
|
when {
|
||||||
id1 != null && id2 == null -> -1
|
a.first.isStub && b.first.isStub.not() -> -1
|
||||||
id2 != null && id1 == null -> 1
|
b.first.isStub && a.first.isStub.not() -> 1
|
||||||
else -> a.second.compareTo(b.second)
|
else -> a.second.compareTo(b.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import logcat.LogPriority
|
||||||
|
|
||||||
|
class UpsertSourceData(
|
||||||
|
private val repository: SourceRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun await(sourceData: SourceData) {
|
||||||
|
try {
|
||||||
|
repository.upsertSourceData(sourceData.id, sourceData.lang, sourceData.name)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ data class Source(
|
|||||||
val lang: String,
|
val lang: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val supportsLatest: Boolean,
|
val supportsLatest: Boolean,
|
||||||
|
val isStub: Boolean,
|
||||||
val pin: Pins = Pins.unpinned,
|
val pin: Pins = Pins.unpinned,
|
||||||
val isUsedLast: Boolean = false,
|
val isUsedLast: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package eu.kanade.domain.source.model
|
||||||
|
|
||||||
|
data class SourceData(
|
||||||
|
val id: Long,
|
||||||
|
val lang: String,
|
||||||
|
val name: String,
|
||||||
|
)
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.domain.source.repository
|
package eu.kanade.domain.source.repository
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import eu.kanade.tachiyomi.source.Source as LoadedSource
|
import eu.kanade.tachiyomi.source.Source as LoadedSource
|
||||||
|
|
||||||
@ -13,4 +14,8 @@ interface SourceRepository {
|
|||||||
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
|
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
|
||||||
|
|
||||||
fun getSourcesWithNonLibraryManga(): Flow<List<Pair<LoadedSource, Long>>>
|
fun getSourcesWithNonLibraryManga(): Flow<List<Pair<LoadedSource, Long>>>
|
||||||
|
|
||||||
|
suspend fun getSourceData(id: Long): SourceData?
|
||||||
|
|
||||||
|
suspend fun upsertSourceData(id: Long, lang: String, name: String)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
@ -10,13 +14,18 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.ItemBadges
|
import eu.kanade.presentation.components.ItemBadges
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
@ -28,6 +37,7 @@ import eu.kanade.presentation.util.topPaddingValues
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceState
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceState
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesPresenter
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceScreen(
|
fun MigrateSourceScreen(
|
||||||
@ -107,6 +117,53 @@ fun MigrateSourceItem(
|
|||||||
showLanguageInContent = source.lang != "",
|
showLanguageInContent = source.lang != "",
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
|
icon = {
|
||||||
|
if (source.isStub) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ic_warning_white_24dp),
|
||||||
|
contentDescription = "",
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SourceIcon(source = source)
|
||||||
|
}
|
||||||
|
},
|
||||||
action = { ItemBadges(primaryText = "$count") },
|
action = { ItemBadges(primaryText = "$count") },
|
||||||
|
content = { source, showLanguageInContent ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = horizontalPadding)
|
||||||
|
.weight(1f),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = source.name.ifBlank { source.id.toString() },
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (showLanguageInContent) {
|
||||||
|
Text(
|
||||||
|
text = LocaleHelper.getDisplayName(source.lang),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (source.isStub) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.not_installed),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ private val defaultContent: @Composable RowScope.(Source, Boolean) -> Unit = { s
|
|||||||
.weight(1f),
|
.weight(1f),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = source.name,
|
text = source.name.ifBlank { source.id.toString() },
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
@ -43,7 +43,12 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
|
|||||||
val sources = backup.backupSources.associate { it.sourceId to it.name }
|
val sources = backup.backupSources.associate { it.sourceId to it.name }
|
||||||
val missingSources = sources
|
val missingSources = sources
|
||||||
.filter { sourceManager.get(it.key) == null }
|
.filter { sourceManager.get(it.key) == null }
|
||||||
.values
|
.values.map {
|
||||||
|
val id = it.toLongOrNull()
|
||||||
|
if (id == null) it
|
||||||
|
else sourceManager.getOrStub(id).toString()
|
||||||
|
}
|
||||||
|
.distinct()
|
||||||
.sorted()
|
.sorted()
|
||||||
|
|
||||||
val trackers = backup.backupManga
|
val trackers = backup.backupManga
|
||||||
|
@ -71,7 +71,7 @@ class DownloadCache(
|
|||||||
*/
|
*/
|
||||||
fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean): Boolean {
|
fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean): Boolean {
|
||||||
if (skipCache) {
|
if (skipCache) {
|
||||||
val source = sourceManager.get(manga.source) ?: return false
|
val source = sourceManager.getOrStub(manga.source)
|
||||||
return provider.findChapterDir(chapter, manga, source) != null
|
return provider.findChapterDir(chapter, manga, source) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,11 +124,15 @@ class DownloadCache(
|
|||||||
private fun renew() {
|
private fun renew() {
|
||||||
val onlineSources = sourceManager.getOnlineSources()
|
val onlineSources = sourceManager.getOnlineSources()
|
||||||
|
|
||||||
|
val stubSources = sourceManager.getStubSources()
|
||||||
|
|
||||||
|
val allSource = onlineSources + stubSources
|
||||||
|
|
||||||
val sourceDirs = rootDir.dir.listFiles()
|
val sourceDirs = rootDir.dir.listFiles()
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
.associate { it.name to SourceDirectory(it) }
|
.associate { it.name to SourceDirectory(it) }
|
||||||
.mapNotNullKeys { entry ->
|
.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
|
rootDir.files = sourceDirs
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.extension
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
@ -90,8 +91,20 @@ class ExtensionManager(
|
|||||||
field = value
|
field = value
|
||||||
availableExtensionsRelay.call(value)
|
availableExtensionsRelay.call(value)
|
||||||
updatedInstalledExtensionsStatuses(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.
|
* Relay used to notify the untrusted extensions.
|
||||||
*/
|
*/
|
||||||
|
@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.extension.api
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
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.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||||
@ -22,6 +23,7 @@ internal class ExtensionGithubApi {
|
|||||||
|
|
||||||
private val networkService: NetworkHelper by injectLazy()
|
private val networkService: NetworkHelper by injectLazy()
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
private val extensionManager: ExtensionManager by injectLazy()
|
||||||
|
|
||||||
private var requiresFallbackSource = false
|
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
|
// 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
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val extensions = findExtensions()
|
val extensions = if (fromAvailableExtensionList) {
|
||||||
|
extensionManager.availableExtensions
|
||||||
preferences.lastExtCheck().set(Date().time)
|
} else {
|
||||||
|
findExtensions().also { preferences.lastExtCheck().set(Date().time) }
|
||||||
|
}
|
||||||
|
|
||||||
val installedExtensions = ExtensionLoader.loadExtensions(context)
|
val installedExtensions = ExtensionLoader.loadExtensions(context)
|
||||||
.filterIsInstance<LoadResult.Success>()
|
.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 {
|
return this.map {
|
||||||
AvailableExtensionSources(
|
AvailableSources(
|
||||||
name = it.name,
|
|
||||||
id = it.id,
|
id = it.id,
|
||||||
|
lang = it.lang,
|
||||||
|
name = it.name,
|
||||||
baseUrl = it.baseUrl,
|
baseUrl = it.baseUrl,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -147,7 +152,8 @@ private data class ExtensionJsonObject(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
private data class ExtensionSourceJsonObject(
|
private data class ExtensionSourceJsonObject(
|
||||||
val name: String,
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
val lang: String,
|
||||||
|
val name: String,
|
||||||
val baseUrl: String,
|
val baseUrl: String,
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.extension.model
|
package eu.kanade.tachiyomi.extension.model
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
|
||||||
sealed class Extension {
|
sealed class Extension {
|
||||||
@ -40,7 +41,7 @@ sealed class Extension {
|
|||||||
override val isNsfw: Boolean,
|
override val isNsfw: Boolean,
|
||||||
override val hasReadme: Boolean,
|
override val hasReadme: Boolean,
|
||||||
override val hasChangelog: Boolean,
|
override val hasChangelog: Boolean,
|
||||||
val sources: List<AvailableExtensionSources>,
|
val sources: List<AvailableSources>,
|
||||||
val apkName: String,
|
val apkName: String,
|
||||||
val iconUrl: String,
|
val iconUrl: String,
|
||||||
) : Extension()
|
) : Extension()
|
||||||
@ -58,8 +59,17 @@ sealed class Extension {
|
|||||||
) : Extension()
|
) : Extension()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AvailableExtensionSources(
|
data class AvailableSources(
|
||||||
val name: String,
|
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
val lang: String,
|
||||||
|
val name: String,
|
||||||
val baseUrl: 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
|
package eu.kanade.tachiyomi.source
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import eu.kanade.domain.source.model.SourceData
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
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.icon(): Drawable? = Injekt.get<ExtensionManager>().getAppIconForSource(this)
|
||||||
|
|
||||||
fun Source.getPreferenceKey(): String = "source_$id"
|
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
|
package eu.kanade.tachiyomi.source
|
||||||
|
|
||||||
import android.content.Context
|
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.R
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import tachiyomi.source.model.ChapterInfo
|
import tachiyomi.source.model.ChapterInfo
|
||||||
import tachiyomi.source.model.MangaInfo
|
import tachiyomi.source.model.MangaInfo
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class SourceManager(private val context: Context) {
|
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 sourcesMap = mutableMapOf<Long, Source>()
|
||||||
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
||||||
|
|
||||||
@ -34,7 +45,7 @@ class SourceManager(private val context: Context) {
|
|||||||
|
|
||||||
fun getOrStub(sourceKey: Long): Source {
|
fun getOrStub(sourceKey: Long): Source {
|
||||||
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
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 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) {
|
internal fun registerSource(source: Source) {
|
||||||
if (!sourcesMap.containsKey(source.id)) {
|
if (!sourcesMap.containsKey(source.id)) {
|
||||||
sourcesMap[source.id] = source
|
sourcesMap[source.id] = source
|
||||||
}
|
}
|
||||||
if (!stubSourcesMap.containsKey(source.id)) {
|
registerStubSource(source.toSourceData())
|
||||||
stubSourcesMap[source.id] = StubSource(source.id)
|
|
||||||
}
|
|
||||||
triggerCatalogueSources()
|
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) {
|
internal fun unregisterSource(source: Source) {
|
||||||
sourcesMap.remove(source.id)
|
sourcesMap.remove(source.id)
|
||||||
triggerCatalogueSources()
|
triggerCatalogueSources()
|
||||||
@ -67,11 +94,24 @@ class SourceManager(private val context: Context) {
|
|||||||
LocalSource(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")
|
@Suppress("OverridingDeprecatedMember")
|
||||||
inner class StubSource(override val id: Long) : Source {
|
open inner class StubSource(val sourceData: SourceData) : Source {
|
||||||
|
|
||||||
override val name: String
|
override val name: String = sourceData.name
|
||||||
get() = id.toString()
|
|
||||||
|
override val lang: String = sourceData.lang
|
||||||
|
|
||||||
|
override val id: Long = sourceData.id
|
||||||
|
|
||||||
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
|
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
|
||||||
throw getSourceNotInstalledException()
|
throw getSourceNotInstalledException()
|
||||||
@ -98,14 +138,17 @@ class SourceManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return name
|
if (name.isNotBlank() && lang.isNotBlank()) {
|
||||||
|
return "$name (${lang.uppercase()})"
|
||||||
|
}
|
||||||
|
return id.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSourceNotInstalledException(): SourceNotInstalledException {
|
fun getSourceNotInstalledException(): SourceNotInstalledException {
|
||||||
return SourceNotInstalledException(id)
|
return SourceNotInstalledException(toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class SourceNotInstalledException(val id: Long) :
|
inner class SourceNotInstalledException(val sourceString: String) :
|
||||||
Exception(context.getString(R.string.source_not_installed, id.toString()))
|
Exception(context.getString(R.string.source_not_installed, sourceString))
|
||||||
}
|
}
|
||||||
|
@ -366,7 +366,10 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
// Extension updates
|
// Extension updates
|
||||||
try {
|
try {
|
||||||
ExtensionGithubApi().checkForUpdates(this@MainActivity)?.let { pendingUpdates ->
|
ExtensionGithubApi().checkForUpdates(
|
||||||
|
this@MainActivity,
|
||||||
|
fromAvailableExtensionList = true
|
||||||
|
)?.let { pendingUpdates ->
|
||||||
preferences.extensionUpdatesCount().set(pendingUpdates.size)
|
preferences.extensionUpdatesCount().set(pendingUpdates.size)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -1140,7 +1140,9 @@ class MangaController :
|
|||||||
|
|
||||||
private fun downloadChapters(chapters: List<ChapterItem>) {
|
private fun downloadChapters(chapters: List<ChapterItem>) {
|
||||||
if (source is SourceManager.StubSource) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,11 +228,7 @@ class MangaInfoHeaderAdapter(
|
|||||||
*/
|
*/
|
||||||
private fun setMangaInfo() {
|
private fun setMangaInfo() {
|
||||||
// Update full title TextView.
|
// Update full title TextView.
|
||||||
binding.mangaFullTitle.text = if (manga.title.isBlank()) {
|
binding.mangaFullTitle.text = manga.title.ifBlank { view.context.getString(R.string.unknown) }
|
||||||
view.context.getString(R.string.unknown)
|
|
||||||
} else {
|
|
||||||
manga.title
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update author TextView.
|
// Update author TextView.
|
||||||
binding.mangaAuthor.text = if (manga.author.isNullOrBlank()) {
|
binding.mangaAuthor.text = if (manga.author.isNullOrBlank()) {
|
||||||
@ -249,6 +245,8 @@ class MangaInfoHeaderAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If manga source is known update source TextView.
|
// If manga source is known update source TextView.
|
||||||
|
binding.mangaMissingSourceIcon.isVisible = source is SourceManager.StubSource
|
||||||
|
|
||||||
val mangaSource = source.toString()
|
val mangaSource = source.toString()
|
||||||
with(binding.mangaSource) {
|
with(binding.mangaSource) {
|
||||||
val enabledLanguages = preferences.enabledLanguages().get()
|
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.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
@ -87,6 +88,7 @@ class ChapterLoader(
|
|||||||
is LocalSource.Format.Epub -> EpubPageLoader(format.file)
|
is LocalSource.Format.Epub -> EpubPageLoader(format.file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
source is SourceManager.StubSource -> throw source.getSourceNotInstalledException()
|
||||||
else -> error(context.getString(R.string.loader_not_implemented_error))
|
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.databinding.ClearDatabaseSourceItemBinding
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.source.icon
|
import eu.kanade.tachiyomi.source.icon
|
||||||
|
|
||||||
data class ClearDatabaseSourceItem(val source: Source, private val mangaCount: Long) : AbstractFlexibleItem<ClearDatabaseSourceItem.Holder>() {
|
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 {
|
itemView.post {
|
||||||
when {
|
when {
|
||||||
source.id == LocalSource.ID -> binding.thumbnail.setImageResource(R.mipmap.ic_local_source)
|
source.icon() != null && source.id != LocalSource.ID ->
|
||||||
source is SourceManager.StubSource -> binding.thumbnail.setImageDrawable(null)
|
binding.thumbnail.setImageDrawable(source.icon())
|
||||||
source.icon() != null -> binding.thumbnail.setImageDrawable(source.icon())
|
else -> binding.thumbnail.setImageResource(R.mipmap.ic_local_source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +117,15 @@
|
|||||||
android:textIsSelectable="false"
|
android:textIsSelectable="false"
|
||||||
tools:text="Status" />
|
tools:text="Status" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/manga_missing_source_icon"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
app:srcCompat="@drawable/ic_warning_white_24dp"
|
||||||
|
app:tint="@color/error"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -133,6 +133,15 @@
|
|||||||
android:textIsSelectable="false"
|
android:textIsSelectable="false"
|
||||||
tools:ignore="HardcodedText" />
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/manga_missing_source_icon"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
app:srcCompat="@drawable/ic_warning_white_24dp"
|
||||||
|
app:tint="@color/error"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/manga_source"
|
android:id="@+id/manga_source"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -737,6 +737,7 @@
|
|||||||
<string name="migrate">Migrate</string>
|
<string name="migrate">Migrate</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
<string name="empty_screen">Well, this is awkward</string>
|
<string name="empty_screen">Well, this is awkward</string>
|
||||||
|
<string name="not_installed">Not installed</string>
|
||||||
|
|
||||||
<!-- Downloads activity and service -->
|
<!-- Downloads activity and service -->
|
||||||
<string name="download_queue_error">Couldn\'t download chapters. You can try again in the downloads section</string>
|
<string name="download_queue_error">Couldn\'t download chapters. You can try again in the downloads section</string>
|
||||||
|
20
app/src/main/sqldelight/data/sources.sq
Normal file
20
app/src/main/sqldelight/data/sources.sq
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE sources(
|
||||||
|
_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
lang TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
getSourceData:
|
||||||
|
SELECT *
|
||||||
|
FROM sources
|
||||||
|
WHERE _id = :id;
|
||||||
|
|
||||||
|
upsert:
|
||||||
|
INSERT INTO sources(_id, lang, name)
|
||||||
|
VALUES (:id, :lang, :name)
|
||||||
|
ON CONFLICT(_id)
|
||||||
|
DO UPDATE
|
||||||
|
SET
|
||||||
|
lang = :lang,
|
||||||
|
name = :name
|
||||||
|
WHERE _id = :id;
|
5
app/src/main/sqldelight/migrations/16.sqm
Normal file
5
app/src/main/sqldelight/migrations/16.sqm
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE sources(
|
||||||
|
_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
lang TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user