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:
arkon 2022-10-30 16:59:33 -04:00
parent 725fcbba0e
commit fc184f1cfa
10 changed files with 79 additions and 108 deletions

View file

@ -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()) }

View file

@ -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}")
}
}
}

View file

@ -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 }
}
}

View file

@ -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))
}
}

View file

@ -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.

View file

@ -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)
}
/**

View file

@ -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"

View file

@ -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

View file

@ -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)
}
}

View file

@ -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