优化
This commit is contained in:
@@ -190,6 +190,19 @@ interface BookSourceDao {
|
|||||||
)
|
)
|
||||||
fun getEnabledByGroup(group: String): List<BookSource>
|
fun getEnabledByGroup(group: String): List<BookSource>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||||
|
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||||
|
from book_sources
|
||||||
|
where enabled = 1
|
||||||
|
and (bookSourceGroup = :group
|
||||||
|
or bookSourceGroup like :group || ',%'
|
||||||
|
or bookSourceGroup like '%,' || :group
|
||||||
|
or bookSourceGroup like '%,' || :group || ',%')
|
||||||
|
order by customOrder asc"""
|
||||||
|
)
|
||||||
|
fun getEnabledPartByGroup(group: String): List<BookSourcePart>
|
||||||
|
|
||||||
@Query("select * from book_sources where bookUrlPattern != 'NONE' and bookSourceType = :type order by customOrder asc")
|
@Query("select * from book_sources where bookUrlPattern != 'NONE' and bookSourceType = :type order by customOrder asc")
|
||||||
fun getEnabledByType(type: Int): List<BookSource>
|
fun getEnabledByType(type: Int): List<BookSource>
|
||||||
|
|
||||||
@@ -215,6 +228,13 @@ interface BookSourceDao {
|
|||||||
@get:Query("select * from book_sources where enabled = 1 order by customOrder")
|
@get:Query("select * from book_sources where enabled = 1 order by customOrder")
|
||||||
val allEnabled: List<BookSource>
|
val allEnabled: List<BookSource>
|
||||||
|
|
||||||
|
@get:Query(
|
||||||
|
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||||
|
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||||
|
from book_sources where enabled = 1 order by customOrder asc"""
|
||||||
|
)
|
||||||
|
val allEnabledPart: List<BookSourcePart>
|
||||||
|
|
||||||
@get:Query("select * from book_sources where enabled = 0 order by customOrder")
|
@get:Query("select * from book_sources where enabled = 0 order by customOrder")
|
||||||
val allDisabled: List<BookSource>
|
val allDisabled: List<BookSource>
|
||||||
|
|
||||||
@@ -242,6 +262,13 @@ interface BookSourceDao {
|
|||||||
@Query("select * from book_sources where bookSourceUrl = :key")
|
@Query("select * from book_sources where bookSourceUrl = :key")
|
||||||
fun getBookSource(key: String): BookSource?
|
fun getBookSource(key: String): BookSource?
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
|
||||||
|
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
|
||||||
|
from book_sources where bookSourceUrl = :key"""
|
||||||
|
)
|
||||||
|
fun getBookSourcePart(key: String): BookSourcePart?
|
||||||
|
|
||||||
@Query("select count(*) from book_sources")
|
@Query("select count(*) from book_sources")
|
||||||
fun allCount(): Int
|
fun allCount(): Int
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
package io.legado.app.model.webBook
|
package io.legado.app.model.webBook
|
||||||
|
|
||||||
import io.legado.app.constant.AppConst
|
import io.legado.app.constant.AppConst
|
||||||
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.constant.PreferKey
|
import io.legado.app.constant.PreferKey
|
||||||
import io.legado.app.data.appDb
|
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.data.entities.SearchBook
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.coroutine.CompositeCoroutine
|
|
||||||
import io.legado.app.ui.book.search.SearchScope
|
import io.legado.app.ui.book.search.SearchScope
|
||||||
import io.legado.app.utils.getPrefBoolean
|
import io.legado.app.utils.getPrefBoolean
|
||||||
|
import io.legado.app.utils.mapNotNullParallel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CoroutineStart
|
|
||||||
import kotlinx.coroutines.ExecutorCoroutineDispatcher
|
import kotlinx.coroutines.ExecutorCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class SearchModel(private val scope: CoroutineScope, private val callBack: CallBack) {
|
class SearchModel(private val scope: CoroutineScope, private val callBack: CallBack) {
|
||||||
@@ -26,12 +37,10 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
private var mSearchId = 0L
|
private var mSearchId = 0L
|
||||||
private var searchPage = 1
|
private var searchPage = 1
|
||||||
private var searchKey: String = ""
|
private var searchKey: String = ""
|
||||||
private var tasks = CompositeCoroutine()
|
private var bookSourceParts = emptyList<BookSourcePart>()
|
||||||
private var bookSourceList = arrayListOf<BookSource>()
|
|
||||||
private var searchBooks = arrayListOf<SearchBook>()
|
private var searchBooks = arrayListOf<SearchBook>()
|
||||||
|
private var searchJob: Job? = null
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var searchIndex = -1
|
|
||||||
|
|
||||||
private fun initSearchPool() {
|
private fun initSearchPool() {
|
||||||
searchPool?.close()
|
searchPool?.close()
|
||||||
@@ -40,97 +49,70 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun search(searchId: Long, key: String) {
|
fun search(searchId: Long, key: String) {
|
||||||
callBack.onSearchStart()
|
|
||||||
if (searchId != mSearchId) {
|
if (searchId != mSearchId) {
|
||||||
if (key.isEmpty()) {
|
if (key.isEmpty()) {
|
||||||
callBack.onSearchCancel()
|
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
this.searchKey = key
|
|
||||||
}
|
}
|
||||||
|
searchKey = key
|
||||||
if (mSearchId != 0L) {
|
if (mSearchId != 0L) {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
initSearchPool()
|
|
||||||
mSearchId = searchId
|
|
||||||
searchPage = 1
|
|
||||||
bookSourceList.clear()
|
|
||||||
searchBooks.clear()
|
searchBooks.clear()
|
||||||
callBack.onSearchSuccess(searchBooks)
|
bookSourceParts = callBack.getSearchScope().getBookSourceParts()
|
||||||
bookSourceList.addAll(callBack.getSearchScope().getBookSources())
|
if (bookSourceParts.isEmpty()) {
|
||||||
if (bookSourceList.isEmpty()) {
|
|
||||||
callBack.onSearchCancel(NoStackTraceException("启用书源为空"))
|
callBack.onSearchCancel(NoStackTraceException("启用书源为空"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
mSearchId = searchId
|
||||||
|
searchPage = 1
|
||||||
|
initSearchPool()
|
||||||
} else {
|
} else {
|
||||||
searchPage++
|
searchPage++
|
||||||
}
|
}
|
||||||
searchIndex = -1
|
startSearch()
|
||||||
for (i in 0 until threadCount) {
|
}
|
||||||
search(searchId)
|
|
||||||
|
private fun startSearch() {
|
||||||
|
val precision = appCtx.getPrefBoolean(PreferKey.precisionSearch)
|
||||||
|
searchJob = scope.launch(searchPool!!) {
|
||||||
|
flow {
|
||||||
|
for (bs in bookSourceParts) {
|
||||||
|
bs.getBookSource()?.let {
|
||||||
|
emit(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onStart {
|
||||||
|
callBack.onSearchStart()
|
||||||
|
}.mapNotNullParallel(threadCount) {
|
||||||
|
try {
|
||||||
|
withTimeout(30000L) {
|
||||||
|
WebBook.searchBookAwait(it, searchKey, searchPage)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
currentCoroutineContext().ensureActive()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.buffer(0).onEach { items ->
|
||||||
|
appDb.searchBookDao.insert(*items.toTypedArray())
|
||||||
|
mergeItems(items, precision)
|
||||||
|
currentCoroutineContext().ensureActive()
|
||||||
|
callBack.onSearchSuccess(searchBooks)
|
||||||
|
}.onCompletion {
|
||||||
|
if (it == null) callBack.onSearchFinish(searchBooks.isEmpty())
|
||||||
|
}.catch {
|
||||||
|
AppLog.put("书源搜索出错\n${it.localizedMessage}", it)
|
||||||
|
}.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
private suspend fun mergeItems(newDataS: List<SearchBook>, precision: Boolean) {
|
||||||
private fun search(searchId: Long) {
|
|
||||||
if (searchIndex >= bookSourceList.lastIndex) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
searchIndex++
|
|
||||||
val source = bookSourceList.getOrNull(searchIndex) ?: return
|
|
||||||
val searchPool = searchPool ?: return
|
|
||||||
val task = WebBook.searchBook(
|
|
||||||
scope,
|
|
||||||
source,
|
|
||||||
searchKey,
|
|
||||||
searchPage,
|
|
||||||
context = searchPool,
|
|
||||||
start = CoroutineStart.LAZY,
|
|
||||||
executeContext = searchPool
|
|
||||||
).timeout(30000L)
|
|
||||||
.onSuccess {
|
|
||||||
ensureActive()
|
|
||||||
onSuccess(searchId, it)
|
|
||||||
}
|
|
||||||
.onFinally {
|
|
||||||
onFinally(searchId)
|
|
||||||
}
|
|
||||||
task.start()
|
|
||||||
tasks.add(task)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun onSuccess(searchId: Long, items: ArrayList<SearchBook>) {
|
|
||||||
if (searchId == mSearchId) {
|
|
||||||
appDb.searchBookDao.insert(*items.toTypedArray())
|
|
||||||
val precision = appCtx.getPrefBoolean(PreferKey.precisionSearch)
|
|
||||||
mergeItems(scope, items, 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(searchBooks.isEmpty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mergeItems(scope: CoroutineScope, newDataS: List<SearchBook>, precision: Boolean) {
|
|
||||||
if (newDataS.isNotEmpty()) {
|
if (newDataS.isNotEmpty()) {
|
||||||
val copyData = ArrayList(searchBooks)
|
val copyData = ArrayList(searchBooks)
|
||||||
val equalData = arrayListOf<SearchBook>()
|
val equalData = arrayListOf<SearchBook>()
|
||||||
val containsData = arrayListOf<SearchBook>()
|
val containsData = arrayListOf<SearchBook>()
|
||||||
val otherData = arrayListOf<SearchBook>()
|
val otherData = arrayListOf<SearchBook>()
|
||||||
copyData.forEach {
|
copyData.forEach {
|
||||||
if (!scope.isActive) return
|
coroutineContext.ensureActive()
|
||||||
if (it.name == searchKey || it.author == searchKey) {
|
if (it.name == searchKey || it.author == searchKey) {
|
||||||
equalData.add(it)
|
equalData.add(it)
|
||||||
} else if (it.name.contains(searchKey) || it.author.contains(searchKey)) {
|
} else if (it.name.contains(searchKey) || it.author.contains(searchKey)) {
|
||||||
@@ -140,11 +122,11 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
newDataS.forEach { nBook ->
|
newDataS.forEach { nBook ->
|
||||||
if (!scope.isActive) return
|
coroutineContext.ensureActive()
|
||||||
if (nBook.name == searchKey || nBook.author == searchKey) {
|
if (nBook.name == searchKey || nBook.author == searchKey) {
|
||||||
var hasSame = false
|
var hasSame = false
|
||||||
equalData.forEach { pBook ->
|
equalData.forEach { pBook ->
|
||||||
if (!scope.isActive) return
|
coroutineContext.ensureActive()
|
||||||
if (pBook.name == nBook.name && pBook.author == nBook.author) {
|
if (pBook.name == nBook.name && pBook.author == nBook.author) {
|
||||||
pBook.addOrigin(nBook.origin)
|
pBook.addOrigin(nBook.origin)
|
||||||
hasSame = true
|
hasSame = true
|
||||||
@@ -156,7 +138,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
} else if (nBook.name.contains(searchKey) || nBook.author.contains(searchKey)) {
|
} else if (nBook.name.contains(searchKey) || nBook.author.contains(searchKey)) {
|
||||||
var hasSame = false
|
var hasSame = false
|
||||||
containsData.forEach { pBook ->
|
containsData.forEach { pBook ->
|
||||||
if (!scope.isActive) return
|
coroutineContext.ensureActive()
|
||||||
if (pBook.name == nBook.name && pBook.author == nBook.author) {
|
if (pBook.name == nBook.name && pBook.author == nBook.author) {
|
||||||
pBook.addOrigin(nBook.origin)
|
pBook.addOrigin(nBook.origin)
|
||||||
hasSame = true
|
hasSame = true
|
||||||
@@ -168,7 +150,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
} else if (!precision) {
|
} else if (!precision) {
|
||||||
var hasSame = false
|
var hasSame = false
|
||||||
otherData.forEach { pBook ->
|
otherData.forEach { pBook ->
|
||||||
if (!scope.isActive) return
|
coroutineContext.ensureActive()
|
||||||
if (pBook.name == nBook.name && pBook.author == nBook.author) {
|
if (pBook.name == nBook.name && pBook.author == nBook.author) {
|
||||||
pBook.addOrigin(nBook.origin)
|
pBook.addOrigin(nBook.origin)
|
||||||
hasSame = true
|
hasSame = true
|
||||||
@@ -179,13 +161,13 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!scope.isActive) return
|
coroutineContext.ensureActive()
|
||||||
equalData.sortByDescending { it.origins.size }
|
equalData.sortByDescending { it.origins.size }
|
||||||
equalData.addAll(containsData.sortedByDescending { it.origins.size })
|
equalData.addAll(containsData.sortedByDescending { it.origins.size })
|
||||||
if (!precision) {
|
if (!precision) {
|
||||||
equalData.addAll(otherData)
|
equalData.addAll(otherData)
|
||||||
}
|
}
|
||||||
if (!scope.isActive) return
|
coroutineContext.ensureActive()
|
||||||
searchBooks = equalData
|
searchBooks = equalData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +178,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun close() {
|
fun close() {
|
||||||
tasks.clear()
|
searchJob?.cancel()
|
||||||
searchPool?.close()
|
searchPool?.close()
|
||||||
searchPool = null
|
searchPool = null
|
||||||
mSearchId = 0L
|
mSearchId = 0L
|
||||||
@@ -205,9 +187,9 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
|
|||||||
interface CallBack {
|
interface CallBack {
|
||||||
fun getSearchScope(): SearchScope
|
fun getSearchScope(): SearchScope
|
||||||
fun onSearchStart()
|
fun onSearchStart()
|
||||||
fun onSearchSuccess(searchBooks: ArrayList<SearchBook>)
|
fun onSearchSuccess(searchBooks: List<SearchBook>)
|
||||||
fun onSearchFinish(isEmpty: Boolean)
|
fun onSearchFinish(isEmpty: Boolean)
|
||||||
fun onSearchCancel(exception: Exception? = null)
|
fun onSearchCancel(exception: Throwable? = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,10 @@ import kotlinx.coroutines.asCoroutineDispatcher
|
|||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.flow.buffer
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.mozilla.javascript.WrappedException
|
import org.mozilla.javascript.WrappedException
|
||||||
@@ -112,9 +114,7 @@ class CheckSourceService : BaseService() {
|
|||||||
upNotification()
|
upNotification()
|
||||||
}.onEachParallel(threadCount) {
|
}.onEachParallel(threadCount) {
|
||||||
checkSource(it)
|
checkSource(it)
|
||||||
}.onCompletion {
|
}.buffer(0).onEach {
|
||||||
stopSelf()
|
|
||||||
}.buffer(0).collect {
|
|
||||||
finishCount++
|
finishCount++
|
||||||
notificationMsg = getString(
|
notificationMsg = getString(
|
||||||
R.string.progress_show,
|
R.string.progress_show,
|
||||||
@@ -124,7 +124,9 @@ class CheckSourceService : BaseService() {
|
|||||||
)
|
)
|
||||||
upNotification()
|
upNotification()
|
||||||
appDb.bookSourceDao.update(it)
|
appDb.bookSourceDao.update(it)
|
||||||
}
|
}.onCompletion {
|
||||||
|
stopSelf()
|
||||||
|
}.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import kotlinx.coroutines.async
|
|||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.flow.buffer
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import kotlinx.coroutines.flow.collectIndexed
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -338,8 +339,7 @@ class ExportBookService : BaseService() {
|
|||||||
}.onEach { it.start() }
|
}.onEach { it.start() }
|
||||||
.buffer(threads)
|
.buffer(threads)
|
||||||
.map { it.await() }
|
.map { it.await() }
|
||||||
.withIndex()
|
.collectIndexed { index, result ->
|
||||||
.collect { (index, result) ->
|
|
||||||
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
|
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
|
||||||
exportProgress[book.bookUrl] = index
|
exportProgress[book.bookUrl] = index
|
||||||
append.invoke(result.first, result.second)
|
append.invoke(result.first, result.second)
|
||||||
@@ -685,8 +685,7 @@ class ExportBookService : BaseService() {
|
|||||||
}.onEach { it.start() }
|
}.onEach { it.start() }
|
||||||
.buffer(threads)
|
.buffer(threads)
|
||||||
.map { it.await() }
|
.map { it.await() }
|
||||||
.withIndex()
|
.collectIndexed { index, exportChapter ->
|
||||||
.collect { (index, exportChapter) ->
|
|
||||||
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
|
postEvent(EventBus.EXPORT_BOOK, book.bookUrl)
|
||||||
exportProgress[book.bookUrl] = index
|
exportProgress[book.bookUrl] = index
|
||||||
epubBook.resources.addAll(exportChapter.resources)
|
epubBook.resources.addAll(exportChapter.resources)
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
|
|||||||
}
|
}
|
||||||
if (viewModel.isSearchLiveData.value == false
|
if (viewModel.isSearchLiveData.value == false
|
||||||
&& viewModel.searchKey.isNotEmpty()
|
&& viewModel.searchKey.isNotEmpty()
|
||||||
|
&& viewModel.searchBookLiveData.value?.isNotEmpty() == true
|
||||||
) {
|
) {
|
||||||
viewModel.search("")
|
viewModel.search("")
|
||||||
}
|
}
|
||||||
@@ -367,6 +368,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
|
|||||||
* 开始搜索
|
* 开始搜索
|
||||||
*/
|
*/
|
||||||
private fun startSearch() {
|
private fun startSearch() {
|
||||||
|
binding.refreshProgressBar.visible()
|
||||||
binding.refreshProgressBar.isAutoLoading = true
|
binding.refreshProgressBar.isAutoLoading = true
|
||||||
binding.fbStop.visible()
|
binding.fbStop.visible()
|
||||||
}
|
}
|
||||||
@@ -376,6 +378,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
|
|||||||
*/
|
*/
|
||||||
private fun searchFinally() {
|
private fun searchFinally() {
|
||||||
binding.refreshProgressBar.isAutoLoading = false
|
binding.refreshProgressBar.isAutoLoading = false
|
||||||
|
binding.refreshProgressBar.gone()
|
||||||
binding.fbStop.invisible()
|
binding.fbStop.invisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,21 +101,21 @@ data class SearchScope(private var scope: String) {
|
|||||||
/**
|
/**
|
||||||
* 搜索范围书源
|
* 搜索范围书源
|
||||||
*/
|
*/
|
||||||
fun getBookSources(): List<BookSource> {
|
fun getBookSourceParts(): List<BookSourcePart> {
|
||||||
val list = hashSetOf<BookSource>()
|
val list = hashSetOf<BookSourcePart>()
|
||||||
if (scope.isEmpty()) {
|
if (scope.isEmpty()) {
|
||||||
list.addAll(appDb.bookSourceDao.allEnabled)
|
list.addAll(appDb.bookSourceDao.allEnabledPart)
|
||||||
} else {
|
} else {
|
||||||
if (scope.contains("::")) {
|
if (scope.contains("::")) {
|
||||||
scope.substringAfter("::").let {
|
scope.substringAfter("::").let {
|
||||||
appDb.bookSourceDao.getBookSource(it)?.let { source ->
|
appDb.bookSourceDao.getBookSourcePart(it)?.let { source ->
|
||||||
list.add(source)
|
list.add(source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val oldScope = scope.splitNotBlank(",")
|
val oldScope = scope.splitNotBlank(",")
|
||||||
val newScope = oldScope.filter {
|
val newScope = oldScope.filter {
|
||||||
val bookSources = appDb.bookSourceDao.getEnabledByGroup(it)
|
val bookSources = appDb.bookSourceDao.getEnabledPartByGroup(it)
|
||||||
list.addAll(bookSources)
|
list.addAll(bookSources)
|
||||||
bookSources.isNotEmpty()
|
bookSources.isNotEmpty()
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ data class SearchScope(private var scope: String) {
|
|||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
scope = ""
|
scope = ""
|
||||||
appDb.bookSourceDao.allEnabled.let {
|
appDb.bookSourceDao.allEnabledPart.let {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
stateLiveData.postValue(scope)
|
stateLiveData.postValue(scope)
|
||||||
list.addAll(it)
|
list.addAll(it)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
isSearchLiveData.postValue(true)
|
isSearchLiveData.postValue(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchSuccess(searchBooks: ArrayList<SearchBook>) {
|
override fun onSearchSuccess(searchBooks: List<SearchBook>) {
|
||||||
searchBookLiveData.postValue(searchBooks)
|
searchBookLiveData.postValue(searchBooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
searchFinishLiveData.postValue(isEmpty)
|
searchFinishLiveData.postValue(isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchCancel(exception: Exception?) {
|
override fun onSearchCancel(exception: Throwable?) {
|
||||||
isSearchLiveData.postValue(false)
|
isSearchLiveData.postValue(false)
|
||||||
exception?.let {
|
exception?.let {
|
||||||
context.toastOnUi(it.localizedMessage)
|
context.toastOnUi(it.localizedMessage)
|
||||||
@@ -95,6 +95,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
if ((searchKey == key) || key.isNotEmpty()) {
|
if ((searchKey == key) || key.isNotEmpty()) {
|
||||||
searchModel.cancelSearch()
|
searchModel.cancelSearch()
|
||||||
searchID = System.currentTimeMillis()
|
searchID = System.currentTimeMillis()
|
||||||
|
searchBookLiveData.postValue(emptyList())
|
||||||
searchKey = key
|
searchKey = key
|
||||||
}
|
}
|
||||||
if (searchKey.isEmpty()) {
|
if (searchKey.isEmpty()) {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
package io.legado.app.utils
|
package io.legado.app.utils
|
||||||
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.FlowPreview
|
|
||||||
import kotlinx.coroutines.flow.DEFAULT_CONCURRENCY
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.flatMapMerge
|
import kotlinx.coroutines.flow.flatMapMerge
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
inline fun <T> Flow<T>.onEachParallel(
|
inline fun <T> Flow<T>.onEachParallel(
|
||||||
concurrency: Int = DEFAULT_CONCURRENCY,
|
concurrency: Int,
|
||||||
crossinline action: suspend (T) -> Unit
|
crossinline action: suspend (T) -> Unit
|
||||||
): Flow<T> = flatMapMerge(concurrency) { value ->
|
): Flow<T> = flatMapMerge(concurrency) { value ->
|
||||||
return@flatMapMerge flow {
|
return@flatMapMerge flow {
|
||||||
@@ -17,3 +16,24 @@ inline fun <T> Flow<T>.onEachParallel(
|
|||||||
emit(value)
|
emit(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
inline fun <T, R> Flow<T>.mapParallel(
|
||||||
|
concurrency: Int,
|
||||||
|
crossinline transform: suspend (T) -> R,
|
||||||
|
): Flow<R> = flatMapMerge(concurrency) { value -> flow { emit(transform(value)) } }
|
||||||
|
|
||||||
|
inline fun <T, R> Flow<T>.mapNotNullParallel(
|
||||||
|
concurrency: Int,
|
||||||
|
crossinline transform: suspend (T) -> R?,
|
||||||
|
): Flow<R> = mapParallel(concurrency, transform).filterNotNull()
|
||||||
|
|
||||||
|
inline fun <T> Flow<T>.onEachIndexed(
|
||||||
|
crossinline action: suspend (index: Int, T) -> Unit,
|
||||||
|
): Flow<T> = flow {
|
||||||
|
var index = 0
|
||||||
|
collect { value ->
|
||||||
|
action(index++, value)
|
||||||
|
emit(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,11 +7,16 @@ import io.legado.app.data.entities.SearchBook
|
|||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.model.webBook.SearchModel
|
import io.legado.app.model.webBook.SearchModel
|
||||||
import io.legado.app.ui.book.search.SearchScope
|
import io.legado.app.ui.book.search.SearchScope
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.GSON
|
||||||
import kotlinx.coroutines.*
|
import io.legado.app.utils.fromJsonObject
|
||||||
|
import io.legado.app.utils.isJson
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class BookSearchWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
|
class BookSearchWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
|
||||||
@@ -81,12 +86,12 @@ class BookSearchWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchSuccess(searchBooks: ArrayList<SearchBook>) {
|
override fun onSearchSuccess(searchBooks: List<SearchBook>) {
|
||||||
send(GSON.toJson(searchBooks))
|
send(GSON.toJson(searchBooks))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchFinish(isEmpty: Boolean) = close(normalClosure, SEARCH_FINISH, false)
|
override fun onSearchFinish(isEmpty: Boolean) = close(normalClosure, SEARCH_FINISH, false)
|
||||||
|
|
||||||
override fun onSearchCancel(exception: Exception?) = close(normalClosure, exception?.toString() ?: SEARCH_FINISH, false)
|
override fun onSearchCancel(exception: Throwable?) = close(normalClosure, exception?.toString() ?: SEARCH_FINISH, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user