Refactor network to local manga logic

Maybe fixes #8289

(cherry picked from commit d5b4bb49b168adc5b7c3934e530571497c85a916)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt
This commit is contained in:
arkon 2022-10-26 23:01:21 -04:00 committed by Jobobby04
parent 9b28b65e62
commit b5ae4c0d43
17 changed files with 157 additions and 277 deletions

View File

@ -27,7 +27,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

@ -24,15 +24,15 @@ 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)
} }
// SY --> // SY -->
suspend fun await(url: String, sourceId: Long): Manga? {
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
}
override suspend fun awaitId(url: String, sourceId: Long): Long? { override suspend fun awaitId(url: String, sourceId: Long): Long? {
return await(url, sourceId)?.id return await(url, sourceId)?.id
} }

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(/* SY --> */ogTitle/* SY <-- */ = 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

@ -295,6 +295,23 @@ fun Manga.toMangaUpdate(): MangaUpdate {
) )
} }
fun SManga.toDomainManga(): Manga {
return Manga.create().copy(
url = url,
// SY -->
ogTitle = title,
ogArtist = artist,
ogAuthor = author,
ogDescription = description,
ogGenre = getGenres(),
ogStatus = status.toLong(),
// SY <--
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

@ -25,7 +25,7 @@ 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.GetMergedMangaForDownloading import eu.kanade.domain.manga.interactor.GetMergedMangaForDownloading
import eu.kanade.domain.manga.interactor.InsertFlatMetadata import eu.kanade.domain.manga.interactor.InsertFlatMetadata
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.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
@ -119,7 +119,7 @@ class LibraryUpdateService(
// SY --> // SY -->
private val getFavorites: GetFavorites = Injekt.get(), private val getFavorites: GetFavorites = Injekt.get(),
private val insertFlatMetadata: InsertFlatMetadata = Injekt.get(), private val insertFlatMetadata: InsertFlatMetadata = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val getMergedMangaForDownloading: GetMergedMangaForDownloading = Injekt.get(), private val getMergedMangaForDownloading: GetMergedMangaForDownloading = Injekt.get(),
// SY <-- // SY <--
) : Service() { ) : Service() {
@ -728,18 +728,16 @@ class LibraryUpdateService(
var dbManga = getManga.await(networkManga.url, mangaDex.id) var dbManga = getManga.await(networkManga.url, mangaDex.id)
if (dbManga == null) { if (dbManga == null) {
val newManga = Manga.create().copy( dbManga = networkToLocalManga.await(
url = networkManga.url, Manga.create().copy(
ogTitle = networkManga.title, url = networkManga.url,
source = mangaDex.id, ogTitle = networkManga.title,
favorite = true, source = mangaDex.id,
dateAdded = System.currentTimeMillis(), favorite = true,
dateAdded = System.currentTimeMillis(),
),
mangaDex.id,
) )
val result = runBlocking {
val id = insertManga.await(newManga)
getManga.await(id!!)
}
dbManga = result ?: return
} else if (!dbManga.favorite) { } else if (!dbManga.favorite) {
updateManga.awaitUpdateFavorite(dbManga.id, true) updateManga.awaitUpdateFavorite(dbManga.id, true)
} }

View File

@ -8,7 +8,7 @@ import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.download.service.DownloadPreferences
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMergedReferencesById import eu.kanade.domain.manga.interactor.GetMergedReferencesById
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.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
@ -40,7 +40,7 @@ class MergedSource : HttpSource() {
private val getManga: GetManga by injectLazy() private val getManga: GetManga by injectLazy()
private val getMergedReferencesById: GetMergedReferencesById by injectLazy() private val getMergedReferencesById: GetMergedReferencesById by injectLazy()
private val getMergedChaptersByMangaId: GetMergedChapterByMangaId by injectLazy() private val getMergedChaptersByMangaId: GetMergedChapterByMangaId by injectLazy()
private val insertManga: InsertManga by injectLazy() private val networkToLocalManga: NetworkToLocalManga by injectLazy()
private val updateManga: UpdateManga by injectLazy() private val updateManga: UpdateManga by injectLazy()
private val getCategories: GetCategories by injectLazy() private val getCategories: GetCategories by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
@ -187,8 +187,7 @@ class MergedSource : HttpSource() {
semaphore.withPermit { semaphore.withPermit {
values.flatMap { values.flatMap {
try { try {
val (source, loadedManga, reference) = val (source, loadedManga, reference) = it.load()
it.load(sourceManager, getManga, insertManga, updateManga)
if (loadedManga != null && reference.getChapterUpdates) { if (loadedManga != null && reference.getChapterUpdates) {
val chapterList = source.getChapterList(loadedManga.toSManga()) val chapterList = source.getChapterList(loadedManga.toSManga())
val results = val results =
@ -219,19 +218,19 @@ class MergedSource : HttpSource() {
} }
} }
suspend fun MergedMangaReference.load(sourceManager: SourceManager, getManga: GetManga, insertManga: InsertManga, updateManga: UpdateManga): LoadedMangaSource { suspend fun MergedMangaReference.load(): LoadedMangaSource {
var manga = getManga.await(mangaUrl, mangaSourceId) var manga = getManga.await(mangaUrl, mangaSourceId)
val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId) val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId)
if (manga == null) { if (manga == null) {
val id = insertManga.await( val newManga = networkToLocalManga.await(
Manga.create().copy( Manga.create().copy(
source = mangaSourceId, source = mangaSourceId,
url = mangaUrl, url = mangaUrl,
), ),
)!! mangaSourceId,
val newManga = getManga.await(id)!! )
updateManga.awaitUpdateFromSource(newManga, source.getMangaDetails(newManga.toSManga()), false) updateManga.awaitUpdateFromSource(newManga, source.getMangaDetails(newManga.toSManga()), false)
manga = getManga.await(id)!! manga = getManga.await(newManga.id)!!
} }
return LoadedMangaSource(source, manga, this) return LoadedMangaSource(source, manga, this)
} }

View File

@ -4,9 +4,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
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.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.CountFeedSavedSearchGlobal import eu.kanade.domain.source.interactor.CountFeedSavedSearchGlobal
import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById
@ -18,13 +18,10 @@ import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.browse.FeedItemUI import eu.kanade.presentation.browse.FeedItemUI
import eu.kanade.presentation.browse.FeedState import eu.kanade.presentation.browse.FeedState
import eu.kanade.presentation.browse.FeedStateImpl import eu.kanade.presentation.browse.FeedStateImpl
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
@ -60,7 +57,7 @@ open class FeedPresenter(
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val sourcePreferences: SourcePreferences = Injekt.get(), val sourcePreferences: SourcePreferences = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = 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 getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(), private val getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(),
private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(), private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(),
@ -204,8 +201,8 @@ open class FeedPresenter(
.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 } // Get manga from search result. .map { it.mangas } // Get manga from search result.
.map { list -> list.map { networkToLocalManga(it, itemUI.source.id) } } // Convert to local manga. .map { list -> runBlocking { list.map { networkToLocalManga.await(it.toDomainManga(), itemUI.source.id) } } } // Convert to local manga.
.map { list -> itemUI.copy(results = list.mapNotNull { it.toDomainManga() }) } .map { list -> itemUI.copy(results = list) }
} else { } else {
Observable.just(itemUI.copy(results = emptyList())) Observable.just(itemUI.copy(results = emptyList()))
} }
@ -255,32 +252,6 @@ open class FeedPresenter(
} }
} }
/**
* 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 fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = runBlocking { getManga.await(sManga.url, 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(ogTitle = sManga.title)
}
return localManga?.toDbManga()!!
}
/** /**
* Initialize a manga. * Initialize a manga.
* *

View File

@ -15,7 +15,7 @@ import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.HistoryUpdate import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMergedReferencesById import eu.kanade.domain.manga.interactor.GetMergedReferencesById
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.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
@ -26,12 +26,10 @@ import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
@ -64,7 +62,7 @@ class MigrationListPresenter(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(), private val coverCache: CoverCache = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = 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 syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(), private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(), private val updateChapter: UpdateChapter = Injekt.get(),
@ -182,7 +180,7 @@ class MigrationListPresenter(
} }
if (searchResult != null && !(searchResult.url == mangaObj.url && source.id == mangaObj.source)) { if (searchResult != null && !(searchResult.url == mangaObj.url && source.id == mangaObj.source)) {
val localManga = networkToLocalManga( val localManga = networkToLocalManga.await(
searchResult, searchResult,
source.id, source.id,
) )
@ -222,7 +220,7 @@ class MigrationListPresenter(
} }
if (searchResult != null) { if (searchResult != null) {
val localManga = networkToLocalManga(searchResult, source.id) val localManga = networkToLocalManga.await(searchResult, source.id)
val chapters = try { val chapters = try {
if (source is EHentai) { if (source is EHentai) {
source.getChapterList(localManga.toSManga(), throttleManager::throttle) source.getChapterList(localManga.toSManga(), throttleManager::throttle)
@ -385,7 +383,7 @@ class MigrationListPresenter(
migratingManga.searchResult.value = SearchResult.Searching migratingManga.searchResult.value = SearchResult.Searching
presenterScope.launchIO { presenterScope.launchIO {
val result = migratingManga.migrationScope.async { val result = migratingManga.migrationScope.async {
val localManga = networkToLocalManga(manga.toDbManga(), source.id) val localManga = networkToLocalManga.await(manga, source.id)
try { try {
val chapters = source.getChapterList(localManga.toSManga()) val chapters = source.getChapterList(localManga.toSManga())
syncChaptersWithSource.await(chapters, localManga, source) syncChaptersWithSource.await(chapters, localManga, source)
@ -524,30 +522,4 @@ class MigrationListPresenter(
it.migrationScope.cancel() it.migrationScope.cancel()
} }
} }
/**
* 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 = eu.kanade.tachiyomi.data.database.models.Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
newManga.id = -1
val result = run {
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(ogTitle = sManga.title)
}
return localManga!!
}
} }

View File

@ -30,9 +30,10 @@ 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.GetFlatMetadataById import eu.kanade.domain.manga.interactor.GetFlatMetadataById
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.DeleteSavedSearchById import eu.kanade.domain.source.interactor.DeleteSavedSearchById
import eu.kanade.domain.source.interactor.GetExhSavedSearch import eu.kanade.domain.source.interactor.GetExhSavedSearch
@ -45,8 +46,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
@ -55,7 +54,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.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.filter.AutoComplete import eu.kanade.tachiyomi.ui.browse.source.filter.AutoComplete
@ -124,7 +122,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(),
@ -170,11 +168,11 @@ open class BrowseSourcePresenter(
createSourcePagingSource(currentFilter.query, currentFilter.filters) createSourcePagingSource(currentFilter.query, currentFilter.filters)
}.flow }.flow
.map { .map {
it.map { it.map { (sManga, metadata) ->
// SY --> // SY -->
withIOContext { withIOContext {
networkToLocalManga(it.first, sourceId).toDomainManga()!! networkToLocalManga.await(sManga.toDomainManga(), sourceId)
} to it.second } to metadata
// SY <-- // SY <--
} }
} }
@ -272,30 +270,6 @@ open class BrowseSourcePresenter(
// SY <-- // SY <--
} }
/**
* 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(ogTitle = sManga.title)
}
return localManga?.toDbManga()!!
}
/** /**
* Initialize a manga. * Initialize a manga.
* *

View File

@ -7,9 +7,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
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.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.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.CountFeedSavedSearchBySourceId import eu.kanade.domain.source.interactor.CountFeedSavedSearchBySourceId
import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById import eu.kanade.domain.source.interactor.DeleteFeedSavedSearchById
@ -20,12 +20,9 @@ import eu.kanade.domain.source.interactor.InsertFeedSavedSearch
import eu.kanade.presentation.browse.SourceFeedState import eu.kanade.presentation.browse.SourceFeedState
import eu.kanade.presentation.browse.SourceFeedStateImpl import eu.kanade.presentation.browse.SourceFeedStateImpl
import eu.kanade.presentation.browse.SourceFeedUI import eu.kanade.presentation.browse.SourceFeedUI
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
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.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
@ -60,7 +57,7 @@ open class SourceFeedPresenter(
val source: CatalogueSource, val source: CatalogueSource,
private val preferences: BasePreferences = Injekt.get(), private val preferences: BasePreferences = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = 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 getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(), private val getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(),
private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(), private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(),
@ -162,8 +159,8 @@ open class SourceFeedPresenter(
.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 } // Get manga from search result. .map { it.mangas } // Get manga from search result.
.map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga. .map { list -> runBlocking { list.map { networkToLocalManga.await(it.toDomainManga(), source.id) } } } // Convert to local manga.
.map { list -> sourceFeed.withResults(list.mapNotNull { it.toDomainManga() }) } .map { list -> sourceFeed.withResults(list) }
}, },
5, 5,
) )
@ -211,32 +208,6 @@ open class SourceFeedPresenter(
} }
} }
/**
* 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 fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = runBlocking { getManga.await(sManga.url, 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(ogTitle = 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? = "",
@ -38,8 +39,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>() {
@ -56,7 +56,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.
@ -172,9 +172,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,
) )
@ -212,7 +212,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))
} }
@ -224,9 +224,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()
@ -263,22 +263,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(ogTitle = sManga.title)
}
return localManga!!.toDbManga()
} }
} }

View File

@ -29,8 +29,8 @@ import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.GetMergedMangaById import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedReferencesById import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.GetPagePreviews import eu.kanade.domain.manga.interactor.GetPagePreviews
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.InsertMergedReference import eu.kanade.domain.manga.interactor.InsertMergedReference
import eu.kanade.domain.manga.interactor.NetworkToLocalManga
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
@ -49,7 +49,6 @@ import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -125,7 +124,6 @@ import java.text.DecimalFormatSymbols
import java.util.Date import java.util.Date
import eu.kanade.domain.chapter.model.Chapter as DomainChapter import eu.kanade.domain.chapter.model.Chapter as DomainChapter
import eu.kanade.domain.manga.model.Manga as DomainManga import eu.kanade.domain.manga.model.Manga as DomainManga
import eu.kanade.tachiyomi.data.database.models.Manga.Companion as DbManga
class MangaPresenter( class MangaPresenter(
val mangaId: Long, val mangaId: Long,
@ -149,7 +147,7 @@ class MangaPresenter(
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(), private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
private val insertMergedReference: InsertMergedReference = Injekt.get(), private val insertMergedReference: InsertMergedReference = Injekt.get(),
private val updateMergedSettings: UpdateMergedSettings = Injekt.get(), private val updateMergedSettings: UpdateMergedSettings = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val deleteMangaById: DeleteMangaById = Injekt.get(), private val deleteMangaById: DeleteMangaById = Injekt.get(),
private val deleteByMergeId: DeleteByMergeId = Injekt.get(), private val deleteByMergeId: DeleteByMergeId = Injekt.get(),
private val getFlatMetadata: GetFlatMetadataById = Injekt.get(), private val getFlatMetadata: GetFlatMetadataById = Injekt.get(),
@ -577,15 +575,20 @@ class MangaPresenter(
return originalManga return originalManga
} else { } else {
val mergedManga = DbManga.create(originalManga.url, originalManga.title, MERGED_SOURCE_ID).apply { var mergedManga = DomainManga.create()
copyFrom(originalManga.toSManga()) .copy(
favorite = true url = originalManga.url,
last_update = originalManga.lastUpdate ogTitle = originalManga.title,
viewer_flags = originalManga.viewerFlags.toInt() source = MERGED_SOURCE_ID,
chapter_flags = originalManga.chapterFlags.toInt() )
sorting = DomainManga.CHAPTER_SORTING_NUMBER.toInt() .copyFrom(originalManga.toSManga())
date_added = System.currentTimeMillis() .copy(
} favorite = true,
lastUpdate = originalManga.lastUpdate,
viewerFlags = originalManga.viewerFlags,
chapterFlags = originalManga.chapterFlags,
dateAdded = System.currentTimeMillis(),
)
var existingManga = getManga.await(mergedManga.url, mergedManga.source) var existingManga = getManga.await(mergedManga.url, mergedManga.source)
while (existingManga != null) { while (existingManga != null) {
@ -602,15 +605,11 @@ class MangaPresenter(
existingManga = getManga.await(mergedManga.url, mergedManga.source) existingManga = getManga.await(mergedManga.url, mergedManga.source)
} }
// Reload chapters immediately mergedManga = networkToLocalManga.await(mergedManga, mergedManga.source)
mergedManga.initialized = false
mergedManga.id = -1
val newId = insertManga.await(mergedManga.toDomainManga()!!)
mergedManga.id = newId ?: throw NullPointerException("Invalid new manga id")
getCategories.await(originalMangaId) getCategories.await(originalMangaId)
.let { .let {
setMangaCategories.await(newId, it.map { it.id }) setMangaCategories.await(mergedManga.id, it.map { it.id })
} }
val originalMangaReference = MergedMangaReference( val originalMangaReference = MergedMangaReference(
@ -620,7 +619,7 @@ class MangaPresenter(
chapterSortMode = 0, chapterSortMode = 0,
chapterPriority = 0, chapterPriority = 0,
downloadChapters = true, downloadChapters = true,
mergeId = mergedManga.id!!, mergeId = mergedManga.id,
mergeUrl = mergedManga.url, mergeUrl = mergedManga.url,
mangaId = originalManga.id, mangaId = originalManga.id,
mangaUrl = originalManga.url, mangaUrl = originalManga.url,
@ -634,7 +633,7 @@ class MangaPresenter(
chapterSortMode = 0, chapterSortMode = 0,
chapterPriority = 0, chapterPriority = 0,
downloadChapters = true, downloadChapters = true,
mergeId = mergedManga.id!!, mergeId = mergedManga.id,
mergeUrl = mergedManga.url, mergeUrl = mergedManga.url,
mangaId = manga.id, mangaId = manga.id,
mangaUrl = manga.url, mangaUrl = manga.url,
@ -648,16 +647,16 @@ class MangaPresenter(
chapterSortMode = 0, chapterSortMode = 0,
chapterPriority = -1, chapterPriority = -1,
downloadChapters = false, downloadChapters = false,
mergeId = mergedManga.id!!, mergeId = mergedManga.id,
mergeUrl = mergedManga.url, mergeUrl = mergedManga.url,
mangaId = mergedManga.id!!, mangaId = mergedManga.id,
mangaUrl = mergedManga.url, mangaUrl = mergedManga.url,
mangaSourceId = MERGED_SOURCE_ID, mangaSourceId = MERGED_SOURCE_ID,
) )
insertMergedReference.awaitAll(listOf(originalMangaReference, newMangaReference, mergedMangaReference)) insertMergedReference.awaitAll(listOf(originalMangaReference, newMangaReference, mergedMangaReference))
return mergedManga.toDomainManga()!! return mergedManga
} }
// Note that if the manga are merged in a different order, this won't trigger, but I don't care lol // Note that if the manga are merged in a different order, this won't trigger, but I don't care lol

View File

@ -6,7 +6,7 @@ import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.Chapter import eu.kanade.domain.chapter.model.Chapter
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.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
@ -22,7 +22,7 @@ import uy.kohesive.injekt.api.get
class GalleryAdder( class GalleryAdder(
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val getChapter: GetChapter = Injekt.get(), private val getChapter: GetChapter = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(), private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
@ -129,15 +129,13 @@ class GalleryAdder(
// Use manga in DB if possible, otherwise, make a new manga // Use manga in DB if possible, otherwise, make a new manga
var manga = getManga.await(cleanedMangaUrl, source.id) var manga = getManga.await(cleanedMangaUrl, source.id)
?: run { ?: networkToLocalManga.await(
insertManga.await( Manga.create().copy(
Manga.create().copy( source = source.id,
source = source.id, url = cleanedMangaUrl,
url = cleanedMangaUrl, ),
), source.id,
) )
getManga.await(cleanedMangaUrl, source.id)!!
}
// Fetch and copy details // Fetch and copy details
val newManga = source.getMangaDetails(manga.toSManga()) val newManga = source.getMangaDetails(manga.toSManga())

View File

@ -1,9 +1,7 @@
package exh.smartsearch package exh.smartsearch
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
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.source.model.SManga
@ -12,19 +10,14 @@ import info.debatty.java.stringsimilarity.NormalizedLevenshtein
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import uy.kohesive.injekt.injectLazy
import java.util.Locale import java.util.Locale
import eu.kanade.tachiyomi.data.database.models.Manga.Companion as DbManga
class SmartSearchEngine( class SmartSearchEngine(
private val extraSearchParams: String? = null, private val extraSearchParams: String? = null,
) { ) {
private val getManga: GetManga by injectLazy()
private val insertManga: InsertManga by injectLazy()
private val normalizedLevenshtein = NormalizedLevenshtein() private val normalizedLevenshtein = NormalizedLevenshtein()
suspend fun smartSearch(source: CatalogueSource, title: String): SManga? { suspend fun smartSearch(source: CatalogueSource, title: String): Manga? {
val cleanedTitle = cleanSmartSearchTitle(title) val cleanedTitle = cleanSmartSearchTitle(title)
val queries = getSmartSearchQueries(cleanedTitle) val queries = getSmartSearchQueries(cleanedTitle)
@ -51,10 +44,10 @@ class SmartSearchEngine(
}.flatMap { it.await() } }.flatMap { it.await() }
} }
return eligibleManga.maxByOrNull { it.dist }?.manga return eligibleManga.maxByOrNull { it.dist }?.manga?.toDomainManga()
} }
suspend fun normalSearch(source: CatalogueSource, title: String): SManga? { suspend fun normalSearch(source: CatalogueSource, title: String): Manga? {
val eligibleManga = supervisorScope { val eligibleManga = supervisorScope {
val searchQuery = if (extraSearchParams != null) { val searchQuery = if (extraSearchParams != null) {
"$title ${extraSearchParams.trim()}" "$title ${extraSearchParams.trim()}"
@ -75,7 +68,7 @@ class SmartSearchEngine(
} }
} }
return eligibleManga.maxByOrNull { it.dist }?.manga return eligibleManga.maxByOrNull { it.dist }?.manga?.toDomainManga()
} }
private fun getSmartSearchQueries(cleanedTitle: String): List<String> { private fun getSmartSearchQueries(cleanedTitle: String): List<String> {
@ -169,32 +162,6 @@ class SmartSearchEngine(
return result.toString() return result.toString()
} }
/**
* 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.
*/
suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = getManga.await(sManga.url, sourceId)
if (localManga == null) {
val newManga = DbManga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
newManga.id = -1
val result = run {
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(ogTitle = sManga.title)
}
return localManga!!
}
companion object { companion object {
const val MIN_SMART_ELIGIBLE_THRESHOLD = 0.4 const val MIN_SMART_ELIGIBLE_THRESHOLD = 0.4
const val MIN_NORMAL_ELIGIBLE_THRESHOLD = 0.4 const val MIN_NORMAL_ELIGIBLE_THRESHOLD = 0.4

View File

@ -1,6 +1,7 @@
package exh.ui.smartsearch package exh.ui.smartsearch
import android.os.Bundle import android.os.Bundle
import eu.kanade.domain.manga.interactor.NetworkToLocalManga
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
@ -10,8 +11,14 @@ import exh.smartsearch.SmartSearchEngine
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SmartSearchPresenter(private val source: CatalogueSource, private val config: SourcesController.SmartSearchConfig) : class SmartSearchPresenter(
private val source: CatalogueSource,
private val config: SourcesController.SmartSearchConfig,
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
) :
BasePresenter<SmartSearchController>() { BasePresenter<SmartSearchController>() {
private val _smartSearchFlow = MutableSharedFlow<SearchResults>() private val _smartSearchFlow = MutableSharedFlow<SearchResults>()
@ -26,7 +33,7 @@ class SmartSearchPresenter(private val source: CatalogueSource, private val conf
val result = try { val result = try {
val resultManga = smartSearchEngine.smartSearch(source, config.origTitle) val resultManga = smartSearchEngine.smartSearch(source, config.origTitle)
if (resultManga != null) { if (resultManga != null) {
val localManga = smartSearchEngine.networkToLocalManga(resultManga, source.id) val localManga = networkToLocalManga.await(resultManga, source.id)
SearchResults.Found(localManga) SearchResults.Found(localManga)
} else { } else {
SearchResults.NotFound SearchResults.NotFound