[优化] 下载优化

This commit is contained in:
HapeLee
2026-04-30 13:07:58 +08:00
parent 849943b787
commit 5ddaa70f67
3 changed files with 55 additions and 7 deletions
@@ -55,6 +55,7 @@ class CacheBookModel(
private val onDownloadSet = linkedSetOf<Int>() private val onDownloadSet = linkedSetOf<Int>()
private val canceledDownloadSet = hashSetOf<Int>() private val canceledDownloadSet = hashSetOf<Int>()
private val pausedChapterSet = linkedSetOf<Int>() private val pausedChapterSet = linkedSetOf<Int>()
private val pendingReadRequestMap = hashMapOf<Int, Boolean>()
private val chapterTasks = hashMapOf<Int, Coroutine<*>>() private val chapterTasks = hashMapOf<Int, Coroutine<*>>()
private val tasks = CompositeCoroutine() private val tasks = CompositeCoroutine()
private val repository = CacheDownloadRepository() private val repository = CacheDownloadRepository()
@@ -197,6 +198,7 @@ class CacheBookModel(
canceledDownloadSet.clear() canceledDownloadSet.clear()
pausedChapterSet.clear() pausedChapterSet.clear()
chapterTasks.clear() chapterTasks.clear()
pendingReadRequestMap.clear()
tasks.clear() tasks.clear()
retryCountMap.clear() retryCountMap.clear()
isStopped = true isStopped = true
@@ -492,12 +494,17 @@ class CacheBookModel(
) { ) {
task.onSuccess { task.onSuccess {
onSuccess(chapter) onSuccess(chapter)
(it as? String)?.let { content ->
emitPendingReadContent(chapter, content)
}
}.onError { }.onError {
onPreError(chapter, it) onPreError(chapter, it)
delay(1000) delay(1000)
onPostError(chapter, it) onPostError(chapter, it)
emitPendingReadError(chapter, it)
}.onCancel { }.onCancel {
onCancel(chapterIndex) onCancel(chapterIndex)
emitPendingReadCanceled(chapter)
}.onFinally { }.onFinally {
chapterTasks.remove(chapterIndex)?.let { tasks.delete(it) } chapterTasks.remove(chapterIndex)?.let { tasks.delete(it) }
onFinally() onFinally()
@@ -536,7 +543,10 @@ class CacheBookModel(
semaphore: Semaphore?, semaphore: Semaphore?,
resetPageOffset: Boolean = false resetPageOffset: Boolean = false
) { ) {
if (!markChapterDownloadStarted(chapter.index)) return if (!markChapterDownloadStarted(chapter.index)) {
markPendingReadRequest(chapter.index, resetPageOffset)
return
}
repository.downloadContentTask( repository.downloadContentTask(
scope = scope, scope = scope,
bookSource = bookSource, bookSource = bookSource,
@@ -551,11 +561,13 @@ class CacheBookModel(
ReadBook.downloadedChapters.add(chapter.index) ReadBook.downloadedChapters.add(chapter.index)
ReadBook.downloadFailChapters.remove(chapter.index) ReadBook.downloadFailChapters.remove(chapter.index)
downloadFinish(chapter, content, resetPageOffset) downloadFinish(chapter, content, resetPageOffset)
emitPendingReadContent(chapter, content)
}.onError { }.onError {
onError(chapter, it) onError(chapter, it)
ReadBook.downloadFailChapters[chapter.index] = ReadBook.downloadFailChapters[chapter.index] =
(ReadBook.downloadFailChapters[chapter.index] ?: 0) + 1 (ReadBook.downloadFailChapters[chapter.index] ?: 0) + 1
downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}", resetPageOffset) downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}", resetPageOffset)
emitPendingReadError(chapter, it)
}.onCancel { }.onCancel {
onCancel(chapter.index, requeue = false) onCancel(chapter.index, requeue = false)
downloadFinish(chapter, "download canceled", resetPageOffset, true) downloadFinish(chapter, "download canceled", resetPageOffset, true)
@@ -573,6 +585,31 @@ class CacheBookModel(
return true return true
} }
@Synchronized
private fun markPendingReadRequest(index: Int, resetPageOffset: Boolean) {
pendingReadRequestMap[index] = pendingReadRequestMap[index] == true || resetPageOffset
}
@Synchronized
private fun consumePendingReadRequest(index: Int): Boolean? {
return pendingReadRequestMap.remove(index)
}
private fun emitPendingReadContent(chapter: BookChapter, content: String) {
val resetPageOffset = consumePendingReadRequest(chapter.index) ?: return
downloadFinish(chapter, content, resetPageOffset)
}
private fun emitPendingReadError(chapter: BookChapter, error: Throwable) {
val resetPageOffset = consumePendingReadRequest(chapter.index) ?: return
downloadFinish(chapter, "获取正文失败\n${error.localizedMessage}", resetPageOffset)
}
private fun emitPendingReadCanceled(chapter: BookChapter) {
val resetPageOffset = consumePendingReadRequest(chapter.index) ?: return
downloadFinish(chapter, "download canceled", resetPageOffset)
}
private fun downloadFinish( private fun downloadFinish(
chapter: BookChapter, chapter: BookChapter,
content: String, content: String,
@@ -71,7 +71,7 @@ class CacheDownloadRepository {
context: CoroutineContext, context: CoroutineContext,
start: CoroutineStart = CoroutineStart.LAZY, start: CoroutineStart = CoroutineStart.LAZY,
executeContext: CoroutineContext = context, executeContext: CoroutineContext = context,
): Coroutine<Unit> { ): Coroutine<String> {
return Coroutine.async( return Coroutine.async(
scope = scope, scope = scope,
context = context, context = context,
@@ -126,15 +126,17 @@ class SearchViewModel(
} }
SearchIntent.SelectAllScope -> { SearchIntent.SelectAllScope -> {
val oldScope = searchScope.toString()
searchScope.update("") searchScope.update("")
syncScopeState() syncScopeState(restartSearch = true, oldScope = oldScope)
} }
is SearchIntent.ToggleScopeGroup -> toggleScopeGroup(intent.groupName) is SearchIntent.ToggleScopeGroup -> toggleScopeGroup(intent.groupName)
is SearchIntent.ToggleScopeSource -> toggleScopeSource(intent.source) is SearchIntent.ToggleScopeSource -> toggleScopeSource(intent.source)
is SearchIntent.RemoveScopeItem -> { is SearchIntent.RemoveScopeItem -> {
val oldScope = searchScope.toString()
searchScope.remove(intent.scopeName) searchScope.remove(intent.scopeName)
syncScopeState() syncScopeState(restartSearch = true, oldScope = oldScope)
} }
is SearchIntent.TogglePrecision -> { is SearchIntent.TogglePrecision -> {
@@ -370,6 +372,7 @@ class SearchViewModel(
} }
private fun toggleScopeGroup(groupName: String) { private fun toggleScopeGroup(groupName: String) {
val oldScope = searchScope.toString()
if (searchScope.isSource()) { if (searchScope.isSource()) {
searchScope.update("") searchScope.update("")
} }
@@ -380,10 +383,11 @@ class SearchViewModel(
selected.add(groupName) selected.add(groupName)
} }
searchScope.update(selected.toList()) searchScope.update(selected.toList())
syncScopeState() syncScopeState(restartSearch = true, oldScope = oldScope)
} }
private fun toggleScopeSource(source: BookSourcePart) { private fun toggleScopeSource(source: BookSourcePart) {
val oldScope = searchScope.toString()
val selectedUrls = if (searchScope.isSource()) { val selectedUrls = if (searchScope.isSource()) {
searchScope.sourceUrls.toMutableSet() searchScope.sourceUrls.toMutableSet()
} else { } else {
@@ -404,7 +408,7 @@ class SearchViewModel(
} }
searchScope.updateSources(selectedSources) searchScope.updateSources(selectedSources)
} }
syncScopeState() syncScopeState(restartSearch = true, oldScope = oldScope)
} }
private fun handleEmptyScopeActionConfirmed() { private fun handleEmptyScopeActionConfirmed() {
@@ -429,7 +433,11 @@ class SearchViewModel(
} }
} }
private fun syncScopeState() { private fun syncScopeState(
restartSearch: Boolean = false,
oldScope: String? = null,
) {
val scopeChanged = oldScope == null || oldScope != searchScope.toString()
_uiState.update { _uiState.update {
it.copy( it.copy(
scopeDisplay = searchScope.display, scopeDisplay = searchScope.display,
@@ -439,6 +447,9 @@ class SearchViewModel(
isSourceScope = searchScope.isSource(), isSourceScope = searchScope.isSource(),
) )
} }
if (restartSearch && scopeChanged) {
restartCommittedSearchIfNeeded()
}
} }
private fun List<SearchBook>.toSearchResultItems( private fun List<SearchBook>.toSearchResultItems(