mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01: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:
		| @@ -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,11 +271,9 @@ 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) | ||||
|             } | ||||
|             downloadManager.downloadChapters(manga, chapters) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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<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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user