mirror of
https://github.com/gedoor/legado.git
synced 2025-01-08 11:47:32 +08:00
优化
This commit is contained in:
parent
9ea26b5eab
commit
8b20f3edbe
@ -14,7 +14,6 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.collections.set
|
||||
|
||||
/**
|
||||
* 采用md5作为key可以在分类修改后自动重新计算,不需要手动刷新
|
||||
@ -91,13 +90,3 @@ suspend fun BookSourcePart.clearExploreKindsCache() {
|
||||
exploreKindsMap.remove(exploreKindsKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun BookSource.contains(word: String?): Boolean {
|
||||
if (word.isNullOrEmpty()) {
|
||||
return true
|
||||
}
|
||||
return bookSourceName.contains(word)
|
||||
|| bookSourceUrl.contains(word)
|
||||
|| bookSourceGroup?.contains(word) == true
|
||||
|| bookSourceComment?.contains(word) == true
|
||||
}
|
@ -4,6 +4,7 @@ import io.legado.app.constant.AppLog
|
||||
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.addType
|
||||
@ -352,13 +353,14 @@ object WebBook {
|
||||
*/
|
||||
fun preciseSearch(
|
||||
scope: CoroutineScope,
|
||||
bookSources: List<BookSource>,
|
||||
bookSourceParts: List<BookSourcePart>,
|
||||
name: String,
|
||||
author: String,
|
||||
context: CoroutineContext = Dispatchers.IO,
|
||||
): Coroutine<Pair<Book, BookSource>> {
|
||||
return Coroutine.async(scope, context) {
|
||||
for (source in bookSources) {
|
||||
for (s in bookSourceParts) {
|
||||
val source = s.getBookSource() ?: continue
|
||||
val book = preciseSearchAwait(scope, source, name, author).getOrNull()
|
||||
if (book != null) {
|
||||
return@async Pair(book, source)
|
||||
|
@ -6,30 +6,39 @@ 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
|
||||
import io.legado.app.constant.AppPattern
|
||||
import io.legado.app.data.appDb
|
||||
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.help.config.AppConfig
|
||||
import io.legado.app.help.coroutine.CompositeCoroutine
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
import io.legado.app.utils.mapParallelSafe
|
||||
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.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import java.util.*
|
||||
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.Executors
|
||||
import kotlin.math.min
|
||||
|
||||
class ChangeCoverViewModel(application: Application) : BaseViewModel(application) {
|
||||
private val threadCount = AppConfig.threadCount
|
||||
private var searchPool: ExecutorCoroutineDispatcher? = null
|
||||
private val tasks = CompositeCoroutine()
|
||||
private var searchSuccess: ((SearchBook) -> Unit)? = null
|
||||
private var upAdapter: (() -> Unit)? = null
|
||||
private var bookSourceList = arrayListOf<BookSource>()
|
||||
private var bookSourceParts = arrayListOf<BookSourcePart>()
|
||||
private val defaultCover by lazy {
|
||||
listOf(
|
||||
SearchBook(
|
||||
@ -40,6 +49,7 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
|
||||
)
|
||||
)
|
||||
}
|
||||
private var task: Job? = null
|
||||
val searchStateData = MutableLiveData<Boolean>()
|
||||
var name: String = ""
|
||||
var author: String = ""
|
||||
@ -73,9 +83,6 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
|
||||
}
|
||||
}.flowOn(IO)
|
||||
|
||||
@Volatile
|
||||
private var searchIndex = -1
|
||||
|
||||
fun initData(arguments: Bundle?) {
|
||||
arguments?.let { bundle ->
|
||||
bundle.getString("name")?.let {
|
||||
@ -90,7 +97,6 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
|
||||
private fun initSearchPool() {
|
||||
searchPool = Executors
|
||||
.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()
|
||||
searchIndex = -1
|
||||
}
|
||||
|
||||
private fun startSearch() {
|
||||
@ -98,71 +104,50 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
|
||||
stopSearch()
|
||||
searchBooks.clear()
|
||||
upAdapter?.invoke()
|
||||
bookSourceList.clear()
|
||||
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
|
||||
searchStateData.postValue(true)
|
||||
bookSourceParts.clear()
|
||||
bookSourceParts.addAll(appDb.bookSourceDao.allEnabledPart)
|
||||
initSearchPool()
|
||||
for (i in 0 until threadCount) {
|
||||
search()
|
||||
}
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun search() {
|
||||
if (searchIndex >= bookSourceList.lastIndex) {
|
||||
return
|
||||
}
|
||||
searchIndex++
|
||||
val source = bookSourceList[searchIndex]
|
||||
if (source.getSearchRule().coverUrl.isNullOrBlank()) {
|
||||
searchNext()
|
||||
return
|
||||
}
|
||||
val task = WebBook
|
||||
.searchBook(
|
||||
viewModelScope,
|
||||
source,
|
||||
name,
|
||||
context = searchPool!!,
|
||||
executeContext = searchPool!!
|
||||
)
|
||||
.timeout(60000L)
|
||||
.onSuccess(IO) {
|
||||
it.firstOrNull()?.let { searchBook ->
|
||||
if (searchBook.name == name && searchBook.author == author
|
||||
&& !searchBook.coverUrl.isNullOrEmpty()
|
||||
) {
|
||||
appDb.searchBookDao.insert(searchBook)
|
||||
searchSuccess?.invoke(searchBook)
|
||||
task = viewModelScope.launch(searchPool!!) {
|
||||
flow {
|
||||
for (bs in bookSourceParts) {
|
||||
bs.getBookSource()?.let {
|
||||
emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFinally {
|
||||
searchNext()
|
||||
}
|
||||
tasks.add(task)
|
||||
}.onStart {
|
||||
searchStateData.postValue(true)
|
||||
}.mapParallelSafe(threadCount) {
|
||||
withTimeout(60000L) {
|
||||
search(it)
|
||||
}
|
||||
}.onCompletion {
|
||||
searchStateData.postValue(false)
|
||||
}.catch {
|
||||
AppLog.put("封面换源搜索出错\n${it.localizedMessage}", it)
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun searchNext() {
|
||||
if (searchIndex < bookSourceList.lastIndex) {
|
||||
search()
|
||||
} else {
|
||||
searchIndex++
|
||||
private suspend fun search(source: BookSource) {
|
||||
if (source.getSearchRule().coverUrl.isNullOrBlank()) {
|
||||
return
|
||||
}
|
||||
if (searchIndex >= bookSourceList.lastIndex + min(
|
||||
bookSourceList.size,
|
||||
threadCount
|
||||
)
|
||||
val searchBook = WebBook.searchBookAwait(source, name).firstOrNull() ?: return
|
||||
if (searchBook.name == name && searchBook.author == author
|
||||
&& !searchBook.coverUrl.isNullOrEmpty()
|
||||
) {
|
||||
searchStateData.postValue(false)
|
||||
tasks.clear()
|
||||
appDb.searchBookDao.insert(searchBook)
|
||||
searchSuccess?.invoke(searchBook)
|
||||
}
|
||||
}
|
||||
|
||||
fun startOrStopSearch() {
|
||||
if (tasks.isEmpty) {
|
||||
if (task == null || !task!!.isActive) {
|
||||
startSearch()
|
||||
} else {
|
||||
stopSearch()
|
||||
@ -170,7 +155,7 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
|
||||
}
|
||||
|
||||
private fun stopSearch() {
|
||||
tasks.clear()
|
||||
task?.cancel()
|
||||
searchPool?.close()
|
||||
searchStateData.postValue(false)
|
||||
}
|
||||
|
@ -10,27 +10,34 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseDialogFragment
|
||||
import io.legado.app.base.adapter.ItemViewHolder
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.data.AppDatabase
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.data.entities.BookSourcePart
|
||||
import io.legado.app.databinding.DialogSearchScopeBinding
|
||||
import io.legado.app.databinding.ItemCheckBoxBinding
|
||||
import io.legado.app.databinding.ItemRadioButtonBinding
|
||||
import io.legado.app.help.source.contains
|
||||
import io.legado.app.lib.theme.primaryColor
|
||||
import io.legado.app.utils.applyTint
|
||||
import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
|
||||
import io.legado.app.utils.setLayout
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class SearchScopeDialog : BaseDialogFragment(R.layout.dialog_search_scope) {
|
||||
|
||||
private val binding by viewBinding(DialogSearchScopeBinding::bind)
|
||||
private var sourceFlowJob: Job? = null
|
||||
val callback: Callback get() = parentFragment as? Callback ?: activity as Callback
|
||||
var groups: List<String> = emptyList()
|
||||
var sources: List<BookSource> = emptyList()
|
||||
val screenSources = arrayListOf<BookSource>()
|
||||
val screenSources = arrayListOf<BookSourcePart>()
|
||||
var screenText: String? = null
|
||||
|
||||
val adapter by lazy {
|
||||
@ -49,7 +56,6 @@ class SearchScopeDialog : BaseDialogFragment(R.layout.dialog_search_scope) {
|
||||
initSearchView()
|
||||
initOtherView()
|
||||
initData()
|
||||
upData()
|
||||
}
|
||||
|
||||
private fun initMenu() {
|
||||
@ -105,34 +111,49 @@ class SearchScopeDialog : BaseDialogFragment(R.layout.dialog_search_scope) {
|
||||
groups = withContext(IO) {
|
||||
appDb.bookSourceDao.allEnabledGroups()
|
||||
}
|
||||
sources = withContext(IO) {
|
||||
appDb.bookSourceDao.allEnabled
|
||||
}
|
||||
upData()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun upData() {
|
||||
lifecycleScope.launch {
|
||||
withContext(IO) {
|
||||
if (binding.rbSource.isChecked) {
|
||||
sources.filter { source ->
|
||||
source.contains(screenText)
|
||||
}.let {
|
||||
screenSources.clear()
|
||||
screenSources.addAll(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (binding.rbSource.isChecked) {
|
||||
upBookSource(screenText)
|
||||
} else {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun upBookSource(searchKey: String? = null) {
|
||||
sourceFlowJob?.cancel()
|
||||
sourceFlowJob = lifecycleScope.launch {
|
||||
when {
|
||||
searchKey.isNullOrEmpty() -> {
|
||||
appDb.bookSourceDao.flowAll()
|
||||
}
|
||||
|
||||
else -> {
|
||||
appDb.bookSourceDao.flowSearch(searchKey)
|
||||
}
|
||||
}.flowWithLifecycleAndDatabaseChange(
|
||||
lifecycle,
|
||||
table = AppDatabase.BOOK_SOURCE_TABLE_NAME
|
||||
).catch {
|
||||
AppLog.put("多分组/书源界面更新书源出错", it)
|
||||
}.flowOn(IO).conflate().collect { data ->
|
||||
screenSources.clear()
|
||||
screenSources.addAll(data)
|
||||
adapter.notifyDataSetChanged()
|
||||
delay(500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class RecyclerAdapter : RecyclerView.Adapter<ItemViewHolder>() {
|
||||
|
||||
val selectGroups = arrayListOf<String>()
|
||||
var selectSource: BookSource? = null
|
||||
var selectSource: BookSourcePart? = null
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (binding.rbSource.isChecked) {
|
||||
@ -165,6 +186,7 @@ class SearchScopeDialog : BaseDialogFragment(R.layout.dialog_search_scope) {
|
||||
holder.binding.checkBox.text = it
|
||||
}
|
||||
}
|
||||
|
||||
is ItemRadioButtonBinding -> {
|
||||
screenSources.getOrNull(position)?.let {
|
||||
holder.binding.radioButton.isChecked = selectSource == it
|
||||
@ -195,6 +217,7 @@ class SearchScopeDialog : BaseDialogFragment(R.layout.dialog_search_scope) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is ItemRadioButtonBinding -> {
|
||||
screenSources.getOrNull(position)?.let {
|
||||
holder.binding.radioButton.isChecked = selectSource == it
|
||||
|
@ -27,11 +27,6 @@ import kotlinx.coroutines.isActive
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.Map
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.hashMapOf
|
||||
import kotlin.collections.set
|
||||
|
||||
class BookshelfViewModel(application: Application) : BaseViewModel(application) {
|
||||
val addBookProgressLiveData = MutableLiveData(-1)
|
||||
@ -156,13 +151,13 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
|
||||
|
||||
private fun importBookshelfByJson(json: String, groupId: Long) {
|
||||
execute {
|
||||
val bookSources = appDb.bookSourceDao.allEnabled
|
||||
val bookSourceParts = appDb.bookSourceDao.allEnabledPart
|
||||
GSON.fromJsonArray<Map<String, String?>>(json).getOrThrow().forEach { bookInfo ->
|
||||
if (!isActive) return@execute
|
||||
val name = bookInfo["name"] ?: ""
|
||||
val author = bookInfo["author"] ?: ""
|
||||
if (name.isNotEmpty() && appDb.bookDao.getBook(name, author) == null) {
|
||||
WebBook.preciseSearch(this, bookSources, name, author)
|
||||
WebBook.preciseSearch(this, bookSourceParts, name, author)
|
||||
.onSuccess {
|
||||
val book = it.first
|
||||
if (groupId > 0) {
|
||||
|
Loading…
Reference in New Issue
Block a user