优化 #5564
This commit is contained in:
@@ -11,18 +11,29 @@ import io.legado.app.data.entities.BookSource
|
|||||||
import io.legado.app.exception.ConcurrentException
|
import io.legado.app.exception.ConcurrentException
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
import io.legado.app.help.book.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.coroutine.CompositeCoroutine
|
import io.legado.app.help.coroutine.CompositeCoroutine
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import io.legado.app.model.webBook.WebBook
|
import io.legado.app.model.webBook.WebBook
|
||||||
import io.legado.app.service.CacheBookService
|
import io.legado.app.service.CacheBookService
|
||||||
|
import io.legado.app.utils.onEachParallel
|
||||||
import io.legado.app.utils.postEvent
|
import io.legado.app.utils.postEvent
|
||||||
import io.legado.app.utils.startService
|
import io.legado.app.utils.startService
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@@ -30,6 +41,9 @@ object CacheBook {
|
|||||||
|
|
||||||
val cacheBookMap = ConcurrentHashMap<String, CacheBookModel>()
|
val cacheBookMap = ConcurrentHashMap<String, CacheBookModel>()
|
||||||
|
|
||||||
|
private val workingState = MutableStateFlow(true)
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getOrCreate(bookUrl: String): CacheBookModel? {
|
fun getOrCreate(bookUrl: String): CacheBookModel? {
|
||||||
val book = appDb.bookDao.getBook(bookUrl) ?: return null
|
val book = appDb.bookDao.getBook(bookUrl) ?: return null
|
||||||
@@ -104,6 +118,36 @@ object CacheBook {
|
|||||||
errorDownloadMap.clear()
|
errorDownloadMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setWorkingState(value: Boolean) {
|
||||||
|
workingState.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun startProcessJob(context: CoroutineContext) = mutex.withLock {
|
||||||
|
setWorkingState(true)
|
||||||
|
flow {
|
||||||
|
while (currentCoroutineContext().isActive && cacheBookMap.isNotEmpty()) {
|
||||||
|
var emitted = false
|
||||||
|
|
||||||
|
cacheBookMap.forEach { (_, model) ->
|
||||||
|
if (!model.isLoading()) {
|
||||||
|
emit(model)
|
||||||
|
emitted = true
|
||||||
|
}
|
||||||
|
workingState.first { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emitted) {
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onEachParallel(AppConfig.threadCount) {
|
||||||
|
coroutineScope {
|
||||||
|
it.download(this, context)
|
||||||
|
}
|
||||||
|
}.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val downloadSummary: String
|
val downloadSummary: String
|
||||||
get() {
|
get() {
|
||||||
return "正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorDownloadMap.count()}|成功:${successDownloadSet.size}"
|
return "正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorDownloadMap.count()}|成功:${successDownloadSet.size}"
|
||||||
@@ -111,11 +155,12 @@ object CacheBook {
|
|||||||
|
|
||||||
val isRun: Boolean
|
val isRun: Boolean
|
||||||
get() {
|
get() {
|
||||||
var isRun = false
|
|
||||||
cacheBookMap.forEach {
|
cacheBookMap.forEach {
|
||||||
isRun = isRun || it.value.isRun()
|
if (it.value.isRun()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return isRun
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private val waitCount: Int
|
private val waitCount: Int
|
||||||
@@ -146,6 +191,7 @@ object CacheBook {
|
|||||||
private val tasks = CompositeCoroutine()
|
private val tasks = CompositeCoroutine()
|
||||||
private var isStopped = false
|
private var isStopped = false
|
||||||
private var waitingRetry = false
|
private var waitingRetry = false
|
||||||
|
private var isLoading = true
|
||||||
|
|
||||||
val waitCount get() = waitDownloadSet.size
|
val waitCount get() = waitDownloadSet.size
|
||||||
val onDownloadCount get() = onDownloadSet.size
|
val onDownloadCount get() = onDownloadSet.size
|
||||||
@@ -156,7 +202,7 @@ object CacheBook {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun isRun(): Boolean {
|
fun isRun(): Boolean {
|
||||||
return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty()
|
return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty() || isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -164,6 +210,11 @@ object CacheBook {
|
|||||||
return isStopped || (!isRun() && !waitingRetry)
|
return isStopped || (!isRun() && !waitingRetry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun isLoading(): Boolean {
|
||||||
|
return isLoading
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun stop() {
|
fun stop() {
|
||||||
waitDownloadSet.clear()
|
waitDownloadSet.clear()
|
||||||
@@ -181,6 +232,7 @@ object CacheBook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cacheBookMap[book.bookUrl] = this
|
cacheBookMap[book.bookUrl] = this
|
||||||
|
isLoading = false
|
||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +295,7 @@ object CacheBook {
|
|||||||
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
|
||||||
val chapterIndex = waitDownloadSet.firstOrNull()
|
val chapterIndex = waitDownloadSet.firstOrNull()
|
||||||
if (chapterIndex == null) {
|
if (chapterIndex == null) {
|
||||||
if (onDownloadSet.isEmpty()) {
|
if (!isLoading && onDownloadSet.isEmpty()) {
|
||||||
cacheBookMap.remove(book.bookUrl)
|
cacheBookMap.remove(book.bookUrl)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -161,23 +161,8 @@ class CacheBookService : BaseService() {
|
|||||||
private fun download() {
|
private fun download() {
|
||||||
downloadJob?.cancel()
|
downloadJob?.cancel()
|
||||||
downloadJob = lifecycleScope.launch(cachePool) {
|
downloadJob = lifecycleScope.launch(cachePool) {
|
||||||
while (isActive) {
|
CacheBook.startProcessJob(cachePool)
|
||||||
if (!CacheBook.isRun) {
|
stopSelf()
|
||||||
stopSelf()
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
CacheBook.cacheBookMap.forEach {
|
|
||||||
val cacheBookModel = it.value
|
|
||||||
while (cacheBookModel.waitCount > 0) {
|
|
||||||
if (CacheBook.onDownloadCount < threadCount) {
|
|
||||||
cacheBookModel.download(this, cachePool)
|
|
||||||
} else {
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -213,28 +213,14 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
if (AppConfig.preDownloadNum == 0) return
|
if (AppConfig.preDownloadNum == 0) return
|
||||||
cacheBookJob?.cancel()
|
cacheBookJob?.cancel()
|
||||||
cacheBookJob = viewModelScope.launch(upTocPool) {
|
cacheBookJob = viewModelScope.launch(upTocPool) {
|
||||||
while (isActive) {
|
launch {
|
||||||
if (CacheBookService.isRun || !CacheBook.isRun) {
|
while (isActive && CacheBook.isRun) {
|
||||||
cacheBookJob?.cancel()
|
//有目录更新是不缓存,优先更新目录,现在更多网站限制并发
|
||||||
cacheBookJob = null
|
CacheBook.setWorkingState(waitUpTocBooks.isEmpty() && onUpTocBooks.isEmpty())
|
||||||
return@launch
|
delay(1000)
|
||||||
}
|
}
|
||||||
CacheBook.cacheBookMap.forEach {
|
|
||||||
val cacheBookModel = it.value
|
|
||||||
while (cacheBookModel.waitCount > 0) {
|
|
||||||
//有目录更新是不缓存,优先更新目录,现在更多网站限制并发
|
|
||||||
if (waitUpTocBooks.isEmpty()
|
|
||||||
&& onUpTocBooks.isEmpty()
|
|
||||||
&& CacheBook.onDownloadCount < threadCount
|
|
||||||
) {
|
|
||||||
cacheBookModel.download(this, upTocPool)
|
|
||||||
} else {
|
|
||||||
delay(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay(100)
|
|
||||||
}
|
}
|
||||||
|
CacheBook.startProcessJob(upTocPool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.FlowCollector
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
@@ -20,7 +21,6 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.produceIn
|
import kotlinx.coroutines.flow.produceIn
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
inline fun <T> Flow<T>.onEachParallel(
|
inline fun <T> Flow<T>.onEachParallel(
|
||||||
@@ -42,7 +42,7 @@ inline fun <T> Flow<T>.onEachParallelSafe(
|
|||||||
try {
|
try {
|
||||||
action(value)
|
action(value)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
}
|
}
|
||||||
emit(value)
|
emit(value)
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ inline fun <T, R> Flow<T>.mapParallelSafe(
|
|||||||
try {
|
try {
|
||||||
emit(transform(value))
|
emit(transform(value))
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.buffer(0)
|
}.buffer(0)
|
||||||
@@ -78,7 +78,7 @@ inline fun <T, R> Flow<T>.transformParallelSafe(
|
|||||||
try {
|
try {
|
||||||
transform(value)
|
transform(value)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.buffer(0)
|
}.buffer(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user