mirror of
https://github.com/gedoor/legado.git
synced 2024-07-04 23:36:56 +08:00
优化
This commit is contained in:
parent
77711af564
commit
2cda94547e
|
@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.viewbinding.ViewBinding
|
||||
import io.legado.app.help.coroutine.Coroutine
|
||||
import io.legado.app.utils.buildMainHandler
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import splitties.views.onLongClick
|
||||
import java.util.Collections
|
||||
|
@ -156,6 +157,7 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
|
|||
} else {
|
||||
DiffUtil.calculateDiff(callback)
|
||||
}
|
||||
ensureActive()
|
||||
handler.post {
|
||||
if (diffResult == null) {
|
||||
setItems(items)
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Application
|
|||
import android.os.Bundle
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.constant.AppConst
|
||||
import io.legado.app.constant.AppLog
|
||||
|
@ -12,27 +13,34 @@ import io.legado.app.data.appDb
|
|||
import io.legado.app.data.entities.Book
|
||||
import io.legado.app.data.entities.BookChapter
|
||||
import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.data.entities.BookSourcePart
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.help.book.BookHelp
|
||||
import io.legado.app.help.book.ContentProcessor
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.SourceConfig
|
||||
import io.legado.app.help.coroutine.CompositeCoroutine
|
||||
import io.legado.app.help.coroutine.Coroutine
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
import io.legado.app.utils.mapParallelSafe
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.ExecutorCoroutineDispatcher
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executors
|
||||
|
@ -48,9 +56,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
var author: String = ""
|
||||
private var fromReadBookActivity = false
|
||||
private var oldBook: Book? = null
|
||||
private var tasks = CompositeCoroutine()
|
||||
private var screenKey: String = ""
|
||||
private var bookSourceList = arrayListOf<BookSource>()
|
||||
private var bookSourceParts = arrayListOf<BookSourcePart>()
|
||||
private var searchBookList = arrayListOf<SearchBook>()
|
||||
private val searchBooks = Collections.synchronizedList(arrayListOf<SearchBook>())
|
||||
private val tocMap = ConcurrentHashMap<String, List<BookChapter>>()
|
||||
|
@ -58,7 +65,6 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
ContentProcessor.get(oldBook!!)
|
||||
}
|
||||
private var searchCallback: SourceCallback? = null
|
||||
private val emptyBookSource = BookSource()
|
||||
private val chapterNumRegex = "^\\[(\\d+)]".toRegex()
|
||||
private val comparatorBase by lazy {
|
||||
compareByDescending<SearchBook> { getBookScore(it) }
|
||||
|
@ -73,6 +79,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
.thenByDescending { it.chapterWordCount }
|
||||
.thenBy { it.originOrder }
|
||||
}
|
||||
private var task: Job? = null
|
||||
val bookMap = ConcurrentHashMap<String, Book>()
|
||||
val searchDataFlow = callbackFlow {
|
||||
|
||||
|
@ -120,9 +127,6 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
}.getOrDefault(searchBooks)
|
||||
}.flowOn(IO)
|
||||
|
||||
@Volatile
|
||||
private var searchIndex = -1
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
searchPool?.close()
|
||||
|
@ -145,7 +149,6 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
private fun initSearchPool() {
|
||||
searchPool = Executors
|
||||
.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()
|
||||
searchIndex = -1
|
||||
}
|
||||
|
||||
fun refresh(): Boolean {
|
||||
|
@ -160,85 +163,83 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
/**
|
||||
* 搜索书籍
|
||||
*/
|
||||
fun startSearch(origin: String? = null) {
|
||||
fun startSearch() {
|
||||
execute {
|
||||
stopSearch()
|
||||
origin?.let {
|
||||
bookSourceList.clear()
|
||||
bookSourceList.add(appDb.bookSourceDao.getBookSource(origin)!!)
|
||||
searchStateData.postValue(true)
|
||||
searchBooks.removeIf { it.origin == origin }
|
||||
initSearchPool()
|
||||
search()
|
||||
return@execute
|
||||
}
|
||||
appDb.searchBookDao.clear(name, author)
|
||||
searchBooks.clear()
|
||||
searchCallback?.upAdapter()
|
||||
bookSourceList.clear()
|
||||
bookSourceParts.clear()
|
||||
val searchGroup = AppConfig.searchGroup
|
||||
if (searchGroup.isBlank()) {
|
||||
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
|
||||
bookSourceParts.addAll(appDb.bookSourceDao.allEnabledPart)
|
||||
} else {
|
||||
val sources = appDb.bookSourceDao.getEnabledByGroup(searchGroup)
|
||||
val sources = appDb.bookSourceDao.getEnabledPartByGroup(searchGroup)
|
||||
if (sources.isEmpty()) {
|
||||
AppConfig.searchGroup = ""
|
||||
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
|
||||
bookSourceParts.addAll(appDb.bookSourceDao.allEnabledPart)
|
||||
} else {
|
||||
bookSourceList.addAll(sources)
|
||||
bookSourceParts.addAll(sources)
|
||||
}
|
||||
}
|
||||
searchStateData.postValue(true)
|
||||
initSearchPool()
|
||||
for (i in 0 until threadCount) {
|
||||
search()
|
||||
}
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
fun startSearch(origin: String) {
|
||||
execute {
|
||||
stopSearch()
|
||||
bookSourceParts.clear()
|
||||
bookSourceParts.add(appDb.bookSourceDao.getBookSourcePart(origin)!!)
|
||||
searchBooks.removeIf { it.origin == origin }
|
||||
initSearchPool()
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
private fun search() {
|
||||
val searchIndex = synchronized(this) {
|
||||
if (searchIndex >= bookSourceList.lastIndex) {
|
||||
return
|
||||
}
|
||||
++searchIndex
|
||||
task = viewModelScope.launch(searchPool!!) {
|
||||
flow {
|
||||
for (bs in bookSourceParts) {
|
||||
bs.getBookSource()?.let {
|
||||
emit(it)
|
||||
}
|
||||
}
|
||||
}.onStart {
|
||||
searchStateData.postValue(true)
|
||||
}.mapParallelSafe(threadCount) {
|
||||
withTimeout(60000L) {
|
||||
search(it)
|
||||
}
|
||||
}.onCompletion {
|
||||
searchStateData.postValue(false)
|
||||
searchFinishCallback?.invoke(searchBooks.isEmpty())
|
||||
}.catch {
|
||||
AppLog.put("换源搜索出错\n${it.localizedMessage}", it)
|
||||
}.collect()
|
||||
}
|
||||
val source = bookSourceList.getOrNull(searchIndex) ?: return
|
||||
bookSourceList[searchIndex] = emptyBookSource
|
||||
val task = execute(
|
||||
context = searchPool!!,
|
||||
start = CoroutineStart.LAZY,
|
||||
executeContext = searchPool!!
|
||||
) {
|
||||
val resultBooks = WebBook.searchBookAwait(source, name)
|
||||
resultBooks.forEach { searchBook ->
|
||||
if (searchBook.name != name) {
|
||||
return@forEach
|
||||
}
|
||||
if (AppConfig.changeSourceCheckAuthor && !searchBook.author.contains(author)) {
|
||||
return@forEach
|
||||
}
|
||||
when {
|
||||
AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc || AppConfig.changeSourceLoadWordCount -> {
|
||||
loadBookInfo(source, searchBook.toBook())
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
private suspend fun search(source: BookSource) {
|
||||
val resultBooks = WebBook.searchBookAwait(source, name)
|
||||
resultBooks.forEach { searchBook ->
|
||||
if (searchBook.name != name) {
|
||||
return@forEach
|
||||
}
|
||||
if (AppConfig.changeSourceCheckAuthor && !searchBook.author.contains(author)) {
|
||||
return@forEach
|
||||
}
|
||||
when {
|
||||
AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc || AppConfig.changeSourceLoadWordCount -> {
|
||||
loadBookInfo(source, searchBook.toBook())
|
||||
}
|
||||
|
||||
else -> {
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
}
|
||||
}.timeout(60000L)
|
||||
.onError {
|
||||
ensureActive()
|
||||
nextSearch()
|
||||
}
|
||||
.onSuccess {
|
||||
ensureActive()
|
||||
nextSearch()
|
||||
}
|
||||
task.start()
|
||||
tasks.add(task)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadBookInfo(source: BookSource, book: Book) {
|
||||
|
@ -298,24 +299,6 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun nextSearch() {
|
||||
kotlin.runCatching {
|
||||
if (searchIndex < bookSourceList.lastIndex) {
|
||||
search()
|
||||
} else {
|
||||
searchIndex++
|
||||
}
|
||||
if (searchIndex >= bookSourceList.lastIndex + bookSourceList.size
|
||||
|| searchIndex >= bookSourceList.lastIndex + threadCount
|
||||
) {
|
||||
searchStateData.postValue(false)
|
||||
tasks.clear()
|
||||
searchFinishCallback?.invoke(searchBooks.isEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onLoadWordCountChecked(isChecked: Boolean) {
|
||||
if (isChecked) {
|
||||
startRefreshList(true)
|
||||
|
@ -330,54 +313,38 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
stopSearch()
|
||||
searchBookList.clear()
|
||||
if (onlyRefreshNoWordCountBook) {
|
||||
val noWordCountBook = searchBooks.filter { it.chapterWordCountText == null }
|
||||
searchBookList.addAll(noWordCountBook)
|
||||
searchBooks.filterTo(searchBookList) {
|
||||
it.chapterWordCountText == null
|
||||
}
|
||||
searchBooks.removeIf { it.chapterWordCountText == null }
|
||||
} else {
|
||||
searchBookList.addAll(searchBooks)
|
||||
searchBooks.clear()
|
||||
}
|
||||
searchCallback?.upAdapter()
|
||||
searchStateData.postValue(true)
|
||||
initSearchPool()
|
||||
for (i in 0 until threadCount) {
|
||||
refreshList()
|
||||
}
|
||||
refreshList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshList() {
|
||||
synchronized(this) {
|
||||
if (searchIndex >= searchBookList.lastIndex) {
|
||||
return
|
||||
}
|
||||
searchIndex++
|
||||
}
|
||||
val searchBook = searchBookList[searchIndex]
|
||||
val task = execute(context = searchPool!!, executeContext = searchPool!!) {
|
||||
val source = appDb.bookSourceDao.getBookSource(searchBook.origin) ?: return@execute
|
||||
loadBookInfo(source, searchBook.toBook())
|
||||
}.timeout(60000L)
|
||||
.onError {
|
||||
nextRefreshList()
|
||||
}
|
||||
.onSuccess {
|
||||
nextRefreshList()
|
||||
}
|
||||
tasks.add(task)
|
||||
}
|
||||
|
||||
private fun nextRefreshList() {
|
||||
synchronized(this) {
|
||||
if (searchIndex < searchBookList.lastIndex) {
|
||||
refreshList()
|
||||
} else {
|
||||
searchIndex++
|
||||
}
|
||||
if (searchIndex >= searchBookList.lastIndex + min(searchBookList.size, threadCount)) {
|
||||
task = viewModelScope.launch(searchPool!!) {
|
||||
flow {
|
||||
for (searchBook in searchBookList) {
|
||||
emit(searchBook)
|
||||
}
|
||||
}.onStart {
|
||||
searchStateData.postValue(true)
|
||||
}.mapParallelSafe(threadCount) {
|
||||
val source = appDb.bookSourceDao.getBookSource(it.origin)!!
|
||||
withTimeout(60000L) {
|
||||
loadBookInfo(source, it.toBook())
|
||||
}
|
||||
}.onCompletion {
|
||||
searchStateData.postValue(false)
|
||||
tasks.clear()
|
||||
}
|
||||
}.catch {
|
||||
AppLog.put("换源刷新列表出错\n${it.localizedMessage}", it)
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,7 +387,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
}
|
||||
|
||||
fun startOrStopSearch() {
|
||||
if (tasks.isEmpty) {
|
||||
if (task == null || !task!!.isActive) {
|
||||
startSearch()
|
||||
} else {
|
||||
stopSearch()
|
||||
|
@ -428,7 +395,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
|
|||
}
|
||||
|
||||
fun stopSearch() {
|
||||
tasks.clear()
|
||||
task?.cancel()
|
||||
searchPool?.close()
|
||||
searchStateData.postValue(false)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user