From fc184f1cfa929328c41b0ec374503afe204d6c15 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 30 Oct 2022 16:59:33 -0400 Subject: [PATCH] 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) --- .../java/eu/kanade/domain/DomainModule.kt | 4 +- .../history/interactor/GetNextChapter.kt | 51 -------------- .../interactor/GetNextUnreadChapters.kt | 33 +++++++++ .../manga/interactor/NetworkToLocalManga.kt | 6 +- .../tachiyomi/data/download/Downloader.kt | 4 ++ .../data/library/LibraryUpdateService.kt | 3 +- .../data/notification/NotificationReceiver.kt | 6 +- .../tachiyomi/ui/reader/ReaderPresenter.kt | 70 ++++++++----------- .../ui/recent/history/HistoryPresenter.kt | 8 +-- .../util/system/BooleanExtensions.kt | 2 - 10 files changed, 79 insertions(+), 108 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapter.kt create mode 100644 app/src/main/java/eu/kanade/domain/history/interactor/GetNextUnreadChapters.kt diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 196c2a347..c8c7d4dfd 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -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()) } diff --git a/app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapter.kt b/app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapter.kt deleted file mode 100644 index 602a73281..000000000 --- a/app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapter.kt +++ /dev/null @@ -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}") - } - } -} diff --git a/app/src/main/java/eu/kanade/domain/history/interactor/GetNextUnreadChapters.kt b/app/src/main/java/eu/kanade/domain/history/interactor/GetNextUnreadChapters.kt new file mode 100644 index 000000000..e03114fe2 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/history/interactor/GetNextUnreadChapters.kt @@ -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 { + 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 } + } +} diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt index 9a2b3f4dd..d6ef9f214 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt @@ -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)) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 584ebcef3..198746c69 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -244,6 +244,10 @@ class Downloader( * @param autoStart whether to start the downloader after enqueing the chapters. */ fun queueChapters(manga: Manga, chapters: List, 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. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 81787edc4..77bbbaaf7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -415,8 +415,7 @@ class LibraryUpdateService( private fun downloadChapters(manga: Manga, chapters: List) { // 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) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 9f8db886b..f538d5360 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -271,11 +271,9 @@ class NotificationReceiver : BroadcastReceiver() { */ private fun downloadChapters(chapterUrls: Array, 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) - } + downloadManager.downloadChapters(manga, chapters) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 1a2c141b1..b33199ef3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -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( - nextChapter.name, - nextChapter.scanlator, - manga.title, - manga.source, - skipCache = true, - ) || downloadManager.getChapterDownloadOrNull(nextChapter) != null - if (isNextChapterDownloadedOrQueued) { - downloadAutoNextChapters(chaptersNumberToDownload, nextChapter.id, nextChapter.read) + + presenterScope.launchIO { + val isNextChapterDownloaded = downloadManager.isChapterDownloaded( + nextChapter.name, + nextChapter.scanlator, + manga.title, + manga.source, + ) + if (!isNextChapterDownloaded) return@launchIO + + val chaptersToDownload = getNextUnreadChapters.await(manga.id!!, nextChapter.id!!) + .take(amount) + downloadManager.downloadChapters( + manga.toDomainManga()!!, + chaptersToDownload.map { it.toDbChapter() }, + ) } } - private fun downloadAutoNextChapters(choice: Int, nextChapterId: Long?, isNextChapterRead: Boolean) { - val chaptersToDownload = getNextUnreadChaptersSorted(nextChapterId).take(choice - 1 + isNextChapterRead.toInt()) - if (chaptersToDownload.isNotEmpty()) { - downloadChapters(chaptersToDownload) - } - } - - private fun getNextUnreadChaptersSorted(nextChapterId: Long?): List { - 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) { - downloadManager.downloadChapters(manga?.toDomainManga()!!, chapters) - } - /** * Removes [currentChapter] from download queue * if setting is enabled and [currentChapter] is queued for download diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt index 464eafe75..caa021dae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/BooleanExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/BooleanExtensions.kt index 130205aa5..d32476757 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/BooleanExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/BooleanExtensions.kt @@ -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