[优化] 下载优化

This commit is contained in:
HapeLee
2026-04-29 01:42:08 +08:00
parent 12b20ce5d8
commit 00dc733ec8
2 changed files with 58 additions and 65 deletions
@@ -679,8 +679,8 @@ private fun BookshelfManageScreen(
val cacheCount = remember(renderVersion, book.bookUrl) { val cacheCount = remember(renderVersion, book.bookUrl) {
viewModel.getCacheCount(book.bookUrl) ?: 0 viewModel.getCacheCount(book.bookUrl) ?: 0
} }
val downloadState = state.downloadStates[book.bookUrl] val downloadState = viewModel.getBookDownloadState(book.bookUrl)
val isPreparingDownload = state.pendingDownloadBookUrls.contains(book.bookUrl) val isPreparingDownload = viewModel.isBookPreparingDownload(book.bookUrl)
val waitingDownloadCount = downloadState?.waitingCount ?: 0 val waitingDownloadCount = downloadState?.waitingCount ?: 0
val runningDownloadCount = downloadState?.runningIndices?.size ?: 0 val runningDownloadCount = downloadState?.runningIndices?.size ?: 0
val isDownloadingInCacheModel = viewModel.isBookDownloading(book.bookUrl) val isDownloadingInCacheModel = viewModel.isBookDownloading(book.bookUrl)
@@ -688,7 +688,7 @@ private fun BookshelfManageScreen(
waitingDownloadCount > 0 || waitingDownloadCount > 0 ||
runningDownloadCount > 0 || runningDownloadCount > 0 ||
isDownloadingInCacheModel isDownloadingInCacheModel
val downloadFailureText = state.downloadFailureMessages[book.bookUrl]?.let { val downloadFailureText = viewModel.getDownloadFailureMessage(book.bookUrl)?.let {
stringResource(R.string.cache_download_failed, it) stringResource(R.string.cache_download_failed, it)
} }
val isSelected = selectedBookUrls.contains(book.bookUrl) val isSelected = selectedBookUrls.contains(book.bookUrl)
@@ -73,9 +73,6 @@ data class BookshelfManageScreenUiState(
val bookSort: Int = BookshelfConfig.bookshelfSort, val bookSort: Int = BookshelfConfig.bookshelfSort,
val bookSortOrder: Int = BookshelfConfig.bookshelfSortOrder, val bookSortOrder: Int = BookshelfConfig.bookshelfSortOrder,
val isDownloadRunning: Boolean = false, val isDownloadRunning: Boolean = false,
val pendingDownloadBookUrls: Set<String> = emptySet(),
val downloadStates: Map<String, CacheBookDownloadState> = emptyMap(),
val downloadFailureMessages: Map<String, String> = emptyMap(),
val isChangingSource: Boolean = false, val isChangingSource: Boolean = false,
val changeSourceProgress: String? = null, val changeSourceProgress: String? = null,
val changeSourceMessage: String? = null, val changeSourceMessage: String? = null,
@@ -172,6 +169,9 @@ class BookshelfManageScreenViewModel(
val effects = _effects.asSharedFlow() val effects = _effects.asSharedFlow()
private val cacheCounts = ConcurrentHashMap<String, Int>() private val cacheCounts = ConcurrentHashMap<String, Int>()
private val downloadStates = ConcurrentHashMap<String, CacheBookDownloadState>()
private val pendingDownloadBookUrls = ConcurrentHashMap.newKeySet<String>()
private val downloadFailureMessages = ConcurrentHashMap<String, String>()
private var booksJob: Job? = null private var booksJob: Job? = null
private var groupsJob: Job? = null private var groupsJob: Job? = null
private var cacheLoadJob: Job? = null private var cacheLoadJob: Job? = null
@@ -309,6 +309,18 @@ class BookshelfManageScreenViewModel(
return CacheBook.cacheBookMap[bookUrl]?.isStop() == false return CacheBook.cacheBookMap[bookUrl]?.isStop() == false
} }
fun getBookDownloadState(bookUrl: String): CacheBookDownloadState? {
return downloadStates[bookUrl]
}
fun getDownloadFailureMessage(bookUrl: String): String? {
return downloadFailureMessages[bookUrl]
}
fun isBookPreparingDownload(bookUrl: String): Boolean {
return pendingDownloadBookUrls.contains(bookUrl)
}
private fun initialize(groupId: Long) { private fun initialize(groupId: Long) {
_uiState.update { it.copy(groupId = groupId) } _uiState.update { it.copy(groupId = groupId) }
syncExportConfig() syncExportConfig()
@@ -385,31 +397,27 @@ class BookshelfManageScreenViewModel(
} }
viewModelScope.launch { viewModelScope.launch {
CacheBook.downloadStateFlow.collect { downloadState -> CacheBook.downloadStateFlow.collect { downloadState ->
_uiState.update { state -> val successfulBookUrls = downloadState.books
val successfulBookUrls = downloadState.books .filterValues {
.filterValues { it.successCount > 0 &&
it.successCount > 0 && it.failedIndices.isEmpty() &&
it.failedIndices.isEmpty() && it.failureMessage == null
it.failureMessage == null }
} .keys
.keys val failureMsgs = downloadState.books.mapNotNull { (bookUrl, bookState) ->
val failureMessages = downloadState.books.mapNotNull { (bookUrl, bookState) -> val message = bookState.failureMessage ?: if (bookState.failedIndices.isNotEmpty()) {
val message = bookState.failureMessage ?: if (bookState.failedIndices.isNotEmpty()) { "${bookState.failedIndices.size}"
"${bookState.failedIndices.size}" } else {
} else { null
null }
} message?.let { bookUrl to it }
message?.let { bookUrl to it } }.toMap()
}.toMap() pendingDownloadBookUrls.removeAll(downloadState.books.keys)
state.copy( downloadStates.clear()
isDownloadRunning = downloadState.isRunning, downloadStates.putAll(downloadState.books)
pendingDownloadBookUrls = state.pendingDownloadBookUrls - downloadState.books.keys, successfulBookUrls.forEach { downloadFailureMessages.remove(it) }
downloadStates = downloadState.books, downloadFailureMessages.putAll(failureMsgs)
downloadFailureMessages = ( _uiState.update { it.copy(isDownloadRunning = downloadState.isRunning) }
state.downloadFailureMessages - successfulBookUrls
) + failureMessages,
)
}
downloadState.books.keys.forEach { bookUrl -> downloadState.books.keys.forEach { bookUrl ->
scheduleDownloadStatusRefresh(bookUrl) scheduleDownloadStatusRefresh(bookUrl)
} }
@@ -475,13 +483,17 @@ class BookshelfManageScreenViewModel(
cacheCounts.remove(bookUrl) cacheCounts.remove(bookUrl)
} }
} }
val changedBookUrls = linkedSetOf<String>()
books.forEach { book -> books.forEach { book ->
if (!book.isLocal && !cacheCounts.containsKey(book.bookUrl)) { if (!book.isLocal && !cacheCounts.containsKey(book.bookUrl)) {
cacheCounts[book.bookUrl] = calculateCacheCount(book) cacheCounts[book.bookUrl] = calculateCacheCount(book)
emitBookChanged(book.bookUrl) changedBookUrls.add(book.bookUrl)
} }
ensureActive() ensureActive()
} }
if (changedBookUrls.isNotEmpty()) {
_uiState.update { it.copy(cacheVersion = it.cacheVersion + 1) }
}
} }
} }
@@ -539,9 +551,7 @@ class BookshelfManageScreenViewModel(
private fun startDownloadForVisibleBooks(books: List<Book>, downloadAllChapters: Boolean) { private fun startDownloadForVisibleBooks(books: List<Book>, downloadAllChapters: Boolean) {
val bookUrls = books.mapTo(hashSetOf()) { it.bookUrl } val bookUrls = books.mapTo(hashSetOf()) { it.bookUrl }
_uiState.update { bookUrls.forEach { downloadFailureMessages.remove(it) }
it.copy(downloadFailureMessages = it.downloadFailureMessages - bookUrls)
}
execute { execute {
batchCacheDownloadUseCase.execute( batchCacheDownloadUseCase.execute(
bookUrls = bookUrls, bookUrls = bookUrls,
@@ -555,40 +565,28 @@ class BookshelfManageScreenViewModel(
private fun toggleBookDownload(book: Book) { private fun toggleBookDownload(book: Book) {
if (book.isLocal) return if (book.isLocal) return
if (isBookDownloading(book.bookUrl) || uiState.value.pendingDownloadBookUrls.contains(book.bookUrl)) { if (isBookDownloading(book.bookUrl) || pendingDownloadBookUrls.contains(book.bookUrl)) {
CacheBook.remove(context, book.bookUrl) CacheBook.remove(context, book.bookUrl)
_uiState.update { pendingDownloadBookUrls.remove(book.bookUrl)
it.copy(pendingDownloadBookUrls = it.pendingDownloadBookUrls - book.bookUrl) emitBookChanged(book.bookUrl)
}
syncDownloadRunning() syncDownloadRunning()
} else { } else {
_uiState.update { pendingDownloadBookUrls.add(book.bookUrl)
it.copy( downloadFailureMessages.remove(book.bookUrl)
pendingDownloadBookUrls = it.pendingDownloadBookUrls + book.bookUrl, emitBookChanged(book.bookUrl)
downloadFailureMessages = it.downloadFailureMessages - book.bookUrl
)
}
execute { execute {
cacheBookChaptersUseCase.executeRange(book.bookUrl, 0, book.lastChapterIndex) cacheBookChaptersUseCase.executeRange(book.bookUrl, 0, book.lastChapterIndex)
}.onSuccess { count -> }.onSuccess { count ->
if (count <= 0) { if (count <= 0) {
_uiState.update { pendingDownloadBookUrls.remove(book.bookUrl)
it.copy( downloadFailureMessages[book.bookUrl] = "没有可缓存的章节"
pendingDownloadBookUrls = it.pendingDownloadBookUrls - book.bookUrl, emitBookChanged(book.bookUrl)
downloadFailureMessages = it.downloadFailureMessages +
(book.bookUrl to "没有可缓存的章节")
)
}
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("没有可缓存的章节")) _effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("没有可缓存的章节"))
} }
}.onError { error -> }.onError { error ->
_uiState.update { pendingDownloadBookUrls.remove(book.bookUrl)
it.copy( downloadFailureMessages[book.bookUrl] = error.localizedMessage ?: "未知错误"
pendingDownloadBookUrls = it.pendingDownloadBookUrls - book.bookUrl, emitBookChanged(book.bookUrl)
downloadFailureMessages = it.downloadFailureMessages +
(book.bookUrl to (error.localizedMessage ?: "未知错误"))
)
}
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("缓存失败\n${error.localizedMessage}")) _effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("缓存失败\n${error.localizedMessage}"))
}.onFinally { }.onFinally {
syncDownloadRunning() syncDownloadRunning()
@@ -661,9 +659,7 @@ class BookshelfManageScreenViewModel(
private fun downloadBooks(bookUrls: Set<String>, downloadAllChapters: Boolean) { private fun downloadBooks(bookUrls: Set<String>, downloadAllChapters: Boolean) {
if (bookUrls.isEmpty()) return if (bookUrls.isEmpty()) return
_uiState.update { bookUrls.forEach { downloadFailureMessages.remove(it) }
it.copy(downloadFailureMessages = it.downloadFailureMessages - bookUrls)
}
execute { execute {
batchCacheDownloadUseCase.execute( batchCacheDownloadUseCase.execute(
bookUrls = bookUrls, bookUrls = bookUrls,
@@ -972,9 +968,6 @@ class BookshelfManageScreenViewModel(
private fun emitBooksChanged(bookUrls: Set<String>) { private fun emitBooksChanged(bookUrls: Set<String>) {
if (bookUrls.isEmpty()) return if (bookUrls.isEmpty()) return
_uiState.update { it.copy(cacheVersion = it.cacheVersion + 1) } _uiState.update { it.copy(cacheVersion = it.cacheVersion + 1) }
bookUrls.forEach { bookUrl ->
_effects.tryEmit(BookshelfManageScreenEffect.NotifyBookChanged(bookUrl))
}
} }
} }