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? {
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?> {

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.GetManga
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.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
@ -98,7 +98,7 @@ class DomainModule : InjektModule {
addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
addFactory { SetMangaViewerFlags(get()) }
addFactory { InsertManga(get()) }
addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get()) }
addFactory { SetMangaCategories(get()) }

View File

@ -24,15 +24,15 @@ class GetManga(
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?> {
return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId)
}
// SY -->
suspend fun await(url: String, sourceId: Long): Manga? {
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
}
override suspend fun awaitId(url: String, sourceId: Long): Long? {
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.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.GetMergedMangaForDownloading
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.model.Manga
import eu.kanade.domain.manga.model.toDbManga
@ -119,7 +119,7 @@ class LibraryUpdateService(
// SY -->
private val getFavorites: GetFavorites = 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(),
// SY <--
) : Service() {
@ -728,18 +728,16 @@ class LibraryUpdateService(
var dbManga = getManga.await(networkManga.url, mangaDex.id)
if (dbManga == null) {
val newManga = Manga.create().copy(
url = networkManga.url,
ogTitle = networkManga.title,
source = mangaDex.id,
favorite = true,
dateAdded = System.currentTimeMillis(),
dbManga = networkToLocalManga.await(
Manga.create().copy(
url = networkManga.url,
ogTitle = networkManga.title,
source = mangaDex.id,
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) {
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.manga.interactor.GetManga
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.model.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
@ -40,7 +40,7 @@ class MergedSource : HttpSource() {
private val getManga: GetManga by injectLazy()
private val getMergedReferencesById: GetMergedReferencesById 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 getCategories: GetCategories by injectLazy()
private val sourceManager: SourceManager by injectLazy()
@ -187,8 +187,7 @@ class MergedSource : HttpSource() {
semaphore.withPermit {
values.flatMap {
try {
val (source, loadedManga, reference) =
it.load(sourceManager, getManga, insertManga, updateManga)
val (source, loadedManga, reference) = it.load()
if (loadedManga != null && reference.getChapterUpdates) {
val chapterList = source.getChapterList(loadedManga.toSManga())
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)
val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId)
if (manga == null) {
val id = insertManga.await(
val newManga = networkToLocalManga.await(
Manga.create().copy(
source = mangaSourceId,
url = mangaUrl,
),
)!!
val newManga = getManga.await(id)!!
mangaSourceId,
)
updateManga.awaitUpdateFromSource(newManga, source.getMangaDetails(newManga.toSManga()), false)
manga = getManga.await(id)!!
manga = getManga.await(newManga.id)!!
}
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.produceState
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.model.toDbManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.CountFeedSavedSearchGlobal
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.FeedState
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.SourceManager
import eu.kanade.tachiyomi.source.model.FilterList
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.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withIOContext
@ -60,7 +57,7 @@ open class FeedPresenter(
val sourceManager: SourceManager = Injekt.get(),
val sourcePreferences: SourcePreferences = 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 getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(),
private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(),
@ -204,8 +201,8 @@ open class FeedPresenter(
.subscribeOn(Schedulers.io())
.onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions
.map { it.mangas } // Get manga from search result.
.map { list -> list.map { networkToLocalManga(it, itemUI.source.id) } } // Convert to local manga.
.map { list -> itemUI.copy(results = list.mapNotNull { it.toDomainManga() }) }
.map { list -> runBlocking { list.map { networkToLocalManga.await(it.toDomainManga(), itemUI.source.id) } } } // Convert to local manga.
.map { list -> itemUI.copy(results = list) }
} else {
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.
*

View File

@ -15,7 +15,7 @@ import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetManga
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.model.Manga
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.tachiyomi.R
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.Source
import eu.kanade.tachiyomi.source.SourceManager
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.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
@ -64,7 +62,7 @@ class MigrationListPresenter(
private val sourceManager: SourceManager = Injekt.get(),
private val coverCache: CoverCache = 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 syncChaptersWithSource: SyncChaptersWithSource = 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)) {
val localManga = networkToLocalManga(
val localManga = networkToLocalManga.await(
searchResult,
source.id,
)
@ -222,7 +220,7 @@ class MigrationListPresenter(
}
if (searchResult != null) {
val localManga = networkToLocalManga(searchResult, source.id)
val localManga = networkToLocalManga.await(searchResult, source.id)
val chapters = try {
if (source is EHentai) {
source.getChapterList(localManga.toSManga(), throttleManager::throttle)
@ -385,7 +383,7 @@ class MigrationListPresenter(
migratingManga.searchResult.value = SearchResult.Searching
presenterScope.launchIO {
val result = migratingManga.migrationScope.async {
val localManga = networkToLocalManga(manga.toDbManga(), source.id)
val localManga = networkToLocalManga.await(manga, source.id)
try {
val chapters = source.getChapterList(localManga.toSManga())
syncChaptersWithSource.await(chapters, localManga, source)
@ -524,30 +522,4 @@ class MigrationListPresenter(
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.GetFlatMetadataById
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.model.toDbManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.DeleteSavedSearchById
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.BrowseSourceStateImpl
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.TrackManager
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.model.Filter
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.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.filter.AutoComplete
@ -124,7 +122,7 @@ open class BrowseSourcePresenter(
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = 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 insertTrack: InsertTrack = Injekt.get(),
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
@ -170,11 +168,11 @@ open class BrowseSourcePresenter(
createSourcePagingSource(currentFilter.query, currentFilter.filters)
}.flow
.map {
it.map {
it.map { (sManga, metadata) ->
// SY -->
withIOContext {
networkToLocalManga(it.first, sourceId).toDomainManga()!!
} to it.second
networkToLocalManga.await(sManga.toDomainManga(), sourceId)
} to metadata
// SY <--
}
}
@ -272,30 +270,6 @@ open class BrowseSourcePresenter(
// 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.
*

View File

@ -7,9 +7,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import eu.kanade.domain.base.BasePreferences
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.model.toDbManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.interactor.CountFeedSavedSearchBySourceId
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.SourceFeedStateImpl
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.model.FilterList
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.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withIOContext
@ -60,7 +57,7 @@ open class SourceFeedPresenter(
val source: CatalogueSource,
private val preferences: BasePreferences = 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 getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(),
private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(),
@ -162,8 +159,8 @@ open class SourceFeedPresenter(
.subscribeOn(Schedulers.io())
.onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions
.map { it.mangas } // Get manga from search result.
.map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga.
.map { list -> sourceFeed.withResults(list.mapNotNull { it.toDomainManga() }) }
.map { list -> runBlocking { list.map { networkToLocalManga.await(it.toDomainManga(), source.id) } } } // Convert to local manga.
.map { list -> sourceFeed.withResults(list) }
},
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.
*

View File

@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.os.Bundle
import eu.kanade.domain.base.BasePreferences
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.model.toDbManga
import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.source.service.SourcePreferences
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.api.get
import uy.kohesive.injekt.injectLazy
import eu.kanade.domain.manga.model.Manga as DomainManga
open class GlobalSearchPresenter(
private val initialQuery: String? = "",
@ -38,8 +39,7 @@ open class GlobalSearchPresenter(
val sourceManager: SourceManager = Injekt.get(),
val preferences: BasePreferences = Injekt.get(),
val sourcePreferences: SourcePreferences = 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(),
) : BasePresenter<GlobalSearchController>() {
@ -56,7 +56,7 @@ open class GlobalSearchPresenter(
/**
* 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.
@ -172,9 +172,9 @@ open class GlobalSearchPresenter(
.subscribeOn(Schedulers.io())
.onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions
.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
.map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it.toDomainManga()!!) }) }
.map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it) }) }
},
5,
)
@ -212,7 +212,7 @@ open class GlobalSearchPresenter(
*
* @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))
}
@ -224,9 +224,9 @@ open class GlobalSearchPresenter(
fetchImageSubscription = fetchImageSubject.observeOn(Schedulers.io())
.flatMap { (first, source) ->
Observable.from(first)
.filter { it.thumbnail_url == null && !it.initialized }
.filter { it.thumbnailUrl == null && !it.initialized }
.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) }
}
.onBackpressureBuffer()
@ -263,22 +263,7 @@ open class GlobalSearchPresenter(
* @param sManga the manga from the source.
* @return a manga from the database.
*/
protected open 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()
protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): DomainManga {
return networkToLocalManga.await(sManga.toDomainManga(), sourceId)
}
}

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.GetMergedReferencesById
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.NetworkToLocalManga
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators
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.tachiyomi.R
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.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
@ -125,7 +124,6 @@ import java.text.DecimalFormatSymbols
import java.util.Date
import eu.kanade.domain.chapter.model.Chapter as DomainChapter
import eu.kanade.domain.manga.model.Manga as DomainManga
import eu.kanade.tachiyomi.data.database.models.Manga.Companion as DbManga
class MangaPresenter(
val mangaId: Long,
@ -149,7 +147,7 @@ class MangaPresenter(
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
private val insertMergedReference: InsertMergedReference = 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 deleteByMergeId: DeleteByMergeId = Injekt.get(),
private val getFlatMetadata: GetFlatMetadataById = Injekt.get(),
@ -577,15 +575,20 @@ class MangaPresenter(
return originalManga
} else {
val mergedManga = DbManga.create(originalManga.url, originalManga.title, MERGED_SOURCE_ID).apply {
copyFrom(originalManga.toSManga())
favorite = true
last_update = originalManga.lastUpdate
viewer_flags = originalManga.viewerFlags.toInt()
chapter_flags = originalManga.chapterFlags.toInt()
sorting = DomainManga.CHAPTER_SORTING_NUMBER.toInt()
date_added = System.currentTimeMillis()
}
var mergedManga = DomainManga.create()
.copy(
url = originalManga.url,
ogTitle = originalManga.title,
source = MERGED_SOURCE_ID,
)
.copyFrom(originalManga.toSManga())
.copy(
favorite = true,
lastUpdate = originalManga.lastUpdate,
viewerFlags = originalManga.viewerFlags,
chapterFlags = originalManga.chapterFlags,
dateAdded = System.currentTimeMillis(),
)
var existingManga = getManga.await(mergedManga.url, mergedManga.source)
while (existingManga != null) {
@ -602,15 +605,11 @@ class MangaPresenter(
existingManga = getManga.await(mergedManga.url, mergedManga.source)
}
// Reload chapters immediately
mergedManga.initialized = false
mergedManga.id = -1
val newId = insertManga.await(mergedManga.toDomainManga()!!)
mergedManga.id = newId ?: throw NullPointerException("Invalid new manga id")
mergedManga = networkToLocalManga.await(mergedManga, mergedManga.source)
getCategories.await(originalMangaId)
.let {
setMangaCategories.await(newId, it.map { it.id })
setMangaCategories.await(mergedManga.id, it.map { it.id })
}
val originalMangaReference = MergedMangaReference(
@ -620,7 +619,7 @@ class MangaPresenter(
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = true,
mergeId = mergedManga.id!!,
mergeId = mergedManga.id,
mergeUrl = mergedManga.url,
mangaId = originalManga.id,
mangaUrl = originalManga.url,
@ -634,7 +633,7 @@ class MangaPresenter(
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = true,
mergeId = mergedManga.id!!,
mergeId = mergedManga.id,
mergeUrl = mergedManga.url,
mangaId = manga.id,
mangaUrl = manga.url,
@ -648,16 +647,16 @@ class MangaPresenter(
chapterSortMode = 0,
chapterPriority = -1,
downloadChapters = false,
mergeId = mergedManga.id!!,
mergeId = mergedManga.id,
mergeUrl = mergedManga.url,
mangaId = mergedManga.id!!,
mangaId = mergedManga.id,
mangaUrl = mergedManga.url,
mangaSourceId = MERGED_SOURCE_ID,
)
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

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.model.Chapter
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.model.Manga
import eu.kanade.domain.source.service.SourcePreferences
@ -22,7 +22,7 @@ import uy.kohesive.injekt.api.get
class GalleryAdder(
private val getManga: GetManga = 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 syncChaptersWithSource: SyncChaptersWithSource = 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
var manga = getManga.await(cleanedMangaUrl, source.id)
?: run {
insertManga.await(
Manga.create().copy(
source = source.id,
url = cleanedMangaUrl,
),
)
getManga.await(cleanedMangaUrl, source.id)!!
}
?: networkToLocalManga.await(
Manga.create().copy(
source = source.id,
url = cleanedMangaUrl,
),
source.id,
)
// Fetch and copy details
val newManga = source.getMangaDetails(manga.toSManga())

View File

@ -1,9 +1,7 @@
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.tachiyomi.data.database.models.toDomainManga
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
@ -12,19 +10,14 @@ import info.debatty.java.stringsimilarity.NormalizedLevenshtein
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.supervisorScope
import uy.kohesive.injekt.injectLazy
import java.util.Locale
import eu.kanade.tachiyomi.data.database.models.Manga.Companion as DbManga
class SmartSearchEngine(
private val extraSearchParams: String? = null,
) {
private val getManga: GetManga by injectLazy()
private val insertManga: InsertManga by injectLazy()
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 queries = getSmartSearchQueries(cleanedTitle)
@ -51,10 +44,10 @@ class SmartSearchEngine(
}.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 searchQuery = if (extraSearchParams != null) {
"$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> {
@ -169,32 +162,6 @@ class SmartSearchEngine(
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 {
const val MIN_SMART_ELIGIBLE_THRESHOLD = 0.4
const val MIN_NORMAL_ELIGIBLE_THRESHOLD = 0.4

View File

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