kunfei 4 жил өмнө
parent
commit
2a4872259d

+ 20 - 25
app/src/main/java/io/legado/app/data/entities/Book.kt

@@ -6,6 +6,8 @@ import io.legado.app.constant.AppPattern
 import io.legado.app.constant.BookType
 import io.legado.app.constant.BookType
 import io.legado.app.constant.PageAnim
 import io.legado.app.constant.PageAnim
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
+import io.legado.app.help.BookHelp
+import io.legado.app.help.ContentProcessor
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
@@ -271,7 +273,13 @@ data class Book(
         this.tocHtml = this@Book.tocHtml
         this.tocHtml = this@Book.tocHtml
     }
     }
 
 
-    fun changeTo(newBook: Book) {
+    fun changeTo(newBook: Book, toc: List<BookChapter>): Book {
+        newBook.durChapterIndex = BookHelp
+            .getDurChapter(durChapterIndex, durChapterTitle, toc, totalChapterNum)
+        newBook.durChapterTitle = toc[newBook.durChapterIndex].getDisplayTitle(
+            ContentProcessor.get(newBook.name, newBook.origin).getTitleReplaceRules()
+        )
+        newBook.durChapterPos = durChapterPos
         newBook.group = group
         newBook.group = group
         newBook.order = order
         newBook.order = order
         newBook.customCoverUrl = customCoverUrl
         newBook.customCoverUrl = customCoverUrl
@@ -279,23 +287,11 @@ data class Book(
         newBook.customTag = customTag
         newBook.customTag = customTag
         newBook.canUpdate = canUpdate
         newBook.canUpdate = canUpdate
         newBook.readConfig = readConfig
         newBook.readConfig = readConfig
-        delete(this)
-        appDb.bookDao.insert(newBook)
-    }
-
-    fun upInfoFromOld(oldBook: Book?) {
-        oldBook?.let {
-            group = oldBook.group
-            durChapterIndex = oldBook.durChapterIndex
-            durChapterPos = oldBook.durChapterPos
-            durChapterTitle = oldBook.durChapterTitle
-            customCoverUrl = oldBook.customCoverUrl
-            customIntro = oldBook.customIntro
-            order = oldBook.order
-            if (coverUrl.isNullOrEmpty()) {
-                coverUrl = oldBook.getDisplayCover()
-            }
+        if (appDb.bookDao.has(bookUrl) == true) {
+            delete()
+            appDb.bookDao.insert(newBook)
         }
         }
+        return newBook
     }
     }
 
 
     fun createBookMark(): Bookmark {
     fun createBookMark(): Bookmark {
@@ -313,20 +309,19 @@ data class Book(
         }
         }
     }
     }
 
 
+    fun delete() {
+        if (ReadBook.book?.bookUrl == bookUrl) {
+            ReadBook.book = null
+        }
+        appDb.bookDao.delete(this)
+    }
+
     companion object {
     companion object {
         const val hTag = 2L
         const val hTag = 2L
         const val rubyTag = 4L
         const val rubyTag = 4L
         const val imgStyleDefault = "DEFAULT"
         const val imgStyleDefault = "DEFAULT"
         const val imgStyleFull = "FULL"
         const val imgStyleFull = "FULL"
         const val imgStyleText = "TEXT"
         const val imgStyleText = "TEXT"
-
-        fun delete(book: Book?) {
-            book ?: return
-            if (ReadBook.book?.bookUrl == book.bookUrl) {
-                ReadBook.book = null
-            }
-            appDb.bookDao.delete(book)
-        }
     }
     }
 
 
     @Parcelize
     @Parcelize

+ 21 - 18
app/src/main/java/io/legado/app/model/webBook/WebBook.kt

@@ -320,35 +320,38 @@ object WebBook {
         name: String,
         name: String,
         author: String,
         author: String,
         context: CoroutineContext = Dispatchers.IO,
         context: CoroutineContext = Dispatchers.IO,
-    ): Coroutine<Pair<BookSource, Book>> {
+    ): Coroutine<Pair<Book, BookSource>> {
         return Coroutine.async(scope, context) {
         return Coroutine.async(scope, context) {
-            preciseSearchAwait(scope, name, author, *bookSources.toTypedArray())
-                ?: throw NoStackTraceException("没有搜索到<$name>$author")
+            for (source in bookSources) {
+                val book = preciseSearchAwait(scope, source, name, author).getOrNull()
+                if (book != null) {
+                    return@async Pair(book, source)
+                }
+            }
+            throw NoStackTraceException("没有搜索到<$name>$author")
         }
         }
     }
     }
 
 
     suspend fun preciseSearchAwait(
     suspend fun preciseSearchAwait(
         scope: CoroutineScope,
         scope: CoroutineScope,
+        bookSource: BookSource,
         name: String,
         name: String,
         author: String,
         author: String,
-        vararg bookSources: BookSource
-    ): Pair<BookSource, Book>? {
-        bookSources.forEach { source ->
-            kotlin.runCatching {
-                if (!scope.isActive) return null
-                searchBookAwait(scope, source, name).firstOrNull {
-                    it.name == name && it.author == author
-                }?.let { searchBook ->
-                    if (!scope.isActive) return null
-                    var book = searchBook.toBook()
-                    if (book.tocUrl.isBlank()) {
-                        book = getBookInfoAwait(scope, source, book)
-                    }
-                    return Pair(source, book)
+    ): Result<Book?> {
+        return kotlin.runCatching {
+            if (!scope.isActive) return@runCatching null
+            searchBookAwait(scope, bookSource, name).firstOrNull {
+                it.name == name && it.author == author
+            }?.let { searchBook ->
+                if (!scope.isActive) return@runCatching null
+                var book = searchBook.toBook()
+                if (book.tocUrl.isBlank()) {
+                    book = getBookInfoAwait(scope, bookSource, book)
                 }
                 }
+                return@runCatching book
             }
             }
+            return@runCatching null
         }
         }
-        return null
     }
     }
 
 
 }
 }

+ 5 - 9
app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt

@@ -46,16 +46,12 @@ class ArrangeBookViewModel(application: Application) : BaseViewModel(application
                 batchChangeSourcePosition.value = index + 1
                 batchChangeSourcePosition.value = index + 1
                 if (book.isLocalBook()) return@forEachIndexed
                 if (book.isLocalBook()) return@forEachIndexed
                 if (book.origin == source.bookSourceUrl) return@forEachIndexed
                 if (book.origin == source.bookSourceUrl) return@forEachIndexed
-                WebBook.preciseSearchAwait(this, book.name, book.author, source)?.let {
-                    val newBook = it.second
-                    newBook.upInfoFromOld(book)
-                    book.changeTo(newBook)
-                    if (newBook.tocUrl.isEmpty()) {
-                        WebBook.getBookInfoAwait(this, source, newBook)
+                WebBook.preciseSearchAwait(this, source, book.name, book.author)
+                    .getOrNull()?.let { newBook ->
+                        val toc = WebBook.getChapterListAwait(this, source, newBook)
+                        book.changeTo(newBook, toc)
+                        appDb.bookChapterDao.insert(*toc.toTypedArray())
                     }
                     }
-                    val toc = WebBook.getChapterListAwait(this, source, newBook)
-                    appDb.bookChapterDao.insert(*toc.toTypedArray())
-                }
             }
             }
         }.onFinally {
         }.onFinally {
             batchChangeSourceState.value = false
             batchChangeSourceState.value = false

+ 3 - 2
app/src/main/java/io/legado/app/ui/book/audio/AudioPlayActivity.kt

@@ -16,6 +16,7 @@ import io.legado.app.constant.EventBus
 import io.legado.app.constant.Status
 import io.legado.app.constant.Status
 import io.legado.app.constant.Theme
 import io.legado.app.constant.Theme
 import io.legado.app.data.entities.Book
 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.BookSource
 import io.legado.app.databinding.ActivityAudioPlayBinding
 import io.legado.app.databinding.ActivityAudioPlayBinding
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
@@ -198,8 +199,8 @@ class AudioPlayActivity :
     override val oldBook: Book?
     override val oldBook: Book?
         get() = AudioPlay.book
         get() = AudioPlay.book
 
 
-    override fun changeTo(source: BookSource, book: Book) {
-        viewModel.changeTo(source, book)
+    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
+        viewModel.changeTo(source, book, toc)
     }
     }
 
 
     override fun finish() {
     override fun finish() {

+ 8 - 50
app/src/main/java/io/legado/app/ui/book/audio/AudioPlayViewModel.kt

@@ -9,8 +9,6 @@ import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
-import io.legado.app.help.BookHelp
-import io.legado.app.help.ContentProcessor
 import io.legado.app.model.AudioPlay
 import io.legado.app.model.AudioPlay
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.postEvent
@@ -45,33 +43,23 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
         }
         }
     }
     }
 
 
-    private fun loadBookInfo(
-        book: Book,
-        changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null
-    ) {
+    private fun loadBookInfo(book: Book) {
         execute {
         execute {
             AudioPlay.bookSource?.let {
             AudioPlay.bookSource?.let {
                 WebBook.getBookInfo(this, it, book)
                 WebBook.getBookInfo(this, it, book)
                     .onSuccess {
                     .onSuccess {
-                        loadChapterList(book, changeDruChapterIndex)
+                        loadChapterList(book)
                     }
                     }
             }
             }
         }
         }
     }
     }
 
 
-    private fun loadChapterList(
-        book: Book,
-        changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null
-    ) {
+    private fun loadChapterList(book: Book) {
         execute {
         execute {
             AudioPlay.bookSource?.let {
             AudioPlay.bookSource?.let {
                 WebBook.getChapterList(this, it, book)
                 WebBook.getChapterList(this, it, book)
                     .onSuccess(Dispatchers.IO) { cList ->
                     .onSuccess(Dispatchers.IO) { cList ->
-                        if (changeDruChapterIndex == null) {
-                            appDb.bookChapterDao.insert(*cList.toTypedArray())
-                        } else {
-                            changeDruChapterIndex(cList)
-                        }
+                        appDb.bookChapterDao.insert(*cList.toTypedArray())
                         AudioPlay.upDurChapter(book)
                         AudioPlay.upDurChapter(book)
                     }.onError {
                     }.onError {
                         context.toastOnUi(R.string.error_load_toc)
                         context.toastOnUi(R.string.error_load_toc)
@@ -88,47 +76,17 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
         }
         }
     }
     }
 
 
-    fun changeTo(source: BookSource, book: Book) {
+    fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
         execute {
         execute {
-            var oldTocSize: Int = book.totalChapterNum
-            AudioPlay.book?.let {
-                oldTocSize = it.totalChapterNum
-                book.order = it.order
-                appDb.bookDao.delete(it)
-            }
-            appDb.bookDao.insert(book)
-            AudioPlay.book = book
+            AudioPlay.book = AudioPlay.book!!.changeTo(book, toc)
             AudioPlay.bookSource = source
             AudioPlay.bookSource = source
-            if (book.tocUrl.isEmpty()) {
-                loadBookInfo(book) { upChangeDurChapterIndex(book, oldTocSize, it) }
-            } else {
-                loadChapterList(book) { upChangeDurChapterIndex(book, oldTocSize, it) }
-            }
+            appDb.bookChapterDao.insert(*toc.toTypedArray())
+            AudioPlay.upDurChapter(book)
         }.onFinally {
         }.onFinally {
             postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)
             postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)
         }
         }
     }
     }
 
 
-    private fun upChangeDurChapterIndex(
-        book: Book,
-        oldTocSize: Int,
-        chapters: List<BookChapter>
-    ) {
-        execute {
-            book.durChapterIndex = BookHelp.getDurChapter(
-                book.durChapterIndex,
-                book.durChapterTitle,
-                chapters,
-                oldTocSize
-            )
-            book.durChapterTitle = chapters[book.durChapterIndex].getDisplayTitle(
-                ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
-            )
-            appDb.bookDao.update(book)
-            appDb.bookChapterDao.insert(*chapters.toTypedArray())
-        }
-    }
-
     fun removeFromBookshelf(success: (() -> Unit)?) {
     fun removeFromBookshelf(success: (() -> Unit)?) {
         execute {
         execute {
             AudioPlay.book?.let {
             AudioPlay.book?.let {

+ 19 - 15
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt

@@ -19,6 +19,7 @@ import io.legado.app.constant.EventBus
 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.Book
 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.BookSource
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.databinding.DialogBookChangeSourceBinding
 import io.legado.app.databinding.DialogBookChangeSourceBinding
@@ -27,6 +28,7 @@ import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.lib.theme.primaryColor
 import io.legado.app.ui.book.source.edit.BookSourceEditActivity
 import io.legado.app.ui.book.source.edit.BookSourceEditActivity
 import io.legado.app.ui.book.source.manage.BookSourceActivity
 import io.legado.app.ui.book.source.manage.BookSourceActivity
+import io.legado.app.ui.widget.dialog.WaitDialog
 import io.legado.app.ui.widget.recycler.VerticalDivider
 import io.legado.app.ui.widget.recycler.VerticalDivider
 import io.legado.app.utils.*
 import io.legado.app.utils.*
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
@@ -50,6 +52,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
     private val groups = linkedSetOf<String>()
     private val groups = linkedSetOf<String>()
     private val callBack: CallBack? get() = activity as? CallBack
     private val callBack: CallBack? get() = activity as? CallBack
     private val viewModel: ChangeBookSourceViewModel by viewModels()
     private val viewModel: ChangeBookSourceViewModel by viewModels()
+    private val waitDialog by lazy { WaitDialog(requireContext()) }
     private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) }
     private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) }
     private val editSourceResult =
     private val editSourceResult =
         registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
         registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
@@ -241,8 +244,9 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
     }
     }
 
 
     override fun changeTo(searchBook: SearchBook) {
     override fun changeTo(searchBook: SearchBook) {
-        changeSource(searchBook)
-        dismissAllowingStateLoss()
+        changeSource(searchBook) {
+            dismissAllowingStateLoss()
+        }
     }
     }
 
 
     override val bookUrl: String?
     override val bookUrl: String?
@@ -269,22 +273,22 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
     override fun deleteSource(searchBook: SearchBook) {
     override fun deleteSource(searchBook: SearchBook) {
         viewModel.del(searchBook)
         viewModel.del(searchBook)
         if (bookUrl == searchBook.bookUrl) {
         if (bookUrl == searchBook.bookUrl) {
-            viewModel.firstSourceOrNull(searchBook)?.let {
-                changeSource(it)
+            viewModel.autoChangeSource { book, toc, source ->
+                callBack?.changeTo(source, book, toc)
             }
             }
         }
         }
     }
     }
 
 
-    private fun changeSource(searchBook: SearchBook) {
-        try {
-            val book = searchBook.toBook()
-            book.upInfoFromOld(callBack?.oldBook)
-            val source = appDb.bookSourceDao.getBookSource(book.origin)
-            callBack?.changeTo(source!!, book)
-            searchBook.time = System.currentTimeMillis()
-            viewModel.updateSource(searchBook)
-        } catch (e: Exception) {
-            toastOnUi("换源失败\n${e.localizedMessage}")
+    private fun changeSource(searchBook: SearchBook, onSuccess: (() -> Unit)? = null) {
+        waitDialog.setText(R.string.load_toc)
+        waitDialog.show()
+        val book = searchBook.toBook()
+        viewModel.getToc(book, {
+            waitDialog.dismiss()
+            toastOnUi(it)
+        }) { toc, source ->
+            callBack?.changeTo(source, book, toc)
+            onSuccess?.invoke()
         }
         }
     }
     }
 
 
@@ -325,7 +329,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
 
 
     interface CallBack {
     interface CallBack {
         val oldBook: Book?
         val oldBook: Book?
-        fun changeTo(source: BookSource, book: Book)
+        fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)
     }
     }
 
 
 }
 }

+ 52 - 5
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt

@@ -11,14 +11,17 @@ import io.legado.app.constant.AppPattern
 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.Book
 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.BookSource
 import io.legado.app.data.entities.SearchBook
 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.config.AppConfig
 import io.legado.app.help.coroutine.CompositeCoroutine
 import io.legado.app.help.coroutine.CompositeCoroutine
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.utils.getPrefBoolean
 import io.legado.app.utils.getPrefBoolean
 import io.legado.app.utils.getPrefString
 import io.legado.app.utils.getPrefString
+import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.ExecutorCoroutineDispatcher
 import kotlinx.coroutines.ExecutorCoroutineDispatcher
@@ -27,6 +30,7 @@ import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
 import splitties.init.appCtx
 import splitties.init.appCtx
 import java.util.*
 import java.util.*
 import java.util.concurrent.Executors
 import java.util.concurrent.Executors
@@ -86,6 +90,11 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
     @Volatile
     @Volatile
     private var searchIndex = -1
     private var searchIndex = -1
 
 
+    override fun onCleared() {
+        super.onCleared()
+        searchPool?.close()
+    }
+
     @CallSuper
     @CallSuper
     open fun initData(arguments: Bundle?) {
     open fun initData(arguments: Bundle?) {
         arguments?.let { bundle ->
         arguments?.let { bundle ->
@@ -253,9 +262,32 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
         searchStateData.postValue(false)
         searchStateData.postValue(false)
     }
     }
 
 
-    override fun onCleared() {
-        super.onCleared()
-        searchPool?.close()
+    open fun getToc(
+        book: Book,
+        onError: (msg: String) -> Unit,
+        onSuccess: (toc: List<BookChapter>, source: BookSource) -> Unit
+    ) {
+        execute {
+            getToc(book).getOrThrow()
+        }.onError {
+            onError.invoke(it.localizedMessage ?: "加载目录错误")
+        }.onSuccess {
+            onSuccess.invoke(it.first, it.second)
+        }
+    }
+
+    suspend fun getToc(book: Book): Result<Pair<List<BookChapter>, BookSource>> {
+        return kotlin.runCatching {
+            withContext(IO) {
+                val source = appDb.bookSourceDao.getBookSource(book.origin)
+                    ?: throw NoStackTraceException("书源不存在")
+                if (book.tocUrl.isEmpty()) {
+                    WebBook.getBookInfoAwait(this, source, book)
+                }
+                val toc = WebBook.getChapterListAwait(this, source, book)
+                Pair(toc, source)
+            }
+        }
     }
     }
 
 
     fun disableSource(searchBook: SearchBook) {
     fun disableSource(searchBook: SearchBook) {
@@ -310,8 +342,23 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
         searchCallback?.upAdapter()
         searchCallback?.upAdapter()
     }
     }
 
 
-    fun firstSourceOrNull(searchBook: SearchBook): SearchBook? {
-        return searchBooks.firstOrNull { it.bookUrl != searchBook.bookUrl }
+    fun autoChangeSource(
+        onSuccess: (book: Book, toc: List<BookChapter>, source: BookSource) -> Unit
+    ) {
+        execute {
+            searchBooks.forEach {
+                val book = it.toBook()
+                val result = getToc(book).getOrNull()
+                if (result != null) {
+                    return@execute Triple(book, result.first, result.second)
+                }
+            }
+            throw NoStackTraceException("没有有效源")
+        }.onSuccess {
+            onSuccess.invoke(it.first, it.second, it.third)
+        }.onError {
+            context.toastOnUi("自动换源失败\n${it.localizedMessage}")
+        }
     }
     }
 
 
     interface SourceCallback {
     interface SourceCallback {

+ 11 - 24
app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt

@@ -67,13 +67,6 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
     private val tocAdapter by lazy {
     private val tocAdapter by lazy {
         ChangeChapterTocAdapter(requireContext(), this)
         ChangeChapterTocAdapter(requireContext(), this)
     }
     }
-    private val tocSuccess: (toc: List<BookChapter>) -> Unit = {
-        tocAdapter.durChapterIndex =
-            BookHelp.getDurChapter(viewModel.chapterIndex, viewModel.chapterTitle, it)
-        binding.loadingToc.hide()
-        tocAdapter.setItems(it)
-        binding.recyclerViewToc.scrollToPosition(tocAdapter.durChapterIndex - 5)
-    }
     private val contentSuccess: (content: String) -> Unit = {
     private val contentSuccess: (content: String) -> Unit = {
         binding.loadingToc.hide()
         binding.loadingToc.hide()
         callBack?.replaceContent(it)
         callBack?.replaceContent(it)
@@ -279,9 +272,16 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
         tocAdapter.setItems(null)
         tocAdapter.setItems(null)
         binding.clToc.visible()
         binding.clToc.visible()
         binding.loadingToc.show()
         binding.loadingToc.show()
-        viewModel.getToc(searchBook, tocSuccess) {
+        val book = searchBook.toBook()
+        viewModel.getToc(book, {
             binding.clToc.gone()
             binding.clToc.gone()
             toastOnUi(it)
             toastOnUi(it)
+        }) { toc: List<BookChapter>, _: BookSource ->
+            tocAdapter.durChapterIndex =
+                BookHelp.getDurChapter(viewModel.chapterIndex, viewModel.chapterTitle, toc)
+            binding.loadingToc.hide()
+            tocAdapter.setItems(toc)
+            binding.recyclerViewToc.scrollToPosition(tocAdapter.durChapterIndex - 5)
         }
         }
     }
     }
 
 
@@ -309,8 +309,8 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
     override fun deleteSource(searchBook: SearchBook) {
     override fun deleteSource(searchBook: SearchBook) {
         viewModel.del(searchBook)
         viewModel.del(searchBook)
         if (bookUrl == searchBook.bookUrl) {
         if (bookUrl == searchBook.bookUrl) {
-            viewModel.firstSourceOrNull(searchBook)?.let {
-                changeSource(it)
+            viewModel.autoChangeSource { book, toc, source ->
+                callBack?.changeTo(source, book, toc)
             }
             }
         }
         }
     }
     }
@@ -326,19 +326,6 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
         }
         }
     }
     }
 
 
-    private fun changeSource(searchBook: SearchBook) {
-        try {
-            val book = searchBook.toBook()
-            book.upInfoFromOld(callBack?.oldBook)
-            val source = appDb.bookSourceDao.getBookSource(book.origin)
-            callBack?.changeTo(source!!, book)
-            searchBook.time = System.currentTimeMillis()
-            viewModel.updateSource(searchBook)
-        } catch (e: Exception) {
-            toastOnUi("换源失败\n${e.localizedMessage}")
-        }
-    }
-
     /**
     /**
      * 更新分组菜单
      * 更新分组菜单
      */
      */
@@ -388,7 +375,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
 
 
     interface CallBack {
     interface CallBack {
         val oldBook: Book?
         val oldBook: Book?
-        fun changeTo(source: BookSource, book: Book)
+        fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)
         fun replaceContent(content: String)
         fun replaceContent(content: String)
     }
     }
 
 

+ 15 - 19
app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt

@@ -5,7 +5,7 @@ import android.os.Bundle
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
-import io.legado.app.data.entities.SearchBook
+import io.legado.app.data.entities.BookSource
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.ConcurrentHashMap
@@ -29,28 +29,24 @@ class ChangeChapterSourceViewModel(application: Application) :
         }
         }
     }
     }
 
 
-    fun getToc(
-        searchBook: SearchBook,
-        success: (toc: List<BookChapter>) -> Unit,
-        error: (msg: String) -> Unit
+    override fun getToc(
+        book: Book,
+        onError: (msg: String) -> Unit,
+        onSuccess: (toc: List<BookChapter>, source: BookSource) -> Unit
     ) {
     ) {
         execute {
         execute {
-            return@execute tocMap[searchBook.bookUrl]
-                ?: let {
-                    val book = searchBook.toBook()
-                    val source = appDb.bookSourceDao.getBookSource(book.origin)
-                        ?: throw NoStackTraceException("书源不存在")
-                    if (book.tocUrl.isEmpty()) {
-                        WebBook.getBookInfoAwait(this, source, book)
-                    }
-                    val toc = WebBook.getChapterListAwait(this, source, book)
-                    tocMap[book.bookUrl] = toc
-                    toc
-                }
+            val toc = tocMap[book.bookUrl]
+            if (toc != null) {
+                val source = appDb.bookSourceDao.getBookSource(book.origin)
+                return@execute Pair(toc, source!!)
+            }
+            val result = getToc(book).getOrThrow()
+            tocMap[book.bookUrl] = result.first
+            return@execute result
         }.onSuccess {
         }.onSuccess {
-            success(it)
+            onSuccess.invoke(it.first, it.second)
         }.onError {
         }.onError {
-            error(it.localizedMessage ?: "获取目录出错")
+            onError.invoke(it.localizedMessage ?: "获取目录出错")
         }
         }
     }
     }
 
 

+ 2 - 3
app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt

@@ -457,9 +457,8 @@ class BookInfoActivity :
     override val oldBook: Book?
     override val oldBook: Book?
         get() = viewModel.bookData.value
         get() = viewModel.bookData.value
 
 
-    override fun changeTo(source: BookSource, book: Book) {
-        upLoading(true)
-        viewModel.changeTo(source, book)
+    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
+        viewModel.changeTo(source, book, toc)
     }
     }
 
 
     override fun coverChangeTo(coverUrl: String) {
     override fun coverChangeTo(coverUrl: String) {

+ 10 - 58
app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt

@@ -14,7 +14,6 @@ import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
-import io.legado.app.help.ContentProcessor
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.model.BookCover
 import io.legado.app.model.BookCover
 import io.legado.app.model.ReadBook
 import io.legado.app.model.ReadBook
@@ -24,7 +23,6 @@ import io.legado.app.utils.postEvent
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.ensureActive
 
 
 class BookInfoViewModel(application: Application) : BaseViewModel(application) {
 class BookInfoViewModel(application: Application) : BaseViewModel(application) {
     val bookData = MutableLiveData<Book>()
     val bookData = MutableLiveData<Book>()
@@ -107,12 +105,11 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
     fun loadBookInfo(
     fun loadBookInfo(
         book: Book,
         book: Book,
         canReName: Boolean = true,
         canReName: Boolean = true,
-        scope: CoroutineScope = viewModelScope,
-        changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null,
+        scope: CoroutineScope = viewModelScope
     ) {
     ) {
         execute(scope) {
         execute(scope) {
             if (book.isLocalBook()) {
             if (book.isLocalBook()) {
-                loadChapter(book, scope, changeDruChapterIndex)
+                loadChapter(book, scope)
             } else {
             } else {
                 bookSource?.let { bookSource ->
                 bookSource?.let { bookSource ->
                     WebBook.getBookInfo(this, bookSource, book, canReName = canReName)
                     WebBook.getBookInfo(this, bookSource, book, canReName = canReName)
@@ -121,7 +118,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
                             if (inBookshelf) {
                             if (inBookshelf) {
                                 appDb.bookDao.update(book)
                                 appDb.bookDao.update(book)
                             }
                             }
-                            loadChapter(it, scope, changeDruChapterIndex)
+                            loadChapter(it, scope)
                         }.onError {
                         }.onError {
                             AppLog.put("获取数据信息失败\n${it.localizedMessage}", it)
                             AppLog.put("获取数据信息失败\n${it.localizedMessage}", it)
                             context.toastOnUi(R.string.error_get_book_info)
                             context.toastOnUi(R.string.error_get_book_info)
@@ -136,8 +133,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
 
 
     private fun loadChapter(
     private fun loadChapter(
         book: Book,
         book: Book,
-        scope: CoroutineScope = viewModelScope,
-        changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null,
+        scope: CoroutineScope = viewModelScope
     ) {
     ) {
         execute(scope) {
         execute(scope) {
             if (book.isLocalBook()) {
             if (book.isLocalBook()) {
@@ -156,11 +152,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
                                 appDb.bookChapterDao.delByBook(book.bookUrl)
                                 appDb.bookChapterDao.delByBook(book.bookUrl)
                                 appDb.bookChapterDao.insert(*it.toTypedArray())
                                 appDb.bookChapterDao.insert(*it.toTypedArray())
                             }
                             }
-                            if (changeDruChapterIndex == null) {
-                                chapterListData.postValue(it)
-                            } else {
-                                changeDruChapterIndex(it)
-                            }
+                            chapterListData.postValue(it)
                         }.onError {
                         }.onError {
                             chapterListData.postValue(emptyList())
                             chapterListData.postValue(emptyList())
                             AppLog.put("获取目录失败\n${it.localizedMessage}", it)
                             AppLog.put("获取目录失败\n${it.localizedMessage}", it)
@@ -184,58 +176,18 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
         }
         }
     }
     }
 
 
-    fun changeTo(source: BookSource, newBook: Book) {
+    fun changeTo(source: BookSource, newBook: Book, toc: List<BookChapter>) {
         changeSourceCoroutine?.cancel()
         changeSourceCoroutine?.cancel()
         changeSourceCoroutine = execute {
         changeSourceCoroutine = execute {
-            var oldTocSize: Int = newBook.totalChapterNum
-            if (inBookshelf) {
-                bookData.value?.let {
-                    oldTocSize = it.totalChapterNum
-                    it.changeTo(newBook)
-                }
-            }
-            bookData.postValue(newBook)
             bookSource = source
             bookSource = source
-            if (newBook.tocUrl.isEmpty()) {
-                loadBookInfo(newBook, false, this) {
-                    ensureActive()
-                    upChangeDurChapterIndex(newBook, oldTocSize, it)
-                }
-            } else {
-                loadChapter(newBook, this) {
-                    ensureActive()
-                    upChangeDurChapterIndex(newBook, oldTocSize, it)
-                }
-            }
+            bookData.value!!.changeTo(newBook, toc)
+            bookData.postValue(newBook)
+            chapterListData.postValue(toc)
         }.onFinally {
         }.onFinally {
             postEvent(EventBus.SOURCE_CHANGED, newBook.bookUrl)
             postEvent(EventBus.SOURCE_CHANGED, newBook.bookUrl)
         }
         }
     }
     }
 
 
-    private fun upChangeDurChapterIndex(
-        book: Book,
-        oldTocSize: Int,
-        chapters: List<BookChapter>
-    ) {
-        execute {
-            book.durChapterIndex = BookHelp.getDurChapter(
-                book.durChapterIndex,
-                book.durChapterTitle,
-                chapters,
-                oldTocSize
-            )
-            book.durChapterTitle = chapters[book.durChapterIndex].getDisplayTitle(
-                ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
-            )
-            if (inBookshelf) {
-                appDb.bookDao.update(book)
-                appDb.bookChapterDao.insert(*chapters.toTypedArray())
-            }
-            bookData.postValue(book)
-            chapterListData.postValue(chapters)
-        }
-    }
-
     fun topBook() {
     fun topBook() {
         execute {
         execute {
             bookData.value?.let { book ->
             bookData.value?.let { book ->
@@ -300,7 +252,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
     fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) {
     fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) {
         execute {
         execute {
             bookData.value?.let {
             bookData.value?.let {
-                Book.delete(it)
+                it.delete()
                 inBookshelf = false
                 inBookshelf = false
                 if (it.isLocalBook()) {
                 if (it.isLocalBook()) {
                     LocalBook.deleteBook(it, deleteOriginal)
                     LocalBook.deleteBook(it, deleteOriginal)

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt

@@ -715,8 +715,8 @@ class ReadBookActivity : BaseReadBookActivity(),
     override val oldBook: Book?
     override val oldBook: Book?
         get() = ReadBook.book
         get() = ReadBook.book
 
 
-    override fun changeTo(source: BookSource, book: Book) {
-        viewModel.changeTo(source, book)
+    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
+        viewModel.changeTo(source, book, toc)
     }
     }
 
 
     override fun replaceContent(content: String) {
     override fun replaceContent(content: String) {

+ 19 - 30
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -10,6 +10,7 @@ import io.legado.app.constant.AppLog
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookProgress
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
@@ -30,7 +31,6 @@ import io.legado.app.utils.postEvent
 import io.legado.app.utils.toStringArray
 import io.legado.app.utils.toStringArray
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.ensureActive
 
 
 class ReadBookViewModel(application: Application) : BaseViewModel(application) {
 class ReadBookViewModel(application: Application) : BaseViewModel(application) {
     val permissionDenialLiveData = MutableLiveData<Int>()
     val permissionDenialLiveData = MutableLiveData<Int>()
@@ -191,40 +191,22 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
     /**
     /**
      * 换源
      * 换源
      */
      */
-    fun changeTo(source: BookSource, book: Book) {
+    fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
         changeSourceCoroutine?.cancel()
         changeSourceCoroutine?.cancel()
         changeSourceCoroutine = execute {
         changeSourceCoroutine = execute {
             ReadBook.upMsg(context.getString(R.string.loading))
             ReadBook.upMsg(context.getString(R.string.loading))
-            if (book.tocUrl.isEmpty()) {
-                WebBook.getBookInfoAwait(this, source, book)
-            }
-            ensureActive()
-            val chapters = WebBook.getChapterListAwait(this, source, book)
-            ensureActive()
-            val oldBook = ReadBook.book!!
-            book.durChapterIndex = BookHelp.getDurChapter(
-                oldBook.durChapterIndex,
-                oldBook.durChapterTitle,
-                chapters,
-                oldBook.totalChapterNum
-            )
-            book.durChapterTitle = chapters[book.durChapterIndex].getDisplayTitle(
-                ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
-            )
-            ensureActive()
-            val nextChapter = chapters.getOrElse(book.durChapterIndex) {
-                chapters.first()
+            ReadBook.book!!.changeTo(book, toc)
+            val nextChapter = toc.getOrElse(book.durChapterIndex) {
+                toc.first()
             }
             }
             WebBook.getContentAwait(
             WebBook.getContentAwait(
                 this,
                 this,
                 bookSource = source,
                 bookSource = source,
                 book = book,
                 book = book,
-                bookChapter = chapters[book.durChapterIndex],
+                bookChapter = toc[book.durChapterIndex],
                 nextChapterUrl = nextChapter.url
                 nextChapterUrl = nextChapter.url
             )
             )
-            ensureActive()
-            oldBook.changeTo(book)
-            appDb.bookChapterDao.insert(*chapters.toTypedArray())
+            appDb.bookChapterDao.insert(*toc.toTypedArray())
             ReadBook.resetData(book)
             ReadBook.resetData(book)
             ReadBook.upMsg(null)
             ReadBook.upMsg(null)
             ReadBook.loadContent(resetPageOffset = true)
             ReadBook.loadContent(resetPageOffset = true)
@@ -244,10 +226,17 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
         if (!AppConfig.autoChangeSource) return
         if (!AppConfig.autoChangeSource) return
         execute {
         execute {
             val sources = appDb.bookSourceDao.allTextEnabled
             val sources = appDb.bookSourceDao.allTextEnabled
-            WebBook.preciseSearchAwait(this, name, author, *sources.toTypedArray())?.let {
-                it.second.upInfoFromOld(ReadBook.book)
-                changeTo(it.first, it.second)
-            } ?: throw NoStackTraceException("自动换源失败")
+            sources.forEach { source ->
+                WebBook.preciseSearchAwait(this, source, name, author).getOrNull()?.let { book ->
+                    if (book.tocUrl.isEmpty()) {
+                        WebBook.getBookInfoAwait(this, source, book)
+                    }
+                    val toc = WebBook.getChapterListAwait(this, source, book)
+                    changeTo(source, book, toc)
+                    return@execute
+                }
+            }
+            throw NoStackTraceException("自动换源失败")
         }.onStart {
         }.onStart {
             ReadBook.upMsg(context.getString(R.string.source_auto_changing))
             ReadBook.upMsg(context.getString(R.string.source_auto_changing))
         }.onError {
         }.onError {
@@ -272,7 +261,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
 
 
     fun removeFromBookshelf(success: (() -> Unit)?) {
     fun removeFromBookshelf(success: (() -> Unit)?) {
         execute {
         execute {
-            Book.delete(ReadBook.book)
+            ReadBook.book?.delete()
         }.onSuccess {
         }.onSuccess {
             success?.invoke()
             success?.invoke()
         }
         }

+ 1 - 1
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -132,7 +132,7 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
                 if (name.isNotEmpty() && appDb.bookDao.getBook(name, author) == null) {
                 if (name.isNotEmpty() && appDb.bookDao.getBook(name, author) == null) {
                     WebBook.preciseSearch(this, bookSources, name, author)
                     WebBook.preciseSearch(this, bookSources, name, author)
                         .onSuccess {
                         .onSuccess {
-                            val book = it.second
+                            val book = it.first
                             if (groupId > 0) {
                             if (groupId > 0) {
                                 book.group = groupId
                                 book.group = groupId
                             }
                             }