Significantly improve browsing speed (near instantaneous) (#1946)

(cherry picked from commit c8ffabc84a096207c1997ab69fc86176f3b53f00)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/domain/manga/model/Manga.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
#	data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt
#	data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt
#	data/src/main/sqldelight/tachiyomi/data/mangas.sq
#	domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt
#	domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt
#	domain/src/main/java/tachiyomi/domain/source/repository/SourceRepository.kt
This commit is contained in:
AntsyLich 2025-03-31 13:17:22 +06:00 committed by Jobobby04
parent 26674136e6
commit c8039739d5
29 changed files with 180 additions and 172 deletions

View File

@ -76,24 +76,6 @@ fun Manga.copyFrom(other: SManga): Manga {
)
}
fun SManga.toDomainManga(sourceId: Long): Manga {
return Manga.create().copy(
url = url,
// SY -->
ogTitle = title,
ogArtist = artist,
ogAuthor = author,
ogThumbnailUrl = thumbnail_url,
ogDescription = description,
ogGenre = getGenres(),
ogStatus = status.toLong(),
// SY <--
updateStrategy = update_strategy,
initialized = initialized,
source = sourceId,
)
}
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
return coverCache.getCustomCoverFile(id).exists()
}

View File

@ -510,7 +510,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private suspend fun updateCovers() {
val semaphore = Semaphore(5)
val progressCount = AtomicInteger(0)
val progressCount = AtomicInt(0)
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
coroutineScope {
@ -585,7 +585,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
var dbManga = getManga.await(networkManga.url, mangaDex.id)
if (dbManga == null) {
dbManga = networkToLocalManga.await(
dbManga = networkToLocalManga(
Manga.create().copy(
url = networkManga.url,
ogTitle = networkManga.title,

View File

@ -170,7 +170,7 @@ class MergedSource : HttpSource() {
var manga = getManga.await(mangaUrl, mangaSourceId)
val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId)
if (manga == null) {
val newManga = networkToLocalManga.await(
val newManga = networkToLocalManga(
Manga.create().copy(
source = mangaSourceId,
url = mangaUrl,

View File

@ -7,7 +7,6 @@ import androidx.compose.ui.util.fastAny
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.browse.FeedItemUI
import eu.kanade.tachiyomi.source.CatalogueSource
@ -31,6 +30,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import mihon.domain.manga.model.toDomainManga
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withIOContext
@ -251,9 +251,7 @@ open class FeedScreenModel(
val result = withIOContext {
itemUI.copy(
results = page.map {
networkToLocalManga.await(it.toDomainManga(itemUI.source!!.id))
},
results = networkToLocalManga(page.map { it.toDomainManga(itemUI.source!!.id) }),
)
}

View File

@ -226,7 +226,7 @@ class MigrationListScreenModel(
if (searchResult != null &&
!(searchResult.url == mangaObj.url && source.id == mangaObj.source)
) {
val localManga = networkToLocalManga.await(searchResult)
val localManga = networkToLocalManga(searchResult)
val chapters = if (source is EHentai) {
source.getChapterList(localManga.toSManga(), throttleManager::throttle)
@ -264,7 +264,7 @@ class MigrationListScreenModel(
}
if (searchResult != null) {
val localManga = networkToLocalManga.await(searchResult)
val localManga = networkToLocalManga(searchResult)
val chapters = try {
if (source is EHentai) {
source.getChapterList(localManga.toSManga(), throttleManager::throttle)
@ -455,7 +455,7 @@ class MigrationListScreenModel(
screenModelScope.launchIO {
val result = migratingManga.migrationScope.async {
val manga = getManga(newMangaId)!!
val localManga = networkToLocalManga.await(manga)
val localManga = networkToLocalManga(manga)
try {
val source = sourceManager.get(manga.source)!!
val chapters = source.getChapterList(localManga.toSManga())

View File

@ -16,7 +16,6 @@ import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource
import eu.kanade.core.preference.asState
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.source.service.SourcePreferences
@ -39,7 +38,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@ -50,7 +48,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import tachiyomi.core.common.preference.CheckboxState
@ -67,7 +64,6 @@ import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
import tachiyomi.domain.manga.interactor.GetFlatMetadataById
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.interactor.DeleteSavedSearchById
@ -75,7 +71,7 @@ import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.interactor.InsertSavedSearch
import tachiyomi.domain.source.model.EXHSavedSearch
import tachiyomi.domain.source.model.SavedSearch
import tachiyomi.domain.source.repository.SourcePagingSourceType
import tachiyomi.domain.source.repository.SourcePagingSource
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.sy.SYMR
import uy.kohesive.injekt.Injekt
@ -101,7 +97,6 @@ open class BrowseSourceScreenModel(
private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val addTracks: AddTracks = Injekt.get(),
private val getIncognitoState: GetIncognitoState = Injekt.get(),
@ -193,10 +188,9 @@ open class BrowseSourceScreenModel(
createSourcePagingSource(listing.query ?: "", listing.filters)
// SY <--
}.flow.map { pagingData ->
pagingData.map { (it, metadata) ->
networkToLocalManga.await(it.toDomainManga(sourceId))
.let { localManga -> getManga.subscribe(localManga.url, localManga.source) }
.filterNotNull()
pagingData.map { (manga, metadata) ->
getManga.subscribe(manga.url, manga.source)
.map { it ?: manga }
// SY -->
.combineMetadata(metadata)
// SY <--
@ -382,8 +376,8 @@ open class BrowseSourceScreenModel(
}
// SY -->
open fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
return getRemoteManga.subscribe(sourceId, query, filters)
open fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSource {
return getRemoteManga(sourceId, query, filters)
}
// SY <--

View File

@ -10,7 +10,6 @@ import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource
import eu.kanade.core.preference.asState
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.browse.SourceFeedUI
@ -32,8 +31,8 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import mihon.domain.manga.model.toDomainManga
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withIOContext
@ -173,9 +172,7 @@ open class SourceFeedScreenModel(
}
val titles = withIOContext {
page.map {
networkToLocalManga.await(it.toDomainManga(source.id))
}
networkToLocalManga(page.map { it.toDomainManga(source.id) })
}
mutableState.update { state ->

View File

@ -5,7 +5,6 @@ import androidx.compose.runtime.Immutable
import androidx.compose.runtime.produceState
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.util.ioCoroutineScope
import eu.kanade.tachiyomi.extension.ExtensionManager
@ -25,6 +24,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mihon.domain.manga.model.toDomainManga
import tachiyomi.core.common.preference.toggle
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
@ -169,9 +169,8 @@ abstract class SearchScreenModel(
source.getSearchManga(1, query, source.getFilterList())
}
val titles = page.mangas.map {
networkToLocalManga.await(it.toDomainManga(source.id))
}
val titles = page.mangas.map { it.toDomainManga(source.id) }
.let { networkToLocalManga(it) }
if (isActive) {
updateItem(source, SearchItemResult.Success(titles))

View File

@ -4,18 +4,16 @@ import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ResolvableSource
import eu.kanade.tachiyomi.source.online.UriType
import kotlinx.coroutines.flow.update
import mihon.domain.manga.model.toDomainManga
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
@ -27,7 +25,6 @@ class DeepLinkScreenModel(
private val sourceManager: SourceManager = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
@ -38,7 +35,7 @@ class DeepLinkScreenModel(
.firstOrNull { it.getUriType(query) != UriType.Unknown }
val manga = source?.getManga(query)?.let {
getMangaFromSManga(it, source.id)
networkToLocalManga(it.toDomainManga(source.id))
}
val chapter = if (source?.getUriType(query) == UriType.Chapter && manga != null) {
@ -73,11 +70,6 @@ class DeepLinkScreenModel(
}
}
private suspend fun getMangaFromSManga(sManga: SManga, sourceId: Long): Manga {
return getMangaByUrlAndSourceId.await(sManga.url, sourceId)
?: networkToLocalManga.await(sManga.toDomainManga(sourceId))
}
sealed interface State {
@Immutable
data object Loading : State

View File

@ -658,7 +658,7 @@ class MangaScreenModel(
existingManga = getManga.await(mergedManga.url, mergedManga.source)
}
mergedManga = networkToLocalManga.await(mergedManga)
mergedManga = networkToLocalManga(mergedManga)
getCategories.await(originalMangaId)
.let {

View File

@ -136,7 +136,7 @@ class GalleryAdder(
// Use manga in DB if possible, otherwise, make a new manga
var manga = getManga.await(cleanedMangaUrl, source.id)
?: networkToLocalManga.await(
?: networkToLocalManga(
Manga.create().copy(
source = source.id,
url = cleanedMangaUrl,

View File

@ -1,6 +1,5 @@
package exh.favorites
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.source.EXH_SOURCE_ID
@ -11,6 +10,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList
import mihon.domain.manga.model.toDomainManga
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries

View File

@ -2,12 +2,12 @@ package exh.md.follows
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex
import tachiyomi.data.source.SourcePagingSource
import tachiyomi.data.source.BaseSourcePagingSource
/**
* LatestUpdatesPager inherited from the general Pager.
*/
class MangaDexFollowsPagingSource(val mangadex: MangaDex) : SourcePagingSource(mangadex) {
class MangaDexFollowsPagingSource(val mangadex: MangaDex) : BaseSourcePagingSource(mangadex) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return mangadex.fetchFollows(currentPage)

View File

@ -8,12 +8,12 @@ import exh.source.getMainSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import tachiyomi.data.source.BaseSourcePagingSource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.repository.SourcePagingSourceType
class MangaDexFollowsScreenModel(sourceId: Long) : BrowseSourceScreenModel(sourceId, null) {
override fun createSourcePagingSource(query: String, filters: FilterList): SourcePagingSourceType {
override fun createSourcePagingSource(query: String, filters: FilterList): BaseSourcePagingSource {
return MangaDexFollowsPagingSource(source.getMainSource() as MangaDex)
}

View File

@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.produceState
import cafe.adriel.voyager.core.model.StateScreenModel
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.presentation.util.ioCoroutineScope
import eu.kanade.tachiyomi.source.CatalogueSource
import exh.recs.sources.RecommendationPagingSource
@ -23,6 +22,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mihon.domain.manga.model.toDomainManga
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
@ -85,7 +85,7 @@ open class RecommendsScreenModel(
val recSourceId = recSource.associatedSourceId
if (recSourceId != null) {
// If the recommendation is associated with a source, resolve it
networkToLocalManga.await(it.toDomainManga(recSourceId))
networkToLocalManga(it.toDomainManga(recSourceId))
} else {
// Otherwise, skip this step. The user will be prompted to choose a source via SmartSearch
it.toDomainManga(-1)

View File

@ -5,7 +5,6 @@ import android.net.wifi.WifiManager
import android.os.PowerManager
import androidx.annotation.StringRes
import androidx.core.net.toUri
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.SManga
@ -26,6 +25,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import mihon.domain.manga.model.toDomainManga
import tachiyomi.data.source.NoResultsException
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.library.model.LibraryManga
@ -201,8 +201,7 @@ class RecommendationSearchHelper(val context: Context) {
return filterNot { manga ->
// Source recommendations can be directly resolved, if the recommendation is from the same source
recSource.associatedSourceId?.let { srcId ->
return@filterNot networkToLocalManga
.await(manga.toDomainManga(srcId))
return@filterNot networkToLocalManga(manga.toDomainManga(srcId))
.let { local -> libraryManga.any { it.id == local.id } }
}

View File

@ -14,8 +14,8 @@ import exh.source.isMdBasedSource
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
import tachiyomi.data.source.BaseSourcePagingSource
import tachiyomi.data.source.NoResultsException
import tachiyomi.data.source.SourcePagingSource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.i18n.sy.SYMR
@ -29,7 +29,7 @@ import uy.kohesive.injekt.injectLazy
abstract class RecommendationPagingSource(
protected val manga: Manga,
source: CatalogueSource? = null,
) : SourcePagingSource(source) {
) : BaseSourcePagingSource(source) {
// Display name
abstract val name: String

View File

@ -1,9 +1,9 @@
package exh.smartsearch
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import mihon.domain.manga.model.toDomainManga
import tachiyomi.domain.manga.model.Manga
class SmartSourceSearchEngine(

View File

@ -28,7 +28,7 @@ class SmartSearchScreenModel(
val result = try {
val resultManga = smartSearchEngine.smartSearch(source, config.origTitle)
if (resultManga != null) {
val localManga = networkToLocalManga.await(resultManga)
val localManga = networkToLocalManga(resultManga)
SearchResults.Found(localManga)
} else {
SearchResults.NotFound

View File

@ -105,40 +105,6 @@ class MangaRepositoryImpl(
}
}
override suspend fun insert(manga: Manga): Long? {
return handler.awaitOneOrNullExecutable(inTransaction = true) {
// SY -->
if (mangasQueries.getIdByUrlAndSource(manga.url, manga.source).executeAsOneOrNull() != null) {
return@awaitOneOrNullExecutable mangasQueries.getIdByUrlAndSource(manga.url, manga.source)
}
// SY <--
mangasQueries.insert(
source = manga.source,
url = manga.url,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.genre,
title = manga.title,
status = manga.status,
thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite,
lastUpdate = manga.lastUpdate,
nextUpdate = manga.nextUpdate,
calculateInterval = manga.fetchInterval.toLong(),
initialized = manga.initialized,
viewerFlags = manga.viewerFlags,
chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
updateStrategy = manga.updateStrategy,
version = manga.version,
notes = manga.notes,
)
mangasQueries.selectLastInsertedRowId()
}
}
override suspend fun update(update: MangaUpdate): Boolean {
return try {
partialUpdate(update)
@ -159,6 +125,39 @@ class MangaRepositoryImpl(
}
}
override suspend fun insertNetworkManga(manga: List<Manga>): List<Manga> {
return handler.await(inTransaction = true) {
manga.map {
mangasQueries.insertNetworkManga(
source = it.source,
url = it.url,
// SY -->
artist = it.ogArtist,
author = it.ogAuthor,
description = it.ogDescription,
genre = it.ogGenre,
title = it.ogTitle,
status = it.ogStatus,
thumbnailUrl = it.ogThumbnailUrl,
// SY <--
favorite = it.favorite,
lastUpdate = it.lastUpdate,
nextUpdate = it.nextUpdate,
calculateInterval = it.fetchInterval.toLong(),
initialized = it.initialized,
viewerFlags = it.viewerFlags,
chapterFlags = it.chapterFlags,
coverLastModified = it.coverLastModified,
dateAdded = it.dateAdded,
updateStrategy = it.updateStrategy,
version = it.version,
mapper = MangaMapper::mapManga,
)
.executeAsOne()
}
}
}
private suspend fun partialUpdate(vararg mangaUpdates: MangaUpdate) {
handler.await(inTransaction = true) {
mangaUpdates.forEach { value ->

View File

@ -4,20 +4,26 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.RaisedSearchMetadata
import mihon.domain.manga.model.toDomainManga
import tachiyomi.domain.manga.model.Manga
abstract class EHentaiPagingSource(override val source: CatalogueSource) : SourcePagingSource(source) {
abstract class EHentaiPagingSource(
override val source: CatalogueSource,
) : BaseSourcePagingSource(source) {
override fun getPageLoadResult(
override suspend fun getPageLoadResult(
params: LoadParams<Long>,
mangasPage: MangasPage,
): LoadResult.Page<Long, Pair<SManga, RaisedSearchMetadata?>> {
): LoadResult.Page<Long, Pair<Manga, RaisedSearchMetadata?>> {
mangasPage as MetadataMangasPage
val metadata = mangasPage.mangasMetadata
val manga = mangasPage.mangas.map { it.toDomainManga(source.id) }
.let { networkToLocalManga(it) }
return LoadResult.Page(
data = mangasPage.mangas
data = manga
.mapIndexed { index, sManga -> sManga to metadata.getOrNull(index) },
prevKey = null,
nextKey = mangasPage.nextKey,

View File

@ -5,63 +5,74 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.RaisedSearchMetadata
import mihon.domain.manga.model.toDomainManga
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.source.repository.SourcePagingSourceType
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.repository.SourcePagingSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SourceSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) :
SourcePagingSource(source) {
class SourceSearchPagingSource(
source: CatalogueSource,
private val query: String,
private val filters: FilterList,
) : BaseSourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source!!.getSearchManga(currentPage, query, filters)
}
}
class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
class SourcePopularPagingSource(source: CatalogueSource) : BaseSourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source!!.getPopularManga(currentPage)
}
}
class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
class SourceLatestPagingSource(source: CatalogueSource) : BaseSourcePagingSource(source) {
override suspend fun requestNextPage(currentPage: Int): MangasPage {
return source!!.getLatestUpdates(currentPage)
}
}
abstract class SourcePagingSource(
abstract class BaseSourcePagingSource(
protected open val source: CatalogueSource?,
) : SourcePagingSourceType() {
protected val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
) : SourcePagingSource() {
abstract suspend fun requestNextPage(currentPage: Int): MangasPage
override suspend fun load(
params: LoadParams<Long>,
): LoadResult<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */> {
): LoadResult<Long, /*SY --> */ Pair<Manga, RaisedSearchMetadata?>/*SY <-- */> {
val page = params.key ?: 1
val mangasPage = try {
withIOContext {
return try {
val mangasPage = withIOContext {
requestNextPage(page.toInt())
.takeIf { it.mangas.isNotEmpty() }
?: throw NoResultsException()
}
} catch (e: Exception) {
return LoadResult.Error(e)
}
// SY -->
return getPageLoadResult(params, mangasPage)
// SY <--
// SY -->
getPageLoadResult(params, mangasPage)
// SY <--
} catch (e: Exception) {
LoadResult.Error(e)
}
}
// SY -->
open fun getPageLoadResult(
open suspend fun getPageLoadResult(
params: LoadParams<Long>,
mangasPage: MangasPage,
): LoadResult.Page<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */> {
): LoadResult.Page<Long, /*SY --> */ Pair<Manga, RaisedSearchMetadata?>/*SY <-- */> {
val page = params.key ?: 1
val manga = mangasPage.mangas.map { it.toDomainManga(source!!.id) }
.let { networkToLocalManga(it) }
// SY -->
val metadata = if (mangasPage is MetadataMangasPage) {
mangasPage.mangasMetadata
@ -71,10 +82,9 @@ abstract class SourcePagingSource(
// SY <--
return LoadResult.Page(
data = mangasPage.mangas
// SY -->
data = manga// SY -->
.mapIndexed { index, sManga -> sManga to metadata.getOrNull(index) },
// SY <--
// SY <--,
prevKey = null,
nextKey = if (mangasPage.hasNextPage) page + 1 else null,
)
@ -82,7 +92,7 @@ abstract class SourcePagingSource(
// SY <--
override fun getRefreshKey(
state: PagingState<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */>,
state: PagingState<Long, /*SY --> */ Pair<Manga, RaisedSearchMetadata?>/*SY <-- */>,
): Long? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)

View File

@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.map
import tachiyomi.data.DatabaseHandler
import tachiyomi.domain.source.model.SourceWithCount
import tachiyomi.domain.source.model.StubSource
import tachiyomi.domain.source.repository.SourcePagingSourceType
import tachiyomi.domain.source.repository.SourcePagingSource
import tachiyomi.domain.source.repository.SourceRepository
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.source.model.Source as DomainSource
@ -77,7 +77,7 @@ class SourceRepositoryImpl(
sourceId: Long,
query: String,
filterList: FilterList,
): SourcePagingSourceType {
): SourcePagingSource {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source.isEhBasedSource()) {
@ -87,7 +87,7 @@ class SourceRepositoryImpl(
return SourceSearchPagingSource(source, query, filterList)
}
override fun getPopular(sourceId: Long): SourcePagingSourceType {
override fun getPopular(sourceId: Long): SourcePagingSource {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source.isEhBasedSource()) {
@ -97,7 +97,7 @@ class SourceRepositoryImpl(
return SourcePopularPagingSource(source)
}
override fun getLatest(sourceId: Long): SourcePagingSourceType {
override fun getLatest(sourceId: Long): SourcePagingSource {
val source = sourceManager.get(sourceId) as CatalogueSource
// SY -->
if (source.isEhBasedSource()) {

View File

@ -195,6 +195,34 @@ WHERE _id = :mangaId;
selectLastInsertedRowId:
SELECT last_insert_rowid();
insertNetworkManga {
-- Insert the manga if it doesn't exist already
INSERT INTO mangas(
source, url, artist, author, description, genre, title, status, thumbnail_url, favorite,
last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added,
update_strategy, calculate_interval, last_modified_at, version
)
SELECT
:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite,
:lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded,
:updateStrategy, :calculateInterval, 0, :version
WHERE NOT EXISTS(SELECT 0 FROM mangas WHERE source = :source AND url = :url);
-- Update the title if it is not favorite
UPDATE mangas
SET title = :title
WHERE source = :source
AND url = :url
AND favorite = 0;
-- Finally return the manga
SELECT *
FROM mangas
WHERE source = :source
AND url = :url
LIMIT 1;
}
getEhMangaWithMetadata:
SELECT mangas.* FROM mangas
INNER JOIN search_metadata

View File

@ -0,0 +1,22 @@
package mihon.domain.manga.model
import eu.kanade.tachiyomi.source.model.SManga
import tachiyomi.domain.manga.model.Manga
fun SManga.toDomainManga(sourceId: Long): Manga {
return Manga.create().copy(
url = url,
// SY -->
ogTitle = title,
ogArtist = artist,
ogAuthor = author,
ogDescription = description,
ogGenre = getGenres(),
ogStatus = status.toLong(),
ogThumbnailUrl = thumbnail_url,
// SY <--
updateStrategy = update_strategy,
initialized = initialized,
source = sourceId,
)
}

View File

@ -7,29 +7,11 @@ class NetworkToLocalManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(manga: Manga): Manga {
val localManga = getManga(manga.url, manga.source)
return when {
localManga == null -> {
val id = insertManga(manga)
manga.copy(id = id!!)
}
!localManga.favorite -> {
// if the manga isn't a favorite, set its display title from source
// if it later becomes a favorite, updated title will go to db
localManga.copy(/* SY --> */ogTitle/* SY <-- */ = manga.title)
}
else -> {
localManga
}
}
suspend operator fun invoke(manga: Manga): Manga {
return mangaRepository.insertNetworkManga(listOf(manga)).single()
}
private suspend fun getManga(url: String, sourceId: Long): Manga? {
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
}
private suspend fun insertManga(manga: Manga): Long? {
return mangaRepository.insert(manga)
suspend operator fun invoke(manga: List<Manga>): List<Manga> {
return mangaRepository.insertNetworkManga(manga)
}
}

View File

@ -33,12 +33,12 @@ interface MangaRepository {
suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>)
suspend fun insert(manga: Manga): Long?
suspend fun update(update: MangaUpdate): Boolean
suspend fun updateAll(mangaUpdates: List<MangaUpdate>): Boolean
suspend fun insertNetworkManga(manga: List<Manga>): List<Manga>
// SY -->
suspend fun getMangaBySourceId(sourceId: Long): List<Manga>

View File

@ -1,14 +1,14 @@
package tachiyomi.domain.source.interactor
import eu.kanade.tachiyomi.source.model.FilterList
import tachiyomi.domain.source.repository.SourcePagingSourceType
import tachiyomi.domain.source.repository.SourcePagingSource
import tachiyomi.domain.source.repository.SourceRepository
class GetRemoteManga(
private val repository: SourceRepository,
) {
fun subscribe(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType {
operator fun invoke(sourceId: Long, query: String, filterList: FilterList): SourcePagingSource {
return when (query) {
QUERY_POPULAR -> repository.getPopular(sourceId)
QUERY_LATEST -> repository.getLatest(sourceId)

View File

@ -2,13 +2,13 @@ package tachiyomi.domain.source.repository
import androidx.paging.PagingSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import exh.metadata.metadata.RaisedSearchMetadata
import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.model.Source
import tachiyomi.domain.source.model.SourceWithCount
typealias SourcePagingSourceType = PagingSource<Long, /*SY --> */ Pair<SManga, RaisedSearchMetadata?>/*SY <-- */>
typealias SourcePagingSource = PagingSource<Long, /*SY --> */ Pair<Manga, RaisedSearchMetadata?>/*SY <-- */>
interface SourceRepository {
@ -20,9 +20,9 @@ interface SourceRepository {
fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>>
fun search(sourceId: Long, query: String, filterList: FilterList): SourcePagingSourceType
fun search(sourceId: Long, query: String, filterList: FilterList): SourcePagingSource
fun getPopular(sourceId: Long): SourcePagingSourceType
fun getPopular(sourceId: Long): SourcePagingSource
fun getLatest(sourceId: Long): SourcePagingSourceType
fun getLatest(sourceId: Long): SourcePagingSource
}