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