下载优化
This commit is contained in:
@@ -17,6 +17,7 @@ import io.legado.app.ui.config.otherConfig.OtherConfig
|
|||||||
import io.legado.app.utils.LogUtils
|
import io.legado.app.utils.LogUtils
|
||||||
import io.legado.app.utils.onEachParallel
|
import io.legado.app.utils.onEachParallel
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
@@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
@@ -180,16 +182,14 @@ object CacheBook {
|
|||||||
_downloadSummaryFlow.value = buildSummary(stats)
|
_downloadSummaryFlow.value = buildSummary(stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
suspend fun getOrCreate(bookUrl: String): CacheBookModel? = withContext(Dispatchers.IO) {
|
||||||
fun getOrCreate(bookUrl: String): CacheBookModel? {
|
val book = appDb.bookDao.getBook(bookUrl) ?: return@withContext null
|
||||||
val book = appDb.bookDao.getBook(bookUrl) ?: return null
|
val source = appDb.bookSourceDao.getBookSource(book.origin) ?: return@withContext null
|
||||||
val source = appDb.bookSourceDao.getBookSource(book.origin) ?: return null
|
getOrCreate(source, book)
|
||||||
return getOrCreate(source, book)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getOrCreate(bookSource: BookSource, book: Book): CacheBookModel {
|
fun getOrCreate(bookSource: BookSource, book: Book): CacheBookModel {
|
||||||
updateBookSource(bookSource)
|
|
||||||
cacheBookMap[book.bookUrl]?.let { model ->
|
cacheBookMap[book.bookUrl]?.let { model ->
|
||||||
model.bookSource = bookSource
|
model.bookSource = bookSource
|
||||||
model.book = book
|
model.book = book
|
||||||
@@ -202,14 +202,16 @@ object CacheBook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateBookSource(newBookSource: BookSource) {
|
private fun updateBookSource(newBookSource: BookSource) {
|
||||||
cacheBookMap.forEach { (_, model) ->
|
// 只有在必要时才更新,且避免在 getOrCreate 中高频调用
|
||||||
if (model.bookSource.bookSourceUrl == newBookSource.bookSourceUrl) {
|
val sourceUrl = newBookSource.bookSourceUrl
|
||||||
|
cacheBookMap.values.forEach { model ->
|
||||||
|
if (model.bookSource.bookSourceUrl == sourceUrl && model.bookSource != newBookSource) {
|
||||||
model.bookSource = newBookSource
|
model.bookSource = newBookSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(context: Context, book: Book, selectedIndices: List<Int>) {
|
suspend fun start(context: Context, book: Book, selectedIndices: List<Int>) {
|
||||||
start(
|
start(
|
||||||
context = context,
|
context = context,
|
||||||
request = CacheDownloadRequest(
|
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(
|
start(
|
||||||
context = context,
|
context = context,
|
||||||
request = CacheDownloadRequest(
|
request = CacheDownloadRequest(
|
||||||
@@ -241,19 +243,28 @@ object CacheBook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(context: Context, requests: List<CacheDownloadRequest>) {
|
suspend fun start(context: Context, requests: List<CacheDownloadRequest>) = withContext(Dispatchers.IO) {
|
||||||
requests.asSequence()
|
val validRequests = requests.filter { it.hasValidSelection() }
|
||||||
.filter { it.hasValidSelection() }
|
if (validRequests.isEmpty()) return@withContext
|
||||||
.filter { request ->
|
|
||||||
appDb.bookDao.getBook(request.bookUrl)?.isLocal != true
|
val urls = validRequests.map { it.bookUrl }.toSet()
|
||||||
}
|
val localBookUrls = appDb.bookDao.getCacheableBooks(urls)
|
||||||
.forEach { request ->
|
.filter { it.isLocal }
|
||||||
isPaused = false
|
.map { it.bookUrl }
|
||||||
startCacheBookService(context) {
|
.toSet()
|
||||||
action = IntentAction.start
|
|
||||||
putRequestExtras(request)
|
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) {
|
private fun android.content.Intent.putRequestExtras(request: CacheDownloadRequest) {
|
||||||
|
|||||||
@@ -116,12 +116,21 @@ class CacheDownloadStateStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun CacheDownloadState.recalculate(): CacheDownloadState {
|
private fun CacheDownloadState.recalculate(): CacheDownloadState {
|
||||||
val totalWaiting = books.values.sumOf { it.waitingCount }
|
var totalWaiting = 0
|
||||||
val totalRunning = books.values.sumOf { it.runningIndices.size }
|
var totalRunning = 0
|
||||||
val totalPaused = books.values.sumOf { it.pausedIndices.size }
|
var totalPaused = 0
|
||||||
val totalFailure = books.values.sumOf { it.failedIndices.size } +
|
var totalFailure = 0
|
||||||
books.values.count { it.failureMessage != null }
|
var totalSuccess = 0
|
||||||
val totalSuccess = books.values.sumOf { it.successCount }
|
|
||||||
|
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(
|
return copy(
|
||||||
isRunning = totalWaiting > totalPaused || totalRunning > 0,
|
isRunning = totalWaiting > totalPaused || totalRunning > 0,
|
||||||
totalWaiting = totalWaiting,
|
totalWaiting = totalWaiting,
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class CacheBookService : BaseService() {
|
|||||||
serviceCommandMutex.withLock {
|
serviceCommandMutex.withLock {
|
||||||
CacheBook.pauseAllFromService()
|
CacheBook.pauseAllFromService()
|
||||||
notificationContent = CacheBook.downloadSummary
|
notificationContent = CacheBook.downloadSummary
|
||||||
upCacheBookNotification()
|
upCacheBookNotification(force = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ class CacheBookService : BaseService() {
|
|||||||
CacheBook.resumeFromService()
|
CacheBook.resumeFromService()
|
||||||
ensureDownloadJob()
|
ensureDownloadJob()
|
||||||
notificationContent = CacheBook.downloadSummary
|
notificationContent = CacheBook.downloadSummary
|
||||||
upCacheBookNotification()
|
upCacheBookNotification(force = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,8 @@ class CacheBookService : BaseService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notificationContent = CacheBook.downloadSummary
|
notificationContent = CacheBook.downloadSummary
|
||||||
upCacheBookNotification()
|
// 移除这里的直接调用,依靠 onCreate 的循环更新
|
||||||
|
// upCacheBookNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AdmissionRequest.isCurrent(): Boolean {
|
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 total = CacheBook.totalCount
|
||||||
val progress = CacheBook.completedCount
|
val progress = CacheBook.completedCount
|
||||||
val pendingBookCount = synchronized(admissionQueue) { admissionQueue.size }
|
val pendingBookCount = synchronized(admissionQueue) { admissionQueue.size }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.doOnAttach
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
import com.google.android.material.datepicker.MaterialDatePicker
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.VMBaseActivity
|
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.setLightStatusBar
|
||||||
import io.legado.app.utils.setNavigationBarColorAuto
|
import io.legado.app.utils.setNavigationBarColorAuto
|
||||||
import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
|
import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.themeColor
|
import io.legado.app.utils.themeColor
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
@@ -304,7 +306,9 @@ abstract class BaseReadBookActivity :
|
|||||||
val end = editEnd.text!!.toString().let {
|
val end = editEnd.text!!.toString().let {
|
||||||
if (it.isEmpty()) book.totalChapterNum else it.toInt()
|
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()
|
cancelButton()
|
||||||
|
|||||||
@@ -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.AppFloatingActionButton
|
||||||
import io.legado.app.ui.widget.components.AppTextField
|
import io.legado.app.ui.widget.components.AppTextField
|
||||||
import io.legado.app.ui.widget.components.button.MediumIconButton
|
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.RoundDropdownMenu
|
||||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||||
|
|||||||
Reference in New Issue
Block a user