Browse Source

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)
arkon 2 years ago
parent
commit
fc184f1cfa

+ 2 - 2
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()) }

+ 0 - 51
app/src/main/java/eu/kanade/domain/history/interactor/GetNextChapter.kt

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

+ 33 - 0
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<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 }
+    }
+}

+ 3 - 3
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))
     }
 }

+ 4 - 0
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<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.

+ 1 - 2
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

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

+ 2 - 4
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt

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

+ 29 - 39
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,43 +459,29 @@ 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)
-        }
-    }
-
-    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 }
-    }
+        presenterScope.launchIO {
+            val isNextChapterDownloaded = downloadManager.isChapterDownloaded(
+                nextChapter.name,
+                nextChapter.scanlator,
+                manga.title,
+                manga.source,
+            )
+            if (!isNextChapterDownloaded) return@launchIO
 
-    /**
-     * 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)
+            val chaptersToDownload = getNextUnreadChapters.await(manga.id!!, nextChapter.id!!)
+                .take(amount)
+            downloadManager.downloadChapters(
+                manga.toDomainManga()!!,
+                chaptersToDownload.map { it.toDbChapter() },
+            )
+        }
     }
 
     /**

+ 4 - 4
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)
         }
     }

+ 0 - 2
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