This commit is contained in:
Horis
2024-01-30 11:30:24 +08:00
parent e8371df901
commit 2de01779af
9 changed files with 149 additions and 110 deletions
@@ -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)
} }