diff --git a/app/src/main/java/io/legado/app/model/CacheBook.kt b/app/src/main/java/io/legado/app/model/CacheBook.kt index 473a3df80..455e94890 100644 --- a/app/src/main/java/io/legado/app/model/CacheBook.kt +++ b/app/src/main/java/io/legado/app/model/CacheBook.kt @@ -17,6 +17,7 @@ import io.legado.app.ui.config.otherConfig.OtherConfig import io.legado.app.utils.LogUtils import io.legado.app.utils.onEachParallel import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.currentCoroutineContext @@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger @@ -180,16 +182,14 @@ object CacheBook { _downloadSummaryFlow.value = buildSummary(stats) } - @Synchronized - fun getOrCreate(bookUrl: String): CacheBookModel? { - val book = appDb.bookDao.getBook(bookUrl) ?: return null - val source = appDb.bookSourceDao.getBookSource(book.origin) ?: return null - return getOrCreate(source, book) + suspend fun getOrCreate(bookUrl: String): CacheBookModel? = withContext(Dispatchers.IO) { + val book = appDb.bookDao.getBook(bookUrl) ?: return@withContext null + val source = appDb.bookSourceDao.getBookSource(book.origin) ?: return@withContext null + getOrCreate(source, book) } @Synchronized fun getOrCreate(bookSource: BookSource, book: Book): CacheBookModel { - updateBookSource(bookSource) cacheBookMap[book.bookUrl]?.let { model -> model.bookSource = bookSource model.book = book @@ -202,14 +202,16 @@ object CacheBook { } private fun updateBookSource(newBookSource: BookSource) { - cacheBookMap.forEach { (_, model) -> - if (model.bookSource.bookSourceUrl == newBookSource.bookSourceUrl) { + // 只有在必要时才更新,且避免在 getOrCreate 中高频调用 + val sourceUrl = newBookSource.bookSourceUrl + cacheBookMap.values.forEach { model -> + if (model.bookSource.bookSourceUrl == sourceUrl && model.bookSource != newBookSource) { model.bookSource = newBookSource } } } - fun start(context: Context, book: Book, selectedIndices: List) { + suspend fun start(context: Context, book: Book, selectedIndices: List) { start( context = context, request = CacheDownloadRequest( @@ -220,7 +222,7 @@ object CacheBook { ) } - fun start(context: Context, book: Book, startIndex: Int, endIndex: Int) { + suspend fun start(context: Context, book: Book, startIndex: Int, endIndex: Int) { start( context = context, request = CacheDownloadRequest( @@ -241,19 +243,28 @@ object CacheBook { } } - fun start(context: Context, requests: List) { - requests.asSequence() - .filter { it.hasValidSelection() } - .filter { request -> - appDb.bookDao.getBook(request.bookUrl)?.isLocal != true - } - .forEach { request -> - isPaused = false - startCacheBookService(context) { - action = IntentAction.start - putRequestExtras(request) - } + suspend fun start(context: Context, requests: List) = withContext(Dispatchers.IO) { + val validRequests = requests.filter { it.hasValidSelection() } + if (validRequests.isEmpty()) return@withContext + + val urls = validRequests.map { it.bookUrl }.toSet() + val localBookUrls = appDb.bookDao.getCacheableBooks(urls) + .filter { it.isLocal } + .map { it.bookUrl } + .toSet() + + val finalRequests = validRequests.filterNot { it.bookUrl in localBookUrls } + if (finalRequests.isEmpty()) return@withContext + + isPaused = false + // 如果请求较多,可以通过 Intent 传递一个特殊的标志让 Service 自己去检查队列, + // 或者分批发送。这里我们先简单处理,但确保不在主线程做数据库查询。 + finalRequests.forEach { request -> + startCacheBookService(context) { + action = IntentAction.start + putRequestExtras(request) } + } } private fun android.content.Intent.putRequestExtras(request: CacheDownloadRequest) { diff --git a/app/src/main/java/io/legado/app/model/cache/CacheDownloadStateStore.kt b/app/src/main/java/io/legado/app/model/cache/CacheDownloadStateStore.kt index b3b416f2f..e8eb4e368 100644 --- a/app/src/main/java/io/legado/app/model/cache/CacheDownloadStateStore.kt +++ b/app/src/main/java/io/legado/app/model/cache/CacheDownloadStateStore.kt @@ -116,12 +116,21 @@ class CacheDownloadStateStore { } private fun CacheDownloadState.recalculate(): CacheDownloadState { - val totalWaiting = books.values.sumOf { it.waitingCount } - val totalRunning = books.values.sumOf { it.runningIndices.size } - val totalPaused = books.values.sumOf { it.pausedIndices.size } - val totalFailure = books.values.sumOf { it.failedIndices.size } + - books.values.count { it.failureMessage != null } - val totalSuccess = books.values.sumOf { it.successCount } + var totalWaiting = 0 + var totalRunning = 0 + var totalPaused = 0 + var totalFailure = 0 + var totalSuccess = 0 + + books.values.forEach { bookState -> + totalWaiting += bookState.waitingCount + totalRunning += bookState.runningIndices.size + totalPaused += bookState.pausedIndices.size + totalFailure += bookState.failedIndices.size + if (bookState.failureMessage != null) totalFailure++ + totalSuccess += bookState.successCount + } + return copy( isRunning = totalWaiting > totalPaused || totalRunning > 0, totalWaiting = totalWaiting, diff --git a/app/src/main/java/io/legado/app/service/CacheBookService.kt b/app/src/main/java/io/legado/app/service/CacheBookService.kt index fbaf5c478..13016c298 100644 --- a/app/src/main/java/io/legado/app/service/CacheBookService.kt +++ b/app/src/main/java/io/legado/app/service/CacheBookService.kt @@ -150,7 +150,7 @@ class CacheBookService : BaseService() { serviceCommandMutex.withLock { CacheBook.pauseAllFromService() notificationContent = CacheBook.downloadSummary - upCacheBookNotification() + upCacheBookNotification(force = true) } } } @@ -160,7 +160,7 @@ class CacheBookService : BaseService() { CacheBook.resumeFromService() ensureDownloadJob() notificationContent = CacheBook.downloadSummary - upCacheBookNotification() + upCacheBookNotification(force = true) } } } @@ -364,7 +364,8 @@ class CacheBookService : BaseService() { } notificationContent = CacheBook.downloadSummary - upCacheBookNotification() + // 移除这里的直接调用,依靠 onCreate 的循环更新 + // upCacheBookNotification() } private fun AdmissionRequest.isCurrent(): Boolean { @@ -521,7 +522,13 @@ class CacheBookService : BaseService() { } } - private fun upCacheBookNotification() { + private var lastNotificationTime = 0L + + private fun upCacheBookNotification(force: Boolean = false) { + val now = System.currentTimeMillis() + if (!force && now - lastNotificationTime < 500L) return + lastNotificationTime = now + val total = CacheBook.totalCount val progress = CacheBook.completedCount val pendingBookCount = synchronized(admissionQueue) { admissionQueue.size } diff --git a/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt index f213f0db3..0dddbe257 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt @@ -13,6 +13,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.doOnAttach import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import androidx.lifecycle.lifecycleScope import com.google.android.material.datepicker.MaterialDatePicker import io.legado.app.R import io.legado.app.base.VMBaseActivity @@ -48,6 +49,7 @@ import io.legado.app.utils.isTv import io.legado.app.utils.setLightStatusBar import io.legado.app.utils.setNavigationBarColorAuto import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat +import kotlinx.coroutines.launch import io.legado.app.utils.showDialogFragment import io.legado.app.utils.themeColor import io.legado.app.utils.viewbindingdelegate.viewBinding @@ -304,7 +306,9 @@ abstract class BaseReadBookActivity : val end = editEnd.text!!.toString().let { if (it.isEmpty()) book.totalChapterNum else it.toInt() } - CacheBook.start(this@BaseReadBookActivity, book, start - 1, end - 1) + lifecycleScope.launch { + CacheBook.start(this@BaseReadBookActivity, book, start - 1, end - 1) + } } } cancelButton() diff --git a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt index 439fe15a1..46f278a63 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/rules/RuleEditSheet.kt @@ -38,6 +38,7 @@ import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.widget.components.AppFloatingActionButton import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.button.MediumIconButton +import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet