下载优化

This commit is contained in:
HapeLee committed 2026-05-16 01:14:26 +08:00
1 parent d5ece78383
commit 59afc9dd94
4 files changed
+47 -19

No files matched your search

@@ -78,7 +78,7 @@ object CacheBook {
} }
var emitted = false var emitted = false
taskMap.forEach { (_, model) -> taskMap.forEach { (_, model) ->
if (model.hasRunnableDownloads()) { if (model.hasLaunchableChapters()) {
emit(model) emit(model)
emitted = true emitted = true
} }
@@ -153,6 +153,16 @@ class CacheBookModel(
) )
} }
/**
* 有可启动的下载任务(队列中有待下载章节或正在加载目录)。
* 与 [hasRunnableDownloads] 不同,不包含已在执行中的任务。
* 用于判断是否需要向下载循环 emit 模型,避免无新章节可取时的高频空转。
*/
@Synchronized
fun hasLaunchableChapters(): Boolean {
return !isPaused && (queue.waitingCount() > 0 || isLoading)
}
@Synchronized @Synchronized
fun diagnostics(): Diagnostics { fun diagnostics(): Diagnostics {
return Diagnostics( return Diagnostics(
@@ -579,7 +589,7 @@ class CacheBookModel(
return true return true
} }
} }
repository.downloadContentTask( val task = repository.downloadContentTask(
scope = scope, scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
@@ -604,8 +614,13 @@ class CacheBookModel(
onCancel(chapter.index, requeue = false) onCancel(chapter.index, requeue = false)
downloadFinish(chapter, "download canceled", resetPageOffset, true) downloadFinish(chapter, "download canceled", resetPageOffset, true)
}.onFinally { }.onFinally {
chapterTasks.remove(chapter.index)
host.onTaskQueuesChanged(book.bookUrl) host.onTaskQueuesChanged(book.bookUrl)
}.start() }
task.start()
synchronized(this) {
chapterTasks[chapter.index] = task
}
return true return true
} }
@@ -172,7 +172,21 @@ class CacheDownloadQueue {
val indexCount = indices.count { val indexCount = indices.count {
!emittedIndices.contains(it) && !removedIndices.contains(it) !emittedIndices.contains(it) && !removedIndices.contains(it)
} }
val rangeCount = ranges.sumOf { it.remainingCount(emittedIndices, removedIndices) } val rangeCount = if (ranges.size <= 1) {
ranges.sumOf { it.remainingCount(emittedIndices, removedIndices) }
} else {
val seen = mutableSetOf<Int>()
ranges.forEach { cursor ->
var i = cursor.next
while (i <= cursor.end) {
if (!emittedIndices.contains(i) && !removedIndices.contains(i)) {
seen.add(i)
}
i++
}
}
seen.size
}
return indexCount + rangeCount return indexCount + rangeCount
} }
@@ -12,6 +12,7 @@ import io.legado.app.constant.NotificationId
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.help.book.update import io.legado.app.help.book.update
import io.legado.app.model.CacheBook import io.legado.app.model.CacheBook
import io.legado.app.model.CacheBookModel
import io.legado.app.model.cache.CacheDownloadAdmissionQueue import io.legado.app.model.cache.CacheDownloadAdmissionQueue
import io.legado.app.model.cache.CacheDownloadRequest import io.legado.app.model.cache.CacheDownloadRequest
import io.legado.app.model.cache.CacheDownloadSource import io.legado.app.model.cache.CacheDownloadSource
@@ -24,11 +25,11 @@ import io.legado.app.utils.activityPendingIntent
import io.legado.app.utils.servicePendingIntent import io.legado.app.utils.servicePendingIntent
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -310,10 +311,10 @@ class CacheBookService : BaseService() {
kotlin.runCatching { kotlin.runCatching {
WebBook.getBookInfoAwait(cacheBook.bookSource, book) WebBook.getBookInfoAwait(cacheBook.bookSource, book)
}.onFailure { }.onFailure {
removeDownload(request.bookUrl)
markBookAdmissionFailed( markBookAdmissionFailed(
request.bookUrl, request.bookUrl,
getString(R.string.error_get_book_info) getString(R.string.error_get_book_info),
model = cacheBook,
) )
AppLog.put( AppLog.put(
"《$name》目录为空且加载详情页失败\n${it.localizedMessage}", "《$name》目录为空且加载详情页失败\n${it.localizedMessage}",
@@ -333,10 +334,10 @@ class CacheBookService : BaseService() {
book.totalChapterNum = 0 book.totalChapterNum = 0
book.update() book.update()
} }
removeDownload(request.bookUrl)
markBookAdmissionFailed( markBookAdmissionFailed(
request.bookUrl, request.bookUrl,
getString(R.string.error_get_chapter_list) getString(R.string.error_get_chapter_list),
model = cacheBook,
) )
AppLog.put( AppLog.put(
"《$name》目录为空且加载目录失败\n${it.localizedMessage}", "《$name》目录为空且加载目录失败\n${it.localizedMessage}",
@@ -408,8 +409,15 @@ class CacheBookService : BaseService() {
return removedQueued || removedAdmission || removedActive return removedQueued || removedAdmission || removedActive
} }
private fun markBookAdmissionFailed(bookUrl: String, message: String) { private fun markBookAdmissionFailed(
bookUrl: String,
message: String,
model: CacheBookModel? = null,
) {
removeQueuedBook(bookUrl) removeQueuedBook(bookUrl)
if (model != null) {
CacheBook.removeModelFromService(bookUrl, model)
}
CacheBook.markBookFailed(bookUrl, message) CacheBook.markBookFailed(bookUrl, message)
} }
@@ -476,15 +484,6 @@ class CacheBookService : BaseService() {
} }
private fun removeDownload(bookUrl: String?) {
CacheBook.cacheBookMap[bookUrl]?.stop()
if (CacheBook.isRun) {
ensureDownloadJob()
return
}
stopIfIdle()
}
private suspend fun runDownloadLoop() { private suspend fun runDownloadLoop() {
try { try {
var idleSince = -1L var idleSince = -1L