Refactor network to local manga logic

Maybe fixes #8289
This commit is contained in:
arkon 2022-10-26 23:01:21 -04:00
parent 5b3f9e082e
commit d5b4bb49b1
9 changed files with 72 additions and 81 deletions

View file

@ -25,7 +25,7 @@ class MangaRepositoryImpl(
} }
override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? { override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? {
return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) } return handler.awaitOneOrNull(inTransaction = true) { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
} }
override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow<Manga?> { override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow<Manga?> {

View file

@ -44,7 +44,7 @@ import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetLibraryManga import eu.kanade.domain.manga.interactor.GetLibraryManga
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.InsertManga import eu.kanade.domain.manga.interactor.NetworkToLocalManga
import eu.kanade.domain.manga.interactor.ResetViewerFlags import eu.kanade.domain.manga.interactor.ResetViewerFlags
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
@ -98,7 +98,7 @@ class DomainModule : InjektModule {
addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) } addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
addFactory { SetMangaViewerFlags(get()) } addFactory { SetMangaViewerFlags(get()) }
addFactory { InsertManga(get()) } addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get()) } addFactory { UpdateManga(get()) }
addFactory { SetMangaCategories(get()) } addFactory { SetMangaCategories(get()) }

View file

@ -23,10 +23,6 @@ class GetManga(
return mangaRepository.getMangaByIdAsFlow(id) return mangaRepository.getMangaByIdAsFlow(id)
} }
suspend fun await(url: String, sourceId: Long): Manga? {
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
}
fun subscribe(url: String, sourceId: Long): Flow<Manga?> { fun subscribe(url: String, sourceId: Long): Flow<Manga?> {
return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId) return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId)
} }

View file

@ -1,13 +0,0 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
class InsertManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(manga: Manga): Long? {
return mangaRepository.insert(manga)
}
}

View file

@ -0,0 +1,35 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
class NetworkToLocalManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(manga: Manga, sourceId: Long): Manga {
val localManga = getManga(manga.url, sourceId)
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(title = manga.title)
}
else -> {
localManga
}
}
}
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)
}
}

View file

@ -232,6 +232,21 @@ fun Manga.toMangaUpdate(): MangaUpdate {
) )
} }
fun SManga.toDomainManga(): Manga {
return Manga.create().copy(
url = url,
title = title,
artist = artist,
author = author,
description = description,
genre = getGenres(),
status = status.toLong(),
thumbnailUrl = thumbnail_url,
updateStrategy = update_strategy,
initialized = initialized,
)
}
fun Manga.isLocal(): Boolean = source == LocalSource.ID fun Manga.isLocal(): Boolean = source == LocalSource.ID
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean { fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {

View file

@ -79,11 +79,10 @@ class SearchPresenter(
return GlobalSearchItem(source, results, source.id == manga.source) return GlobalSearchItem(source, results, source.id == manga.source)
} }
override fun networkToLocalManga(sManga: SManga, sourceId: Long): eu.kanade.tachiyomi.data.database.models.Manga { override suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
val localManga = super.networkToLocalManga(sManga, sourceId) val localManga = super.networkToLocalManga(sManga, sourceId)
// For migration, displayed title should always match source rather than local DB // For migration, displayed title should always match source rather than local DB
localManga.title = sManga.title return localManga.copy(title = sManga.title)
return localManga
} }
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) { fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) {

View file

@ -28,9 +28,10 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertManga import eu.kanade.domain.manga.interactor.NetworkToLocalManga
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.GetRemoteManga import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
@ -39,8 +40,6 @@ import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.presentation.browse.BrowseSourceState import eu.kanade.presentation.browse.BrowseSourceState
import eu.kanade.presentation.browse.BrowseSourceStateImpl import eu.kanade.presentation.browse.BrowseSourceStateImpl
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
@ -49,7 +48,6 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem
@ -97,7 +95,7 @@ open class BrowseSourcePresenter(
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(),
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
@ -131,9 +129,9 @@ open class BrowseSourcePresenter(
getRemoteManga.subscribe(sourceId, currentFilter.query, currentFilter.filters) getRemoteManga.subscribe(sourceId, currentFilter.query, currentFilter.filters)
}.flow }.flow
.map { .map {
it.map { it.map { sManga ->
withIOContext { withIOContext {
networkToLocalManga(it, sourceId).toDomainManga()!! networkToLocalManga.await(sManga.toDomainManga(), sourceId)
} }
} }
} }
@ -183,30 +181,6 @@ open class BrowseSourcePresenter(
state.filters = source!!.getFilterList() state.filters = source!!.getFilterList()
} }
/**
* Returns a manga from the database for the given manga from network. It creates a new entry
* if the manga is not yet in the database.
*
* @param sManga the manga from the source.
* @return a manga from the database.
*/
private suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = getManga.await(sManga.url, sourceId)
if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
newManga.id = -1
val id = insertManga.await(newManga.toDomainManga()!!)
val result = getManga.await(id!!)
localManga = result
} else if (!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 = localManga.copy(title = sManga.title)
}
return localManga?.toDbManga()!!
}
/** /**
* Initialize a manga. * Initialize a manga.
* *

View file

@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.os.Bundle import android.os.Bundle
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.NetworkToLocalManga
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -30,6 +30,7 @@ import rx.subjects.PublishSubject
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import eu.kanade.domain.manga.model.Manga as DomainManga
open class GlobalSearchPresenter( open class GlobalSearchPresenter(
private val initialQuery: String? = "", private val initialQuery: String? = "",
@ -37,8 +38,7 @@ open class GlobalSearchPresenter(
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val preferences: BasePreferences = Injekt.get(), val preferences: BasePreferences = Injekt.get(),
val sourcePreferences: SourcePreferences = Injekt.get(), val sourcePreferences: SourcePreferences = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
) : BasePresenter<GlobalSearchController>() { ) : BasePresenter<GlobalSearchController>() {
@ -55,7 +55,7 @@ open class GlobalSearchPresenter(
/** /**
* Subject which fetches image of given manga. * Subject which fetches image of given manga.
*/ */
private val fetchImageSubject = PublishSubject.create<Pair<List<Manga>, Source>>() private val fetchImageSubject = PublishSubject.create<Pair<List<DomainManga>, Source>>()
/** /**
* Subscription for fetching images of manga. * Subscription for fetching images of manga.
@ -168,9 +168,9 @@ open class GlobalSearchPresenter(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions .onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions
.map { it.mangas } .map { it.mangas }
.map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga .map { list -> list.map { runBlocking { networkToLocalManga(it, source.id) } } } // Convert to local manga
.doOnNext { fetchImage(it, source) } // Load manga covers .doOnNext { fetchImage(it, source) } // Load manga covers
.map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it.toDomainManga()!!) }) } .map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it) }) }
}, },
5, 5,
) )
@ -208,7 +208,7 @@ open class GlobalSearchPresenter(
* *
* @param manga the list of manga to initialize. * @param manga the list of manga to initialize.
*/ */
private fun fetchImage(manga: List<Manga>, source: Source) { private fun fetchImage(manga: List<DomainManga>, source: Source) {
fetchImageSubject.onNext(Pair(manga, source)) fetchImageSubject.onNext(Pair(manga, source))
} }
@ -220,9 +220,9 @@ open class GlobalSearchPresenter(
fetchImageSubscription = fetchImageSubject.observeOn(Schedulers.io()) fetchImageSubscription = fetchImageSubject.observeOn(Schedulers.io())
.flatMap { (first, source) -> .flatMap { (first, source) ->
Observable.from(first) Observable.from(first)
.filter { it.thumbnail_url == null && !it.initialized } .filter { it.thumbnailUrl == null && !it.initialized }
.map { Pair(it, source) } .map { Pair(it, source) }
.concatMap { runAsObservable { getMangaDetails(it.first, it.second) } } .concatMap { runAsObservable { getMangaDetails(it.first.toDbManga(), it.second) } }
.map { Pair(source as CatalogueSource, it) } .map { Pair(source as CatalogueSource, it) }
} }
.onBackpressureBuffer() .onBackpressureBuffer()
@ -259,22 +259,7 @@ open class GlobalSearchPresenter(
* @param sManga the manga from the source. * @param sManga the manga from the source.
* @return a manga from the database. * @return a manga from the database.
*/ */
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): DomainManga {
var localManga = runBlocking { getManga.await(sManga.url, sourceId) } return networkToLocalManga.await(sManga.toDomainManga(), sourceId)
if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
newManga.id = -1
val result = runBlocking {
val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!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 = localManga.copy(title = sManga.title)
}
return localManga!!.toDbManga()
} }
} }