pull/1303/head
gedoor 3 years ago
parent c4ddc2b642
commit da907755c4
  1. 118
      app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt
  2. 200
      app/src/main/java/io/legado/app/model/webBook/SearchModel.kt
  3. 113
      app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt

@ -1,118 +0,0 @@
package io.legado.app.model.webBook
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import splitties.init.appCtx
import java.util.concurrent.Executors
import kotlin.math.min
class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) {
val threadCount = AppConfig.threadCount
private var searchPool: ExecutorCoroutineDispatcher? = null
private var mSearchId = 0L
private var searchPage = 1
private var searchKey: String = ""
private var tasks = CompositeCoroutine()
private var bookSourceList = arrayListOf<BookSource>()
@Volatile
private var searchIndex = -1
private fun initSearchPool() {
searchPool = Executors.newFixedThreadPool(min(threadCount,8)).asCoroutineDispatcher()
}
fun search(searchId: Long, key: String) {
callBack.onSearchStart()
if (searchId != mSearchId) {
if (key.isEmpty()) {
callBack.onSearchCancel()
return
} else {
this.searchKey = key
}
if (mSearchId != 0L) {
close()
}
initSearchPool()
mSearchId = searchId
searchPage = 1
val searchGroup = appCtx.getPrefString("searchGroup") ?: ""
bookSourceList.clear()
if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else {
bookSourceList.addAll(appDb.bookSourceDao.getEnabledByGroup(searchGroup))
}
} else {
searchPage++
}
searchIndex = -1
for (i in 0 until threadCount) {
search(searchId)
}
}
private fun search(searchId: Long) {
synchronized(this) {
if (searchIndex >= bookSourceList.lastIndex) {
return
}
searchIndex++
val source = bookSourceList[searchIndex]
val task = WebBook.searchBook(
scope,
source,
searchKey,
searchPage,
context = searchPool!!
).timeout(30000L)
.onSuccess(searchPool) {
if (searchId == mSearchId) {
callBack.onSearchSuccess(it)
}
}
.onFinally(searchPool) {
synchronized(this) {
if (searchIndex < bookSourceList.lastIndex) {
search(searchId)
} else {
searchIndex++
}
if (searchIndex >= bookSourceList.lastIndex
+ min(bookSourceList.size, threadCount)
) {
callBack.onSearchFinish()
}
}
}
tasks.add(task)
}
}
fun cancelSearch() {
close()
callBack.onSearchCancel()
}
fun close() {
tasks.clear()
searchPool?.close()
mSearchId = 0L
}
interface CallBack {
fun onSearchStart()
fun onSearchSuccess(searchBooks: ArrayList<SearchBook>)
fun onSearchFinish()
fun onSearchCancel()
}
}

@ -0,0 +1,200 @@
package io.legado.app.model.webBook
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.isActive
import splitties.init.appCtx
import java.util.concurrent.Executors
import kotlin.math.min
class SearchModel(private val scope: CoroutineScope, private val callBack: CallBack) {
val threadCount = AppConfig.threadCount
private var searchPool: ExecutorCoroutineDispatcher? = null
private var mSearchId = 0L
private var searchPage = 1
private var searchKey: String = ""
private var tasks = CompositeCoroutine()
private var bookSourceList = arrayListOf<BookSource>()
private var searchBooks = arrayListOf<SearchBook>()
@Volatile
private var searchIndex = -1
private fun initSearchPool() {
searchPool?.close()
searchPool = Executors.newFixedThreadPool(min(threadCount, 8)).asCoroutineDispatcher()
}
fun search(searchId: Long, key: String) {
callBack.onSearchStart()
if (searchId != mSearchId) {
if (key.isEmpty()) {
callBack.onSearchCancel()
return
} else {
this.searchKey = key
}
if (mSearchId != 0L) {
close()
}
initSearchPool()
mSearchId = searchId
searchPage = 1
val searchGroup = appCtx.getPrefString("searchGroup") ?: ""
bookSourceList.clear()
if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
} else {
bookSourceList.addAll(appDb.bookSourceDao.getEnabledByGroup(searchGroup))
}
} else {
searchPage++
}
searchIndex = -1
for (i in 0 until threadCount) {
search(searchId)
}
}
@Synchronized
private fun search(searchId: Long) {
if (searchIndex >= bookSourceList.lastIndex) {
return
}
searchIndex++
val source = bookSourceList[searchIndex]
val task = WebBook.searchBook(
scope,
source,
searchKey,
searchPage,
context = searchPool!!
).timeout(30000L)
.onSuccess(searchPool) {
onSuccess(searchId, it)
}
.onFinally(searchPool) {
onFinally(searchId)
}
tasks.add(task)
}
@Synchronized
private fun onSuccess(searchId: Long, searchBooks: ArrayList<SearchBook>) {
if (searchId == mSearchId) {
val precision = appCtx.getPrefBoolean(PreferKey.precisionSearch)
appDb.searchBookDao.insert(*searchBooks.toTypedArray())
mergeItems(scope, searchBooks, precision)
callBack.onSearchSuccess(searchBooks)
}
}
@Synchronized
private fun onFinally(searchId: Long) {
if (searchIndex < bookSourceList.lastIndex) {
search(searchId)
} else {
searchIndex++
}
if (searchIndex >= bookSourceList.lastIndex
+ min(bookSourceList.size, threadCount)
) {
callBack.onSearchFinish()
}
}
private fun mergeItems(scope: CoroutineScope, newDataS: List<SearchBook>, precision: Boolean) {
if (newDataS.isNotEmpty()) {
val copyData = ArrayList(searchBooks)
val equalData = arrayListOf<SearchBook>()
val containsData = arrayListOf<SearchBook>()
val otherData = arrayListOf<SearchBook>()
copyData.forEach {
if (!scope.isActive) return
if (it.name == searchKey || it.author == searchKey) {
equalData.add(it)
} else if (it.name.contains(searchKey) || it.author.contains(searchKey)) {
containsData.add(it)
} else {
otherData.add(it)
}
}
newDataS.forEach { nBook ->
if (!scope.isActive) return
if (nBook.name == searchKey || nBook.author == searchKey) {
var hasSame = false
equalData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
equalData.add(nBook)
}
} else if (nBook.name.contains(searchKey) || nBook.author.contains(searchKey)) {
var hasSame = false
containsData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
containsData.add(nBook)
}
} else if (!precision) {
var hasSame = false
otherData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
otherData.add(nBook)
}
}
}
if (!scope.isActive) return
equalData.sortByDescending { it.origins.size }
equalData.addAll(containsData.sortedByDescending { it.origins.size })
if (!precision) {
equalData.addAll(otherData)
}
searchBooks = equalData
}
}
fun cancelSearch() {
close()
callBack.onSearchCancel()
}
fun close() {
tasks.clear()
searchPool?.close()
searchPool = null
mSearchId = 0L
}
interface CallBack {
fun onSearchStart()
fun onSearchSuccess(searchBooks: ArrayList<SearchBook>)
fun onSearchFinish()
fun onSearchCancel()
}
}

@ -4,17 +4,16 @@ import android.app.Application
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.SearchKeyword
import io.legado.app.model.webBook.SearchBookModel
import io.legado.app.utils.getPrefBoolean
import kotlinx.coroutines.*
import io.legado.app.model.webBook.SearchModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class SearchViewModel(application: Application) : BaseViewModel(application),
SearchBookModel.CallBack {
private val searchBookModel = SearchBookModel(viewModelScope, this)
class SearchViewModel(application: Application) : BaseViewModel(application), SearchModel.CallBack {
private val searchModel = SearchModel(viewModelScope, this)
private var upAdapterJob: Job? = null
var isSearchLiveData = MutableLiveData<Boolean>()
var searchBookLiveData = MutableLiveData<List<SearchBook>>()
@ -29,7 +28,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
*/
fun search(key: String) {
if ((searchKey == key) || key.isNotEmpty()) {
searchBookModel.cancelSearch()
searchModel.cancelSearch()
searchBooks.clear()
searchBookLiveData.postValue(searchBooks)
searchID = System.currentTimeMillis()
@ -38,19 +37,18 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
if (searchKey.isEmpty()) {
return
}
searchBookModel.search(searchID, searchKey)
searchModel.search(searchID, searchKey)
}
@Synchronized
private fun upAdapter() {
if (System.currentTimeMillis() >= postTime + 500) {
upAdapterJob?.cancel()
upAdapterJob?.cancel()
if (System.currentTimeMillis() >= postTime + 1000) {
postTime = System.currentTimeMillis()
searchBookLiveData.postValue(searchBooks)
} else {
upAdapterJob?.cancel()
upAdapterJob = viewModelScope.launch {
delay(500)
delay(1000)
upAdapter()
}
}
@ -62,9 +60,8 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
}
override fun onSearchSuccess(searchBooks: ArrayList<SearchBook>) {
val precision = context.getPrefBoolean(PreferKey.precisionSearch)
appDb.searchBookDao.insert(*searchBooks.toTypedArray())
mergeItems(viewModelScope, searchBooks, precision)
this.searchBooks = searchBooks
upAdapter()
}
override fun onSearchFinish() {
@ -77,92 +74,12 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
isLoading = false
}
/**
* 合并搜索结果并排序
*/
@Synchronized
private fun mergeItems(scope: CoroutineScope, newDataS: List<SearchBook>, precision: Boolean) {
if (newDataS.isNotEmpty()) {
val copyData = ArrayList(searchBooks)
val equalData = arrayListOf<SearchBook>()
val containsData = arrayListOf<SearchBook>()
val otherData = arrayListOf<SearchBook>()
copyData.forEach {
if (!scope.isActive) return
if (it.name == searchKey || it.author == searchKey) {
equalData.add(it)
} else if (it.name.contains(searchKey) || it.author.contains(searchKey)) {
containsData.add(it)
} else {
otherData.add(it)
}
}
newDataS.forEach { nBook ->
if (!scope.isActive) return
if (nBook.name == searchKey || nBook.author == searchKey) {
var hasSame = false
equalData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
equalData.add(nBook)
}
} else if (nBook.name.contains(searchKey) || nBook.author.contains(searchKey)) {
var hasSame = false
containsData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
containsData.add(nBook)
}
} else if (!precision) {
var hasSame = false
otherData.forEach { pBook ->
if (!scope.isActive) return
if (pBook.name == nBook.name && pBook.author == nBook.author) {
pBook.addOrigin(nBook.origin)
hasSame = true
}
}
if (!hasSame) {
otherData.add(nBook)
}
}
}
if (!scope.isActive) return
equalData.sortByDescending { it.origins.size }
equalData.addAll(containsData.sortedByDescending { it.origins.size })
if (!precision) {
equalData.addAll(otherData)
}
searchBooks = equalData
upAdapter()
}
}
/**
* 停止搜索
*/
fun stop() {
searchBookModel.cancelSearch()
}
/**
* 按书名和作者获取书源排序最前的搜索结果
*/
fun getSearchBook(name: String, author: String, success: ((searchBook: SearchBook?) -> Unit)?) {
execute {
val searchBook = appDb.searchBookDao.getFirstByNameAuthor(name, author)
success?.invoke(searchBook)
}
searchModel.cancelSearch()
}
/**
@ -192,7 +109,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
override fun onCleared() {
super.onCleared()
searchBookModel.close()
searchModel.close()
}
}

Loading…
Cancel
Save