From e71c9e2775586db1f79202233e3c3156c4edaec0 Mon Sep 17 00:00:00 2001 From: Jobobby04 Date: Sun, 3 Jul 2022 00:33:03 -0400 Subject: [PATCH] Use SQLDelight for mass migration --- .../advanced/process/MigratingManga.kt | 9 +- .../process/MigrationListController.kt | 56 +++--- .../process/MigrationProcessAdapter.kt | 171 +++++++++++------- .../process/MigrationProcessHolder.kt | 25 +-- 4 files changed, 148 insertions(+), 113 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigratingManga.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigratingManga.kt index bd759abd6..adbc295d8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigratingManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigratingManga.kt @@ -1,18 +1,17 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.process -import eu.kanade.tachiyomi.data.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.domain.manga.interactor.GetMangaById +import eu.kanade.domain.manga.model.Manga import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import exh.util.DeferredField -import exh.util.executeOnIO import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlin.coroutines.CoroutineContext class MigratingManga( - private val db: DatabaseHelper, + private val getMangaById: GetMangaById, private val sourceManager: SourceManager, val mangaId: Long, parentContext: CoroutineContext, @@ -29,7 +28,7 @@ class MigratingManga( @Volatile private var manga: Manga? = null suspend fun manga(): Manga? { - if (manga == null) manga = db.getManga(mangaId).executeOnIO() + if (manga == null) manga = getMangaById.await(mangaId) return manga } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt index e5bf2e689..a0d8e6414 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationListController.kt @@ -15,17 +15,20 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.chrisbanes.insetter.applyInsetter +import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource +import eu.kanade.domain.manga.interactor.GetMangaById +import eu.kanade.domain.manga.interactor.UpdateManga +import eu.kanade.domain.manga.model.toDbManga +import eu.kanade.domain.manga.model.toMangaInfo import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.toMangaInfo +import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.MigrationListControllerBinding import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.toSChapter -import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.ui.base.changehandler.OneWayFadeChangeHandler import eu.kanade.tachiyomi.ui.base.controller.BaseController @@ -35,15 +38,12 @@ import eu.kanade.tachiyomi.ui.browse.migration.MigrationMangaDialog import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController import eu.kanade.tachiyomi.ui.manga.MangaController -import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.lang.launchUI -import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toast import exh.eh.EHentaiThrottleManager import exh.smartsearch.SmartSearchEngine -import exh.util.executeOnIO import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -70,11 +70,13 @@ class MigrationListController(bundle: Bundle? = null) : val config: MigrationProcedureConfig? = args.getParcelable(CONFIG_EXTRA) - private val db: DatabaseHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy() private val sourceManager: SourceManager by injectLazy() private val smartSearchEngine = SmartSearchEngine(config?.extraSearchParams) + private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy() + private val getMangaById: GetMangaById by injectLazy() + private val updateManga: UpdateManga by injectLazy() private val migrationScope = CoroutineScope(Job() + Dispatchers.IO) var migrationsJob: Job? = null @@ -107,7 +109,7 @@ class MigrationListController(bundle: Bundle? = null) : val newMigratingManga = migratingManga ?: run { val new = config.mangaIds.map { - MigratingManga(db, sourceManager, it, migrationScope.coroutineContext) + MigratingManga(getMangaById, sourceManager, it, migrationScope.coroutineContext) } migratingManga = new.toMutableList() new @@ -172,16 +174,16 @@ class MigrationListController(bundle: Bundle? = null) : sourceSemaphore.withPermit { try { val searchResult = if (useSmartSearch) { - smartSearchEngine.smartSearch(source, mangaObj.originalTitle) + smartSearchEngine.smartSearch(source, mangaObj.ogTitle) } else { - smartSearchEngine.normalSearch(source, mangaObj.originalTitle) + smartSearchEngine.normalSearch(source, mangaObj.ogTitle) } if (searchResult != null && !(searchResult.url == mangaObj.url && source.id == mangaObj.source)) { val localManga = smartSearchEngine.networkToLocalManga( searchResult, source.id, - ) + ).toDomainManga()!! val chapters = if (source is EHentai) { source.getChapterList(localManga.toMangaInfo(), throttleManager::throttle) @@ -190,7 +192,7 @@ class MigrationListController(bundle: Bundle? = null) : } try { - syncChaptersWithSource(chapters.map { it.toSChapter() }, localManga, source) + syncChaptersWithSource.await(chapters.map { it.toSChapter() }, localManga, source) } catch (e: Exception) { return@async2 null } @@ -212,13 +214,13 @@ class MigrationListController(bundle: Bundle? = null) : validSources.forEachIndexed { index, source -> val searchResult = try { val searchResult = if (useSmartSearch) { - smartSearchEngine.smartSearch(source, mangaObj.originalTitle) + smartSearchEngine.smartSearch(source, mangaObj.ogTitle) } else { - smartSearchEngine.normalSearch(source, mangaObj.originalTitle) + smartSearchEngine.normalSearch(source, mangaObj.ogTitle) } if (searchResult != null) { - val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id) + val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id).toDomainManga()!! val chapters = try { if (source is EHentai) { source.getChapterList(localManga.toMangaInfo(), throttleManager::throttle) @@ -229,9 +231,7 @@ class MigrationListController(bundle: Bundle? = null) : this@MigrationListController.logcat(LogPriority.ERROR, e) emptyList() } - withIOContext { - syncChaptersWithSource(chapters, localManga, source) - } + syncChaptersWithSource.await(chapters, localManga, source) localManga } else null } catch (e: CancellationException) { @@ -252,12 +252,10 @@ class MigrationListController(bundle: Bundle? = null) : continue } - if (result != null && result.thumbnail_url == null) { + if (result != null && result.thumbnailUrl == null) { try { val newManga = sourceManager.getOrStub(result.source).getMangaDetails(result.toMangaInfo()) - result.copyFrom(newManga.toSManga()) - - db.insertManga(result).executeOnIO() + updateManga.awaitUpdateFromSource(result, newManga, true) } catch (e: CancellationException) { // Ignore cancellations throw e @@ -335,7 +333,7 @@ class MigrationListController(bundle: Bundle? = null) : } else { sources.filter { it.id != manga.source } } - val searchController = SearchController(manga, validSources) + val searchController = SearchController(manga.toDbManga(), validSources) searchController.targetController = this@MigrationListController router.pushController(searchController) } @@ -359,11 +357,11 @@ class MigrationListController(bundle: Bundle? = null) : adapter?.notifyItemChanged(firstIndex) launchUI { val result = CoroutineScope(migratingManga.manga.migrationJob).async { - val localManga = smartSearchEngine.networkToLocalManga(manga, source.id) + val localManga = smartSearchEngine.networkToLocalManga(manga, source.id).toDomainManga()!! try { val chapters = source.getChapterList(localManga.toMangaInfo()) .map { it.toSChapter() } - syncChaptersWithSource(chapters, localManga, source) + syncChaptersWithSource.await(chapters, localManga, source) } catch (e: Exception) { return@async null } @@ -373,9 +371,7 @@ class MigrationListController(bundle: Bundle? = null) : if (result != null) { try { val newManga = sourceManager.getOrStub(result.source).getMangaDetails(result.toMangaInfo()) - result.copyFrom(newManga.toSManga()) - - db.insertManga(result).executeOnIO() + updateManga.awaitUpdateFromSource(result, newManga, true) } catch (e: CancellationException) { // Ignore cancellations throw e @@ -413,14 +409,14 @@ class MigrationListController(bundle: Bundle? = null) : val hasDetails = router.backstack.any { it.controller is MangaController } if (hasDetails) { val manga = migratingManga?.firstOrNull()?.searchResult?.get()?.let { - db.getManga(it).executeOnIO() + getMangaById.await(it) } if (manga != null) { val newStack = router.backstack.filter { it.controller !is MangaController && it.controller !is MigrationListController && it.controller !is PreMigrationController - } + MangaController(manga.id!!).withFadeTransaction() + } + MangaController(manga.id).withFadeTransaction() router.setBackstack(newStack, OneWayFadeChangeHandler()) return@launchUI } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt index c2ddf7247..35bd71840 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt @@ -2,15 +2,28 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.process import android.view.MenuItem import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.kanade.data.DatabaseHandler +import eu.kanade.data.history.historyMapper +import eu.kanade.domain.category.interactor.GetCategories +import eu.kanade.domain.category.interactor.SetMangaCategories +import eu.kanade.domain.chapter.interactor.GetChapterByMangaId +import eu.kanade.domain.chapter.interactor.UpdateChapter +import eu.kanade.domain.chapter.model.Chapter +import eu.kanade.domain.chapter.model.ChapterUpdate +import eu.kanade.domain.history.interactor.UpsertHistory +import eu.kanade.domain.history.model.HistoryUpdate +import eu.kanade.domain.manga.interactor.GetMangaById +import eu.kanade.domain.manga.interactor.UpdateManga +import eu.kanade.domain.manga.model.Manga +import eu.kanade.domain.manga.model.MangaUpdate +import eu.kanade.domain.manga.model.hasCustomCover +import eu.kanade.domain.manga.model.toDbManga +import eu.kanade.domain.track.interactor.DeleteTrack +import eu.kanade.domain.track.interactor.GetTracks +import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.data.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.History -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags -import eu.kanade.tachiyomi.util.hasCustomCover import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.withIOContext import kotlinx.coroutines.cancel @@ -19,9 +32,19 @@ import uy.kohesive.injekt.injectLazy class MigrationProcessAdapter( val controller: MigrationListController, ) : FlexibleAdapter(null, controller, true) { - private val db: DatabaseHelper by injectLazy() + private val handler: DatabaseHandler by injectLazy() private val preferences: PreferencesHelper by injectLazy() private val coverCache: CoverCache by injectLazy() + private val getMangaById: GetMangaById by injectLazy() + private val updateManga: UpdateManga by injectLazy() + private val updateChapter: UpdateChapter by injectLazy() + private val getChapterByMangaId: GetChapterByMangaId by injectLazy() + private val upsertHistory: UpsertHistory by injectLazy() + private val getCategories: GetCategories by injectLazy() + private val setMangaCategories: SetMangaCategories by injectLazy() + private val getTracks: GetTracks by injectLazy() + private val insertTrack: InsertTrack by injectLazy() + private val deleteTrack: DeleteTrack by injectLazy() var items: List = emptyList() @@ -55,19 +78,16 @@ class MigrationProcessAdapter( suspend fun performMigrations(copy: Boolean) { withIOContext { - db.inTransaction { - currentItems.forEach { migratingManga -> - val manga = migratingManga.manga - if (manga.searchResult.initialized) { - val toMangaObj = - db.getManga(manga.searchResult.get() ?: return@forEach).executeAsBlocking() - ?: return@forEach - migrateMangaInternal( - manga.manga() ?: return@forEach, - toMangaObj, - !copy, - ) - } + currentItems.forEach { migratingManga -> + val manga = migratingManga.manga + if (manga.searchResult.initialized) { + val toMangaObj = getMangaById.await(manga.searchResult.get() ?: return@forEach) + ?: return@forEach + migrateMangaInternal( + manga.manga() ?: return@forEach, + toMangaObj, + !copy, + ) } } } @@ -76,16 +96,15 @@ class MigrationProcessAdapter( fun migrateManga(position: Int, copy: Boolean) { launchUI { val manga = getItem(position)?.manga ?: return@launchUI - db.inTransaction { - val toMangaObj = - db.getManga(manga.searchResult.get() ?: return@launchUI).executeAsBlocking() - ?: return@launchUI - migrateMangaInternal( - manga.manga() ?: return@launchUI, - toMangaObj, - !copy, - ) - } + + val toMangaObj = getMangaById.await(manga.searchResult.get() ?: return@launchUI) + ?: return@launchUI + migrateMangaInternal( + manga.manga() ?: return@launchUI, + toMangaObj, + !copy, + ) + removeManga(position) } } @@ -107,7 +126,7 @@ class MigrationProcessAdapter( sourceFinished() } - private fun migrateMangaInternal( + private suspend fun migrateMangaInternal( prevManga: Manga, manga: Manga, replace: Boolean, @@ -116,69 +135,87 @@ class MigrationProcessAdapter( val flags = preferences.migrateFlags().get() // Update chapters read if (MigrationFlags.hasChapters(flags)) { - val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() + val prevMangaChapters = getChapterByMangaId.await(prevManga.id) val maxChapterRead = - prevMangaChapters.filter(Chapter::read).maxOfOrNull(Chapter::chapter_number) - val dbChapters = db.getChapters(manga).executeAsBlocking() - val prevHistoryList = db.getHistoryByMangaId(prevManga.id!!).executeAsBlocking() - val historyList = mutableListOf() + prevMangaChapters.filter(Chapter::read).maxOfOrNull(Chapter::chapterNumber) + val dbChapters = getChapterByMangaId.await(manga.id) + val prevHistoryList = handler.awaitList { historyQueries.getHistoryByMangaId(prevManga.id, historyMapper) } + + val chapterUpdates = mutableListOf() + val historyUpdates = mutableListOf() + dbChapters.forEach { chapter -> if (chapter.isRecognizedNumber) { - val prevChapter = - prevMangaChapters.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number } + val prevChapter = prevMangaChapters.find { it.isRecognizedNumber && it.chapterNumber == chapter.chapterNumber } if (prevChapter != null) { - chapter.bookmark = prevChapter.bookmark - chapter.read = prevChapter.read - chapter.date_fetch = prevChapter.date_fetch - prevHistoryList.find { it.chapter_id == prevChapter.id }?.let { prevHistory -> - val history = History.create(chapter).apply { last_read = prevHistory.last_read } - historyList.add(history) + chapterUpdates += ChapterUpdate( + id = chapter.id, + bookmark = prevChapter.bookmark, + read = prevChapter.read, + dateFetch = prevChapter.dateFetch, + ) + prevHistoryList.find { it.chapterId == prevChapter.id }?.let { prevHistory -> + historyUpdates += HistoryUpdate( + chapter.id, + prevHistory.readAt ?: return@let, + prevHistory.readDuration, + ) } - } else if (maxChapterRead != null && chapter.chapter_number <= maxChapterRead) { - chapter.read = true + } else if (maxChapterRead != null && chapter.chapterNumber <= maxChapterRead) { + chapterUpdates += ChapterUpdate( + id = chapter.id, + read = true, + ) } } } - db.insertChapters(dbChapters).executeAsBlocking() - db.upsertHistoryLastRead(historyList).executeAsBlocking() + + updateChapter.awaitAll(chapterUpdates) + historyUpdates.forEach { + upsertHistory.await(it) + } } // Update categories if (MigrationFlags.hasCategories(flags)) { - val categories = db.getCategoriesForManga(prevManga).executeAsBlocking() - val mangaCategories = categories.map { MangaCategory.create(manga, it) } - db.setMangaCategories(mangaCategories, listOf(manga)) + val categories = getCategories.await(prevManga.id) + setMangaCategories.await(manga.id, categories.map { it.id }) } // Update track if (MigrationFlags.hasTracks(flags)) { - val tracks = db.getTracks(prevManga.id).executeAsBlocking() + val tracks = getTracks.await(prevManga.id) if (tracks.isNotEmpty()) { - tracks.forEach { track -> - track.id = null - track.manga_id = manga.id!! + getTracks.await(manga.id).forEach { + deleteTrack.await(manga.id, it.syncId) } - db.insertTracks(tracks).executeAsBlocking() + insertTrack.awaitAll(tracks.map { it.copy(mangaId = manga.id) }) } } // Update custom cover if (MigrationFlags.hasCustomCover(flags) && prevManga.hasCustomCover(coverCache)) { - coverCache.setCustomCoverToCache(manga, coverCache.getCustomCoverFile(prevManga.id).inputStream()) + coverCache.setCustomCoverToCache(manga.toDbManga(), coverCache.getCustomCoverFile(prevManga.id).inputStream()) } + + var mangaUpdate = MangaUpdate(manga.id, favorite = true, dateAdded = System.currentTimeMillis()) + var prevMangaUpdate: MangaUpdate? = null // Update extras if (MigrationFlags.hasExtra(flags)) { - manga.chapter_flags = prevManga.chapter_flags - manga.viewer_flags = prevManga.viewer_flags + mangaUpdate = mangaUpdate.copy( + chapterFlags = prevManga.chapterFlags, + viewerFlags = prevManga.viewerFlags, + ) } // Update favorite status if (replace) { - prevManga.favorite = false - manga.date_added = prevManga.date_added - prevManga.date_added = 0 - db.updateMangaFavorite(prevManga).executeAsBlocking() - } else { - manga.date_added = System.currentTimeMillis() + prevMangaUpdate = MangaUpdate( + id = prevManga.id, + favorite = false, + dateAdded = 0, + ) + mangaUpdate = mangaUpdate.copy( + dateAdded = prevManga.dateAdded, + ) } - manga.favorite = true - db.updateMangaMigrate(manga).executeAsBlocking() + updateManga.awaitAll(listOfNotNull(mangaUpdate, prevMangaUpdate)) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt index 2c672af09..ae89121a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt @@ -6,9 +6,11 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import coil.dispose import eu.davidea.viewholders.FlexibleViewHolder +import eu.kanade.domain.chapter.interactor.GetChapterByMangaId +import eu.kanade.domain.manga.interactor.GetMangaById +import eu.kanade.domain.manga.interactor.GetMergedReferencesById +import eu.kanade.domain.manga.model.Manga import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.databinding.MigrationMangaCardBinding import eu.kanade.tachiyomi.databinding.MigrationProcessItemBinding import eu.kanade.tachiyomi.source.Source @@ -19,7 +21,6 @@ import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.view.loadAutoPause import eu.kanade.tachiyomi.util.view.setVectorCompat import exh.source.MERGED_SOURCE_ID -import exh.util.executeOnIO import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.view.clicks @@ -30,8 +31,10 @@ class MigrationProcessHolder( private val view: View, private val adapter: MigrationProcessAdapter, ) : FlexibleViewHolder(view, adapter) { - private val db: DatabaseHelper by injectLazy() private val sourceManager: SourceManager by injectLazy() + private val getMangaById: GetMangaById by injectLazy() + private val getChapterByMangaId: GetChapterByMangaId by injectLazy() + private val getMergedReferencesById: GetMergedReferencesById by injectLazy() private var item: MigrationProcessItem? = null private val binding = MigrationProcessItemBinding.bind(view) @@ -67,7 +70,7 @@ class MigrationProcessHolder( .onEach { adapter.controller.router.pushController( MangaController( - manga.id!!, + manga.id, true, ), ) @@ -86,7 +89,7 @@ class MigrationProcessHolder( }*/ val searchResult = item.manga.searchResult.get()?.let { - db.getManga(it).executeOnIO() + getMangaById.await(it) } val resultSource = searchResult?.source?.let { sourceManager.get(it) @@ -103,7 +106,7 @@ class MigrationProcessHolder( .onEach { adapter.controller.router.pushController( MangaController( - searchResult.id!!, + searchResult.id, true, ), ) @@ -143,24 +146,24 @@ class MigrationProcessHolder( title.text = if (manga.title.isBlank()) { view.context.getString(R.string.unknown) } else { - manga.originalTitle + manga.ogTitle } mangaSourceLabel.text = if (source.id == MERGED_SOURCE_ID) { - db.getMergedMangaReferences(manga.id!!).executeOnIO().map { + getMergedReferencesById.await(manga.id).map { sourceManager.getOrStub(it.mangaSourceId).toString() }.distinct().joinToString() } else { source.toString() } - val chapters = db.getChapters(manga).executeOnIO() + val chapters = getChapterByMangaId.await(manga.id) // For rounded corners badges.leftBadges.clipToOutline = true badges.rightBadges.clipToOutline = true badges.unreadText.isVisible = true badges.unreadText.text = chapters.size.toString() - val latestChapter = chapters.maxByOrNull { it.chapter_number }?.chapter_number ?: -1f + val latestChapter = chapters.maxOfOrNull { it.chapterNumber } ?: -1f if (latestChapter > 0f) { mangaLastChapterLabel.text = root.context.getString(