diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt index 571e0629e..7011fc1c2 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt @@ -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() + 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 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 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 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 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 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 { diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt index 9bc7185e6..701781ad6 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt @@ -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() - 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() - private val searchBooks = Collections.synchronizedList(arrayListOf()) - private val tocMap = ConcurrentHashMap>() - 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 + private val tocMap = ConcurrentHashMap>() - 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 { - 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) -> 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() - - } - } \ No newline at end of file