优化
This commit is contained in:
@@ -14,10 +14,11 @@ import io.legado.app.data.entities.BookSource
|
||||
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.help.coroutine.Coroutine
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
import io.legado.app.utils.getPrefBoolean
|
||||
import io.legado.app.utils.getPrefString
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.ExecutorCoroutineDispatcher
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
@@ -26,16 +27,16 @@ import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import splitties.init.appCtx
|
||||
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.min
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class ChangeBookSourceViewModel(application: Application) : BaseViewModel(application) {
|
||||
open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(application) {
|
||||
private val threadCount = AppConfig.threadCount
|
||||
private var searchPool: ExecutorCoroutineDispatcher? = null
|
||||
val searchStateData = MutableLiveData<Boolean>()
|
||||
var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null
|
||||
var name: String = ""
|
||||
var author: String = ""
|
||||
private var tasks = CompositeCoroutine()
|
||||
@@ -70,7 +71,7 @@ class ChangeBookSourceViewModel(application: Application) : BaseViewModel(applic
|
||||
trySend(arrayOf(searchBooks))
|
||||
}
|
||||
|
||||
if (searchBooks.size <= 1) {
|
||||
if (searchBooks.isEmpty()) {
|
||||
startSearch()
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ class ChangeBookSourceViewModel(application: Application) : BaseViewModel(applic
|
||||
@Volatile
|
||||
private var searchIndex = -1
|
||||
|
||||
fun initData(arguments: Bundle?) {
|
||||
open fun initData(arguments: Bundle?) {
|
||||
arguments?.let { bundle ->
|
||||
bundle.getString("name")?.let {
|
||||
name = it
|
||||
@@ -141,73 +142,67 @@ class ChangeBookSourceViewModel(application: Application) : BaseViewModel(applic
|
||||
searchIndex++
|
||||
}
|
||||
val source = bookSourceList[searchIndex]
|
||||
val task = WebBook
|
||||
.searchBook(viewModelScope, source, name, context = searchPool!!)
|
||||
.timeout(60000L)
|
||||
.onSuccess(IO) {
|
||||
it.forEach { searchBook ->
|
||||
if (searchBook.name == name) {
|
||||
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author))
|
||||
|| !AppConfig.changeSourceCheckAuthor
|
||||
) {
|
||||
if (searchBook.latestChapterTitle.isNullOrEmpty()) {
|
||||
if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) {
|
||||
loadBookInfo(source, searchBook.toBook())
|
||||
} else {
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
val task = Coroutine.async(scope = viewModelScope, context = searchPool!!) {
|
||||
val resultBooks = WebBook.searchBookAwait(this, source, name)
|
||||
resultBooks.forEach { searchBook ->
|
||||
if (searchBook.name == name) {
|
||||
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author))
|
||||
|| !AppConfig.changeSourceCheckAuthor
|
||||
) {
|
||||
if (searchBook.latestChapterTitle.isNullOrEmpty()) {
|
||||
if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) {
|
||||
loadBookInfo(this, source, searchBook.toBook())
|
||||
} else {
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
} else {
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFinally(searchPool) {
|
||||
synchronized(this) {
|
||||
if (searchIndex < bookSourceList.lastIndex) {
|
||||
search()
|
||||
} else {
|
||||
searchIndex++
|
||||
}
|
||||
if (searchIndex >= bookSourceList.lastIndex + bookSourceList.size
|
||||
|| searchIndex >= bookSourceList.lastIndex + threadCount
|
||||
) {
|
||||
searchStateData.postValue(false)
|
||||
tasks.clear()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.timeout(60000L)
|
||||
.onFinally {
|
||||
nextSearch()
|
||||
}
|
||||
tasks.add(task)
|
||||
|
||||
}
|
||||
|
||||
private fun loadBookInfo(source: BookSource, book: Book) {
|
||||
WebBook.getBookInfo(viewModelScope, source, book, context = searchPool!!)
|
||||
.onSuccess(IO) {
|
||||
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
|
||||
loadBookToc(source, book)
|
||||
} else {
|
||||
//从详情页里获取最新章节
|
||||
book.latestChapterTitle = it.latestChapterTitle
|
||||
val searchBook = book.toSearchBook()
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
}.onError(IO) {
|
||||
it.printOnDebug()
|
||||
}
|
||||
private suspend fun loadBookInfo(scope: CoroutineScope, source: BookSource, book: Book) {
|
||||
val mBook = WebBook.getBookInfoAwait(scope, source, book)
|
||||
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
|
||||
loadBookToc(scope, source, mBook)
|
||||
} else {
|
||||
//从详情页里获取最新章节
|
||||
book.latestChapterTitle = mBook.latestChapterTitle
|
||||
val searchBook = book.toSearchBook()
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadBookToc(source: BookSource, book: Book) {
|
||||
WebBook.getChapterList(viewModelScope, source, book, context = searchPool!!)
|
||||
.onSuccess(IO) { chapters ->
|
||||
book.latestChapterTitle = chapters.last().title
|
||||
val searchBook: SearchBook = book.toSearchBook()
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}.onError(IO) {
|
||||
it.printOnDebug()
|
||||
private suspend fun loadBookToc(scope: CoroutineScope, source: BookSource, book: Book) {
|
||||
val chapters = WebBook.getChapterListAwait(scope, source, book)
|
||||
book.latestChapterTitle = chapters.last().title
|
||||
val searchBook: SearchBook = book.toSearchBook()
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
|
||||
private fun nextSearch() {
|
||||
synchronized(this) {
|
||||
if (searchIndex < bookSourceList.lastIndex) {
|
||||
search()
|
||||
} else {
|
||||
searchIndex++
|
||||
}
|
||||
if (searchIndex >= bookSourceList.lastIndex + bookSourceList.size
|
||||
|| searchIndex >= bookSourceList.lastIndex + threadCount
|
||||
) {
|
||||
searchStateData.postValue(false)
|
||||
tasks.clear()
|
||||
searchFinishCallback?.invoke(searchBooks.isEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDbSearchBooks(): List<SearchBook> {
|
||||
|
||||
+5
-310
@@ -2,101 +2,25 @@ package io.legado.app.ui.book.changesource
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Bundle
|
||||
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.AppPattern
|
||||
import io.legado.app.constant.PreferKey
|
||||
import io.legado.app.data.appDb
|
||||
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.SearchBook
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
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.getPrefBoolean
|
||||
import io.legado.app.utils.getPrefString
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.ExecutorCoroutineDispatcher
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import splitties.init.appCtx
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.min
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class ChangeChapterSourceViewModel(application: Application) : BaseViewModel(application) {
|
||||
private val threadCount = AppConfig.threadCount
|
||||
private var searchPool: ExecutorCoroutineDispatcher? = null
|
||||
private val searchGroup get() = appCtx.getPrefString("searchGroup") ?: ""
|
||||
val searchStateData = MutableLiveData<Boolean>()
|
||||
var name: String = ""
|
||||
var author: String = ""
|
||||
class ChangeChapterSourceViewModel(application: Application) :
|
||||
ChangeBookSourceViewModel(application) {
|
||||
|
||||
var chapterIndex: Int = 0
|
||||
var chapterTitle: String = ""
|
||||
private var tasks = CompositeCoroutine()
|
||||
private var screenKey: String = ""
|
||||
private var bookSourceList = arrayListOf<BookSource>()
|
||||
private val searchBooks = Collections.synchronizedList(arrayListOf<SearchBook>())
|
||||
|
||||
private val tocMap = ConcurrentHashMap<String, List<BookChapter>>()
|
||||
private var searchCallback: SourceCallback? = null
|
||||
val searchDataFlow = callbackFlow {
|
||||
|
||||
searchCallback = object : SourceCallback {
|
||||
|
||||
override fun searchSuccess(searchBook: SearchBook) {
|
||||
appDb.searchBookDao.insert(searchBook)
|
||||
when {
|
||||
screenKey.isEmpty() -> searchBooks.add(searchBook)
|
||||
searchBook.name.contains(screenKey) -> searchBooks.add(searchBook)
|
||||
else -> return
|
||||
}
|
||||
trySend(searchBooks)
|
||||
}
|
||||
|
||||
override fun upAdapter() {
|
||||
trySend(searchBooks)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getDbSearchBooks().let {
|
||||
searchBooks.clear()
|
||||
searchBooks.addAll(it)
|
||||
trySend(searchBooks)
|
||||
}
|
||||
|
||||
if (searchBooks.size <= 1) {
|
||||
startSearch()
|
||||
}
|
||||
|
||||
awaitClose {
|
||||
searchCallback = null
|
||||
}
|
||||
}.map {
|
||||
searchBooks.sortedBy { it.originOrder }
|
||||
}.flowOn(IO)
|
||||
|
||||
@Volatile
|
||||
private var searchIndex = -1
|
||||
|
||||
fun initData(arguments: Bundle?) {
|
||||
override fun initData(arguments: Bundle?) {
|
||||
arguments?.let { bundle ->
|
||||
bundle.getString("name")?.let {
|
||||
name = it
|
||||
}
|
||||
bundle.getString("author")?.let {
|
||||
author = it.replace(AppPattern.authorRegex, "")
|
||||
}
|
||||
bundle.getString("chapterTitle")?.let {
|
||||
chapterTitle = it
|
||||
}
|
||||
@@ -104,171 +28,6 @@ class ChangeChapterSourceViewModel(application: Application) : BaseViewModel(app
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSearchPool() {
|
||||
searchPool = Executors
|
||||
.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()
|
||||
searchIndex = -1
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
getDbSearchBooks().let {
|
||||
searchBooks.clear()
|
||||
searchBooks.addAll(it)
|
||||
searchCallback?.upAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
fun startSearch() {
|
||||
execute {
|
||||
stopSearch()
|
||||
appDb.searchBookDao.clear(name, author)
|
||||
searchBooks.clear()
|
||||
bookSourceList.clear()
|
||||
if (searchGroup.isBlank()) {
|
||||
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
|
||||
} else {
|
||||
val sources = appDb.bookSourceDao.getEnabledByGroup(searchGroup)
|
||||
if (sources.isEmpty()) {
|
||||
bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
|
||||
} else {
|
||||
bookSourceList.addAll(sources)
|
||||
}
|
||||
}
|
||||
searchStateData.postValue(true)
|
||||
initSearchPool()
|
||||
for (i in 0 until threadCount) {
|
||||
search()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun search() {
|
||||
synchronized(this) {
|
||||
if (searchIndex >= bookSourceList.lastIndex) {
|
||||
return
|
||||
}
|
||||
searchIndex++
|
||||
}
|
||||
val source = bookSourceList[searchIndex]
|
||||
val task = WebBook
|
||||
.searchBook(viewModelScope, source, name, context = searchPool!!)
|
||||
.timeout(60000L)
|
||||
.onSuccess(IO) {
|
||||
it.forEach { searchBook ->
|
||||
if (searchBook.name == name) {
|
||||
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author))
|
||||
|| !AppConfig.changeSourceCheckAuthor
|
||||
) {
|
||||
if (searchBook.latestChapterTitle.isNullOrEmpty()) {
|
||||
if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) {
|
||||
loadBookInfo(source, searchBook.toBook())
|
||||
} else {
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
} else {
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFinally(searchPool) {
|
||||
synchronized(this) {
|
||||
if (searchIndex < bookSourceList.lastIndex) {
|
||||
search()
|
||||
} else {
|
||||
searchIndex++
|
||||
}
|
||||
if (searchIndex >= bookSourceList.lastIndex + bookSourceList.size
|
||||
|| searchIndex >= bookSourceList.lastIndex + threadCount
|
||||
) {
|
||||
searchStateData.postValue(false)
|
||||
tasks.clear()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
tasks.add(task)
|
||||
|
||||
}
|
||||
|
||||
private fun loadBookInfo(source: BookSource, book: Book) {
|
||||
WebBook.getBookInfo(viewModelScope, source, book, context = searchPool!!)
|
||||
.onSuccess(IO) {
|
||||
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
|
||||
loadBookToc(source, book)
|
||||
} else {
|
||||
//从详情页里获取最新章节
|
||||
book.latestChapterTitle = it.latestChapterTitle
|
||||
val searchBook = book.toSearchBook()
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}
|
||||
}.onError(IO) {
|
||||
it.printOnDebug()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadBookToc(source: BookSource, book: Book) {
|
||||
WebBook.getChapterList(viewModelScope, source, book, context = searchPool!!)
|
||||
.onSuccess(IO) { chapters ->
|
||||
tocMap[book.bookUrl] = chapters
|
||||
book.latestChapterTitle = chapters.last().title
|
||||
val searchBook: SearchBook = book.toSearchBook()
|
||||
searchCallback?.searchSuccess(searchBook)
|
||||
}.onError(IO) {
|
||||
it.printOnDebug()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDbSearchBooks(): List<SearchBook> {
|
||||
return if (screenKey.isEmpty()) {
|
||||
if (AppConfig.changeSourceCheckAuthor) {
|
||||
appDb.searchBookDao.getChangeSourceSearch(name, author, searchGroup)
|
||||
} else {
|
||||
appDb.searchBookDao.getChangeSourceSearch(name, "", searchGroup)
|
||||
}
|
||||
} else {
|
||||
if (AppConfig.changeSourceCheckAuthor) {
|
||||
appDb.searchBookDao.getChangeSourceSearch(name, author, screenKey, searchGroup)
|
||||
} else {
|
||||
appDb.searchBookDao.getChangeSourceSearch(name, "", screenKey, searchGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 筛选
|
||||
*/
|
||||
fun screen(key: String?) {
|
||||
screenKey = key?.trim() ?: ""
|
||||
execute {
|
||||
getDbSearchBooks().let {
|
||||
searchBooks.clear()
|
||||
searchBooks.addAll(it)
|
||||
searchCallback?.upAdapter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startOrStopSearch() {
|
||||
if (tasks.isEmpty) {
|
||||
startSearch()
|
||||
} else {
|
||||
stopSearch()
|
||||
}
|
||||
}
|
||||
|
||||
fun stopSearch() {
|
||||
tasks.clear()
|
||||
searchPool?.close()
|
||||
searchStateData.postValue(false)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
searchPool?.close()
|
||||
}
|
||||
|
||||
fun getToc(
|
||||
searchBook: SearchBook,
|
||||
success: (toc: List<BookChapter>) -> Unit,
|
||||
@@ -312,68 +71,4 @@ class ChangeChapterSourceViewModel(application: Application) : BaseViewModel(app
|
||||
}
|
||||
}
|
||||
|
||||
fun disableSource(searchBook: SearchBook) {
|
||||
execute {
|
||||
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
|
||||
source.enabled = false
|
||||
appDb.bookSourceDao.update(source)
|
||||
}
|
||||
searchBooks.remove(searchBook)
|
||||
searchCallback?.upAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
fun topSource(searchBook: SearchBook) {
|
||||
execute {
|
||||
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
|
||||
val minOrder = appDb.bookSourceDao.minOrder - 1
|
||||
source.customOrder = minOrder
|
||||
searchBook.originOrder = source.customOrder
|
||||
appDb.bookSourceDao.update(source)
|
||||
updateSource(searchBook)
|
||||
}
|
||||
searchCallback?.upAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
fun bottomSource(searchBook: SearchBook) {
|
||||
execute {
|
||||
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
|
||||
val maxOrder = appDb.bookSourceDao.maxOrder + 1
|
||||
source.customOrder = maxOrder
|
||||
searchBook.originOrder = source.customOrder
|
||||
appDb.bookSourceDao.update(source)
|
||||
updateSource(searchBook)
|
||||
}
|
||||
searchCallback?.upAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSource(searchBook: SearchBook) {
|
||||
appDb.searchBookDao.update(searchBook)
|
||||
}
|
||||
|
||||
fun del(searchBook: SearchBook) {
|
||||
execute {
|
||||
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->
|
||||
appDb.bookSourceDao.delete(source)
|
||||
appDb.searchBookDao.delete(searchBook)
|
||||
}
|
||||
}
|
||||
searchBooks.remove(searchBook)
|
||||
searchCallback?.upAdapter()
|
||||
}
|
||||
|
||||
fun firstSourceOrNull(searchBook: SearchBook): SearchBook? {
|
||||
return searchBooks.firstOrNull { it.bookUrl != searchBook.bookUrl }
|
||||
}
|
||||
|
||||
interface SourceCallback {
|
||||
|
||||
fun searchSuccess(searchBook: SearchBook)
|
||||
|
||||
fun upAdapter()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user