diff --git a/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt deleted file mode 100644 index 9de40e02c..000000000 --- a/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt +++ /dev/null @@ -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() - - @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) - fun onSearchFinish() - fun onSearchCancel() - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/webBook/SearchModel.kt b/app/src/main/java/io/legado/app/model/webBook/SearchModel.kt new file mode 100644 index 000000000..bd33ed0e7 --- /dev/null +++ b/app/src/main/java/io/legado/app/model/webBook/SearchModel.kt @@ -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() + private var searchBooks = arrayListOf() + + @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) { + 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, precision: Boolean) { + if (newDataS.isNotEmpty()) { + val copyData = ArrayList(searchBooks) + val equalData = arrayListOf() + val containsData = arrayListOf() + val otherData = arrayListOf() + 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) + fun onSearchFinish() + fun onSearchCancel() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index 43bec708f..6f91f224f 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -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() var searchBookLiveData = MutableLiveData>() @@ -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) { - 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, precision: Boolean) { - if (newDataS.isNotEmpty()) { - val copyData = ArrayList(searchBooks) - val equalData = arrayListOf() - val containsData = arrayListOf() - val otherData = arrayListOf() - 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() } }