mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Clean up download ahead logic
- Remove redundant chapter sorting logic when fetching next chapter(s) - Remove redundant download queue checks (it'll handle already queued or downloaded items) - Trigger download ahead when read >= 25% of chapter rather than 20% - Rely on download cache when checking if next chapter is downloaded to avoid jank (fixes #8328)
This commit is contained in:
parent
725fcbba0e
commit
fc184f1cfa
10 changed files with 79 additions and 108 deletions
|
@ -34,7 +34,7 @@ import eu.kanade.domain.extension.interactor.GetExtensionSources
|
|||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||
import eu.kanade.domain.history.interactor.DeleteAllHistory
|
||||
import eu.kanade.domain.history.interactor.GetHistory
|
||||
import eu.kanade.domain.history.interactor.GetNextChapter
|
||||
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
|
||||
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
||||
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
||||
import eu.kanade.domain.history.interactor.UpsertHistory
|
||||
|
@ -94,7 +94,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetLibraryManga(get()) }
|
||||
addFactory { GetMangaWithChapters(get(), get()) }
|
||||
addFactory { GetManga(get()) }
|
||||
addFactory { GetNextChapter(get(), get(), get(), get()) }
|
||||
addFactory { GetNextUnreadChapters(get(), get(), get(), get()) }
|
||||
addFactory { ResetViewerFlags(get()) }
|
||||
addFactory { SetMangaChapterFlags(get()) }
|
||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
import eu.kanade.domain.manga.interactor.GetManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||
|
||||
class GetNextChapter(
|
||||
private val getChapter: GetChapter,
|
||||
private val getChapterByMangaId: GetChapterByMangaId,
|
||||
private val getManga: GetManga,
|
||||
private val historyRepository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): Chapter? {
|
||||
val history = historyRepository.getLastHistory() ?: return null
|
||||
return await(history.mangaId, history.chapterId)
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, chapterId: Long): Chapter? {
|
||||
val chapter = getChapter.await(chapterId) ?: return null
|
||||
val manga = getManga.await(mangaId) ?: return null
|
||||
|
||||
if (!chapter.read) return chapter
|
||||
|
||||
val chapters = getChapterByMangaId.await(mangaId)
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
|
||||
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
|
||||
return when (manga.sorting) {
|
||||
Manga.CHAPTER_SORTING_SOURCE -> chapters.getOrNull(currChapterIndex + 1)
|
||||
Manga.CHAPTER_SORTING_NUMBER -> {
|
||||
val chapterNumber = chapter.chapterNumber
|
||||
|
||||
((currChapterIndex + 1) until chapters.size)
|
||||
.map { chapters[it] }
|
||||
.firstOrNull {
|
||||
it.chapterNumber > chapterNumber && it.chapterNumber <= chapterNumber + 1
|
||||
}
|
||||
}
|
||||
Manga.CHAPTER_SORTING_UPLOAD_DATE -> {
|
||||
chapters.drop(currChapterIndex + 1)
|
||||
.firstOrNull { it.dateUpload >= chapter.dateUpload }
|
||||
}
|
||||
else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package eu.kanade.domain.history.interactor
|
||||
|
||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.history.repository.HistoryRepository
|
||||
import eu.kanade.domain.manga.interactor.GetManga
|
||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||
|
||||
class GetNextUnreadChapters(
|
||||
private val getChapter: GetChapter,
|
||||
private val getChapterByMangaId: GetChapterByMangaId,
|
||||
private val getManga: GetManga,
|
||||
private val historyRepository: HistoryRepository,
|
||||
) {
|
||||
|
||||
suspend fun await(): Chapter? {
|
||||
val history = historyRepository.getLastHistory() ?: return null
|
||||
return await(history.mangaId, history.chapterId).firstOrNull()
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long, chapterId: Long): List<Chapter> {
|
||||
val chapter = getChapter.await(chapterId) ?: return emptyList()
|
||||
val manga = getManga.await(mangaId) ?: return emptyList()
|
||||
|
||||
val chapters = getChapterByMangaId.await(mangaId)
|
||||
.sortedWith(getChapterSort(manga, sortDescending = false))
|
||||
val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id }
|
||||
return chapters
|
||||
.subList(currChapterIndex, chapters.size)
|
||||
.filterNot { it.read }
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ class NetworkToLocalManga(
|
|||
val localManga = getManga(manga.url, sourceId)
|
||||
return when {
|
||||
localManga == null -> {
|
||||
val id = insertManga(manga.copy(source = sourceId))
|
||||
val id = insertManga(manga, sourceId)
|
||||
manga.copy(id = id!!)
|
||||
}
|
||||
!localManga.favorite -> {
|
||||
|
@ -29,7 +29,7 @@ class NetworkToLocalManga(
|
|||
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
|
||||
}
|
||||
|
||||
private suspend fun insertManga(manga: Manga): Long? {
|
||||
return mangaRepository.insert(manga)
|
||||
private suspend fun insertManga(manga: Manga, sourceId: Long): Long? {
|
||||
return mangaRepository.insert(manga.copy(source = sourceId))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,6 +244,10 @@ class Downloader(
|
|||
* @param autoStart whether to start the downloader after enqueing the chapters.
|
||||
*/
|
||||
fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) = launchIO {
|
||||
if (chapters.isEmpty()) {
|
||||
return@launchIO
|
||||
}
|
||||
|
||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO
|
||||
val wasEmpty = queue.isEmpty()
|
||||
// Called in background thread, the operation can be slow with SAF.
|
||||
|
|
|
@ -415,8 +415,7 @@ class LibraryUpdateService(
|
|||
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
||||
// We don't want to start downloading while the library is updating, because websites
|
||||
// may don't like it and they could ban the user.
|
||||
val dbChapters = chapters.map { it.toDbChapter() }
|
||||
downloadManager.downloadChapters(manga, dbChapters, false)
|
||||
downloadManager.downloadChapters(manga, chapters.map { it.toDbChapter() }, false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -271,13 +271,11 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||
*/
|
||||
private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) {
|
||||
launchIO {
|
||||
val manga = getManga.await(mangaId)
|
||||
val manga = getManga.await(mangaId) ?: return@launchIO
|
||||
val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId)?.toDbChapter() }
|
||||
if (manga != null && chapters.isNotEmpty()) {
|
||||
downloadManager.downloadChapters(manga, chapters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NAME = "NotificationReceiver"
|
||||
|
|
|
@ -11,6 +11,7 @@ import eu.kanade.domain.chapter.interactor.UpdateChapter
|
|||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.download.service.DownloadPreferences
|
||||
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
|
||||
import eu.kanade.domain.history.interactor.UpsertHistory
|
||||
import eu.kanade.domain.history.model.HistoryUpdate
|
||||
import eu.kanade.domain.manga.interactor.GetManga
|
||||
|
@ -22,7 +23,6 @@ import eu.kanade.domain.track.interactor.InsertTrack
|
|||
import eu.kanade.domain.track.model.toDbTrack
|
||||
import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||
|
@ -59,7 +59,6 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|||
import eu.kanade.tachiyomi.util.storage.cacheImageDir
|
||||
import eu.kanade.tachiyomi.util.system.isOnline
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import eu.kanade.tachiyomi.util.system.toInt
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -74,7 +73,6 @@ import uy.kohesive.injekt.injectLazy
|
|||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter as DbChapter
|
||||
|
||||
/**
|
||||
* Presenter used by the activity to perform background operations.
|
||||
|
@ -90,6 +88,7 @@ class ReaderPresenter(
|
|||
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
||||
private val getNextUnreadChapters: GetNextUnreadChapters = Injekt.get(),
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
private val insertTrack: InsertTrack = Injekt.get(),
|
||||
private val upsertHistory: UpsertHistory = Injekt.get(),
|
||||
|
@ -393,7 +392,13 @@ class ReaderPresenter(
|
|||
if (chapter.pageLoader is HttpPageLoader) {
|
||||
val manga = manga ?: return
|
||||
val dbChapter = chapter.chapter
|
||||
val isDownloaded = downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source, skipCache = true)
|
||||
val isDownloaded = downloadManager.isChapterDownloaded(
|
||||
dbChapter.name,
|
||||
dbChapter.scanlator,
|
||||
manga.title,
|
||||
manga.source,
|
||||
skipCache = true,
|
||||
)
|
||||
if (isDownloaded) {
|
||||
chapter.state = ReaderChapter.State.Wait
|
||||
}
|
||||
|
@ -406,7 +411,6 @@ class ReaderPresenter(
|
|||
logcat { "Preloading ${chapter.chapter.url}" }
|
||||
|
||||
val loader = loader ?: return
|
||||
|
||||
loader.loadChapter(chapter)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Update current chapters whenever a chapter is preloaded
|
||||
|
@ -447,7 +451,7 @@ class ReaderPresenter(
|
|||
loadNewChapter(selectedChapter)
|
||||
}
|
||||
val pages = page.chapter.pages ?: return
|
||||
val inDownloadRange = page.number.toDouble() / pages.size > 0.2
|
||||
val inDownloadRange = page.number.toDouble() / pages.size > 0.25
|
||||
if (inDownloadRange) {
|
||||
downloadNextChapters()
|
||||
}
|
||||
|
@ -455,45 +459,31 @@ class ReaderPresenter(
|
|||
|
||||
private fun downloadNextChapters() {
|
||||
val manga = manga ?: return
|
||||
val amount = downloadPreferences.autoDownloadWhileReading().get()
|
||||
if (amount == 0 || !manga.favorite) return
|
||||
|
||||
// Only download ahead if current + next chapter is already downloaded too to avoid jank
|
||||
if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return
|
||||
val nextChapter = viewerChaptersRelay.value?.nextChapter?.chapter ?: return
|
||||
val chaptersNumberToDownload = downloadPreferences.autoDownloadWhileReading().get()
|
||||
if (chaptersNumberToDownload == 0 || !manga.favorite) return
|
||||
val isNextChapterDownloadedOrQueued = downloadManager.isChapterDownloaded(
|
||||
|
||||
presenterScope.launchIO {
|
||||
val isNextChapterDownloaded = downloadManager.isChapterDownloaded(
|
||||
nextChapter.name,
|
||||
nextChapter.scanlator,
|
||||
manga.title,
|
||||
manga.source,
|
||||
skipCache = true,
|
||||
) || downloadManager.getChapterDownloadOrNull(nextChapter) != null
|
||||
if (isNextChapterDownloadedOrQueued) {
|
||||
downloadAutoNextChapters(chaptersNumberToDownload, nextChapter.id, nextChapter.read)
|
||||
}
|
||||
}
|
||||
)
|
||||
if (!isNextChapterDownloaded) return@launchIO
|
||||
|
||||
private fun downloadAutoNextChapters(choice: Int, nextChapterId: Long?, isNextChapterRead: Boolean) {
|
||||
val chaptersToDownload = getNextUnreadChaptersSorted(nextChapterId).take(choice - 1 + isNextChapterRead.toInt())
|
||||
if (chaptersToDownload.isNotEmpty()) {
|
||||
downloadChapters(chaptersToDownload)
|
||||
val chaptersToDownload = getNextUnreadChapters.await(manga.id!!, nextChapter.id!!)
|
||||
.take(amount)
|
||||
downloadManager.downloadChapters(
|
||||
manga.toDomainManga()!!,
|
||||
chaptersToDownload.map { it.toDbChapter() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNextUnreadChaptersSorted(nextChapterId: Long?): List<DbChapter> {
|
||||
return chapterList.map { it.chapter.toDomainChapter()!! }
|
||||
.filter { !it.read || it.id == nextChapterId }
|
||||
.sortedWith(getChapterSort(manga?.toDomainManga()!!, false))
|
||||
.map { it.toDbChapter() }
|
||||
.takeLastWhile { it.id != nextChapterId }
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the given list of chapters with the manager.
|
||||
* @param chapters the list of chapters to download.
|
||||
*/
|
||||
private fun downloadChapters(chapters: List<DbChapter>) {
|
||||
downloadManager.downloadChapters(manga?.toDomainManga()!!, chapters)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes [currentChapter] from download queue
|
||||
* if setting is enabled and [currentChapter] is queued for download
|
||||
|
|
|
@ -11,7 +11,7 @@ import eu.kanade.domain.base.BasePreferences
|
|||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.history.interactor.DeleteAllHistory
|
||||
import eu.kanade.domain.history.interactor.GetHistory
|
||||
import eu.kanade.domain.history.interactor.GetNextChapter
|
||||
import eu.kanade.domain.history.interactor.GetNextUnreadChapters
|
||||
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
||||
import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
|
||||
import eu.kanade.domain.history.model.HistoryWithRelations
|
||||
|
@ -37,7 +37,7 @@ import java.util.Date
|
|||
class HistoryPresenter(
|
||||
private val state: HistoryStateImpl = HistoryState() as HistoryStateImpl,
|
||||
private val getHistory: GetHistory = Injekt.get(),
|
||||
private val getNextChapter: GetNextChapter = Injekt.get(),
|
||||
private val getNextUnreadChapters: GetNextUnreadChapters = Injekt.get(),
|
||||
private val deleteAllHistory: DeleteAllHistory = Injekt.get(),
|
||||
private val removeHistoryById: RemoveHistoryById = Injekt.get(),
|
||||
private val removeHistoryByMangaId: RemoveHistoryByMangaId = Injekt.get(),
|
||||
|
@ -94,7 +94,7 @@ class HistoryPresenter(
|
|||
|
||||
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
||||
presenterScope.launchIO {
|
||||
val chapter = getNextChapter.await(mangaId, chapterId)
|
||||
val chapter = getNextUnreadChapters.await(mangaId, chapterId).firstOrNull()
|
||||
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ class HistoryPresenter(
|
|||
|
||||
fun resumeLastChapterRead() {
|
||||
presenterScope.launchIO {
|
||||
val chapter = getNextChapter.await()
|
||||
val chapter = getNextUnreadChapters.await()
|
||||
_events.send(if (chapter != null) Event.OpenChapter(chapter) else Event.NoNextChapterFound)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
package eu.kanade.tachiyomi.util.system
|
||||
|
||||
fun Boolean.toInt() = if (this) 1 else 0
|
||||
|
||||
fun Boolean.toLong() = if (this) 1L else 0L
|
||||
|
|
Loading…
Reference in a new issue