pull/2146/head
kunfei 2 years ago
parent e8f947b87a
commit 6356991920
  1. 4
      app/src/main/java/io/legado/app/api/controller/BookController.kt
  2. 10
      app/src/main/java/io/legado/app/help/BookHelp.kt
  3. 2
      app/src/main/java/io/legado/app/model/CacheBook.kt
  4. 6
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  5. 22
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  6. 11
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  7. 30
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  8. 37
      app/src/main/java/io/legado/app/model/webBook/BookList.kt
  9. 24
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  10. 13
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  11. 32
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  12. 19
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt
  13. 2
      app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt
  14. 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  15. 2
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt
  16. 15
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt
  17. 4
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt

@ -110,9 +110,9 @@ object BookController {
?: return returnData.setErrorMsg("未找到对应书源,请换源") ?: return returnData.setErrorMsg("未找到对应书源,请换源")
val toc = runBlocking { val toc = runBlocking {
if (book.tocUrl.isBlank()) { if (book.tocUrl.isBlank()) {
WebBook.getBookInfoAwait(this, bookSource, book) WebBook.getBookInfoAwait(bookSource, book)
} }
WebBook.getChapterListAwait(this, bookSource, book).getOrThrow() WebBook.getChapterListAwait(bookSource, book).getOrThrow()
} }
appDb.bookChapterDao.delByBook(book.bookUrl) appDb.bookChapterDao.delByBook(book.bookUrl)
appDb.bookChapterDao.insert(*toc.toTypedArray()) appDb.bookChapterDao.insert(*toc.toTypedArray())

@ -68,14 +68,13 @@ object BookHelp {
} }
suspend fun saveContent( suspend fun saveContent(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
bookChapter: BookChapter, bookChapter: BookChapter,
content: String content: String
) { ) {
saveText(book, bookChapter, content) saveText(book, bookChapter, content)
saveImages(scope, bookSource, book, bookChapter, content) saveImages(bookSource, book, bookChapter, content)
postEvent(EventBus.SAVE_CONTENT, bookChapter) postEvent(EventBus.SAVE_CONTENT, bookChapter)
} }
@ -95,7 +94,6 @@ object BookHelp {
} }
private suspend fun saveImages( private suspend fun saveImages(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
bookChapter: BookChapter, bookChapter: BookChapter,
@ -107,8 +105,10 @@ object BookHelp {
if (matcher.find()) { if (matcher.find()) {
matcher.group(1)?.let { src -> matcher.group(1)?.let { src ->
val mSrc = NetworkUtils.getAbsoluteURL(bookChapter.url, src) val mSrc = NetworkUtils.getAbsoluteURL(bookChapter.url, src)
awaitList.add(scope.async { awaitList.add(coroutineScope {
saveImage(bookSource, book, mSrc) async {
saveImage(bookSource, book, mSrc)
}
}) })
} }
} }

@ -179,7 +179,7 @@ object CacheBook {
val chapterCount = appDb.bookChapterDao.getChapterCount(book.bookUrl) val chapterCount = appDb.bookChapterDao.getChapterCount(book.bookUrl)
if (chapterCount == 0) { if (chapterCount == 0) {
runBlocking { runBlocking {
WebBook.getChapterListAwait(this, bookSource, book) WebBook.getChapterListAwait(bookSource, book)
.onFailure { .onFailure {
AppLog.put("缓存书籍没有目录且加载目录失败\n${it.localizedMessage}", it) AppLog.put("缓存书籍没有目录且加载目录失败\n${it.localizedMessage}", it)
appCtx.toastOnUi("缓存书籍没有目录且加载目录失败\n${it.localizedMessage}") appCtx.toastOnUi("缓存书籍没有目录且加载目录失败\n${it.localizedMessage}")

@ -700,7 +700,7 @@ class AnalyzeRule(
book.putVariable(entry.key, entry.value) book.putVariable(entry.key, entry.value)
} }
} }
WebBook.getBookInfoAwait(this, bookSource, book, false) WebBook.getBookInfoAwait(bookSource, book, false)
} }
} }
} }
@ -714,7 +714,7 @@ class AnalyzeRule(
if (bookSource == null || book == null) return if (bookSource == null || book == null) return
runBlocking { runBlocking {
withTimeout(1800000) { withTimeout(1800000) {
WebBook.getBookInfoAwait(this, bookSource, book) WebBook.getBookInfoAwait(bookSource, book)
} }
} }
} }
@ -728,7 +728,7 @@ class AnalyzeRule(
if (bookSource == null || book == null) return if (bookSource == null || book == null) return
runBlocking { runBlocking {
withTimeout(1800000) { withTimeout(1800000) {
WebBook.getBookInfoAwait(this, bookSource, book) WebBook.getBookInfoAwait(bookSource, book)
} }
} }
} }

@ -13,12 +13,12 @@ import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.isTrue import io.legado.app.utils.isTrue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
import kotlin.coroutines.coroutineContext
/** /**
* 获取目录 * 获取目录
@ -26,7 +26,6 @@ import splitties.init.appCtx
object BookChapterList { object BookChapterList {
suspend fun analyzeChapterList( suspend fun analyzeChapterList(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
baseUrl: String, baseUrl: String,
@ -52,7 +51,7 @@ object BookChapterList {
} }
var chapterData = var chapterData =
analyzeChapterList( analyzeChapterList(
scope, book, baseUrl, redirectUrl, body, book, baseUrl, redirectUrl, body,
tocRule, listRule, bookSource, log = true tocRule, listRule, bookSource, log = true
) )
chapterList.addAll(chapterData.first) chapterList.addAll(chapterData.first)
@ -69,7 +68,7 @@ object BookChapterList {
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getStrResponseAwait().body?.let { nextBody -> ).getStrResponseAwait().body?.let { nextBody ->
chapterData = analyzeChapterList( chapterData = analyzeChapterList(
scope, book, nextUrl, nextUrl, book, nextUrl, nextUrl,
nextBody, tocRule, listRule, bookSource nextBody, tocRule, listRule, bookSource
) )
nextUrl = chapterData.second.firstOrNull() ?: "" nextUrl = chapterData.second.firstOrNull() ?: ""
@ -92,7 +91,7 @@ object BookChapterList {
) )
val res = analyzeUrl.getStrResponseAwait() val res = analyzeUrl.getStrResponseAwait()
analyzeChapterList( analyzeChapterList(
this, book, urlStr, res.url, book, urlStr, res.url,
res.body!!, tocRule, listRule, bookSource, false res.body!!, tocRule, listRule, bookSource, false
).first ).first
} }
@ -110,14 +109,14 @@ object BookChapterList {
if (!reverse) { if (!reverse) {
chapterList.reverse() chapterList.reverse()
} }
scope.ensureActive() coroutineContext.ensureActive()
val lh = LinkedHashSet(chapterList) val lh = LinkedHashSet(chapterList)
val list = ArrayList(lh) val list = ArrayList(lh)
if (!book.getReverseToc()) { if (!book.getReverseToc()) {
list.reverse() list.reverse()
} }
Debug.log(book.origin, "◇目录总数:${list.size}") Debug.log(book.origin, "◇目录总数:${list.size}")
scope.ensureActive() coroutineContext.ensureActive()
list.forEachIndexed { index, bookChapter -> list.forEachIndexed { index, bookChapter ->
bookChapter.index = index bookChapter.index = index
} }
@ -131,12 +130,11 @@ object BookChapterList {
} }
book.lastCheckTime = System.currentTimeMillis() book.lastCheckTime = System.currentTimeMillis()
book.totalChapterNum = list.size book.totalChapterNum = list.size
scope.ensureActive() coroutineContext.ensureActive()
return list return list
} }
private fun analyzeChapterList( private suspend fun analyzeChapterList(
scope: CoroutineScope,
book: Book, book: Book,
baseUrl: String, baseUrl: String,
redirectUrl: String, redirectUrl: String,
@ -173,7 +171,7 @@ object BookChapterList {
log log
) )
} }
scope.ensureActive() coroutineContext.ensureActive()
if (elements.isNotEmpty()) { if (elements.isNotEmpty()) {
Debug.log(bookSource.bookSourceUrl, "┌解析目录列表", log) Debug.log(bookSource.bookSourceUrl, "┌解析目录列表", log)
val nameRule = analyzeRule.splitSourceRule(tocRule.chapterName) val nameRule = analyzeRule.splitSourceRule(tocRule.chapterName)
@ -183,7 +181,7 @@ object BookChapterList {
val upTimeRule = analyzeRule.splitSourceRule(tocRule.updateTime) val upTimeRule = analyzeRule.splitSourceRule(tocRule.updateTime)
val isVolumeRule = analyzeRule.splitSourceRule(tocRule.isVolume) val isVolumeRule = analyzeRule.splitSourceRule(tocRule.isVolume)
elements.forEachIndexed { index, item -> elements.forEachIndexed { index, item ->
scope.ensureActive() coroutineContext.ensureActive()
analyzeRule.setContent(item) analyzeRule.setContent(item)
val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = redirectUrl) val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = redirectUrl)
analyzeRule.chapter = bookChapter analyzeRule.chapter = bookChapter

@ -14,12 +14,12 @@ import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.HtmlFormatter import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
import kotlin.coroutines.coroutineContext
/** /**
* 获取正文 * 获取正文
@ -28,7 +28,6 @@ object BookContent {
@Throws(Exception::class) @Throws(Exception::class)
suspend fun analyzeContent( suspend fun analyzeContent(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
bookChapter: BookChapter, bookChapter: BookChapter,
@ -55,7 +54,7 @@ object BookContent {
val analyzeRule = AnalyzeRule(book, bookSource).setContent(body, baseUrl) val analyzeRule = AnalyzeRule(book, bookSource).setContent(body, baseUrl)
analyzeRule.setRedirectUrl(redirectUrl) analyzeRule.setRedirectUrl(redirectUrl)
analyzeRule.nextChapterUrl = mNextChapterUrl analyzeRule.nextChapterUrl = mNextChapterUrl
scope.ensureActive() coroutineContext.ensureActive()
var contentData = analyzeContent( var contentData = analyzeContent(
book, baseUrl, redirectUrl, body, contentRule, bookChapter, bookSource, mNextChapterUrl book, baseUrl, redirectUrl, body, contentRule, bookChapter, bookSource, mNextChapterUrl
) )
@ -68,7 +67,7 @@ object BookContent {
== NetworkUtils.getAbsoluteURL(redirectUrl, mNextChapterUrl) == NetworkUtils.getAbsoluteURL(redirectUrl, mNextChapterUrl)
) break ) break
nextUrlList.add(nextUrl) nextUrlList.add(nextUrl)
scope.ensureActive() coroutineContext.ensureActive()
val res = AnalyzeUrl( val res = AnalyzeUrl(
mUrl = nextUrl, mUrl = nextUrl,
source = bookSource, source = bookSource,
@ -106,7 +105,7 @@ object BookContent {
} }
} }
asyncArray.forEach { coroutine -> asyncArray.forEach { coroutine ->
scope.ensureActive() coroutineContext.ensureActive()
content.append("\n").append(coroutine.await()) content.append("\n").append(coroutine.await())
} }
} }
@ -124,7 +123,7 @@ object BookContent {
throw ContentEmptyException("内容为空") throw ContentEmptyException("内容为空")
} }
if (needSave) { if (needSave) {
BookHelp.saveContent(scope, bookSource, book, bookChapter, contentStr) BookHelp.saveContent(bookSource, book, bookChapter, contentStr)
} }
return contentStr return contentStr
} }

@ -13,9 +13,9 @@ import io.legado.app.utils.DebugLog
import io.legado.app.utils.HtmlFormatter import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat import io.legado.app.utils.StringUtils.wordCountFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import splitties.init.appCtx import splitties.init.appCtx
import kotlin.coroutines.coroutineContext
/** /**
@ -24,8 +24,7 @@ import splitties.init.appCtx
object BookInfo { object BookInfo {
@Throws(Exception::class) @Throws(Exception::class)
fun analyzeBookInfo( suspend fun analyzeBookInfo(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
baseUrl: String, baseUrl: String,
@ -41,11 +40,10 @@ object BookInfo {
val analyzeRule = AnalyzeRule(book, bookSource) val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl) analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(redirectUrl) analyzeRule.setRedirectUrl(redirectUrl)
analyzeBookInfo(scope, book, body, analyzeRule, bookSource, baseUrl, redirectUrl, canReName) analyzeBookInfo(book, body, analyzeRule, bookSource, baseUrl, redirectUrl, canReName)
} }
fun analyzeBookInfo( suspend fun analyzeBookInfo(
scope: CoroutineScope,
book: Book, book: Book,
body: String, body: String,
analyzeRule: AnalyzeRule, analyzeRule: AnalyzeRule,
@ -57,13 +55,13 @@ object BookInfo {
val infoRule = bookSource.getBookInfoRule() val infoRule = bookSource.getBookInfoRule()
infoRule.init?.let { infoRule.init?.let {
if (it.isNotBlank()) { if (it.isNotBlank()) {
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it)) analyzeRule.setContent(analyzeRule.getElement(it))
} }
} }
val mCanReName = canReName && !infoRule.canReName.isNullOrBlank() val mCanReName = canReName && !infoRule.canReName.isNullOrBlank()
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名") Debug.log(bookSource.bookSourceUrl, "┌获取书名")
BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let { BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let {
if (it.isNotEmpty() && (mCanReName || book.name.isEmpty())) { if (it.isNotEmpty() && (mCanReName || book.name.isEmpty())) {
@ -71,7 +69,7 @@ object BookInfo {
} }
Debug.log(bookSource.bookSourceUrl, "${it}") Debug.log(bookSource.bookSourceUrl, "${it}")
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者") Debug.log(bookSource.bookSourceUrl, "┌获取作者")
BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let { BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let {
if (it.isNotEmpty() && (mCanReName || book.author.isEmpty())) { if (it.isNotEmpty() && (mCanReName || book.author.isEmpty())) {
@ -79,7 +77,7 @@ object BookInfo {
} }
Debug.log(bookSource.bookSourceUrl, "${it}") Debug.log(bookSource.bookSourceUrl, "${it}")
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类") Debug.log(bookSource.bookSourceUrl, "┌获取分类")
try { try {
analyzeRule.getStringList(infoRule.kind) analyzeRule.getStringList(infoRule.kind)
@ -92,7 +90,7 @@ object BookInfo {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
DebugLog.e("获取分类出错", e) DebugLog.e("获取分类出错", e)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数") Debug.log(bookSource.bookSourceUrl, "┌获取字数")
try { try {
wordCountFormat(analyzeRule.getString(infoRule.wordCount)).let { wordCountFormat(analyzeRule.getString(infoRule.wordCount)).let {
@ -103,7 +101,7 @@ object BookInfo {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
DebugLog.e("获取字数出错", e) DebugLog.e("获取字数出错", e)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节") Debug.log(bookSource.bookSourceUrl, "┌获取最新章节")
try { try {
analyzeRule.getString(infoRule.lastChapter).let { analyzeRule.getString(infoRule.lastChapter).let {
@ -114,7 +112,7 @@ object BookInfo {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
DebugLog.e("获取最新章节出错", e) DebugLog.e("获取最新章节出错", e)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介") Debug.log(bookSource.bookSourceUrl, "┌获取简介")
try { try {
HtmlFormatter.format(analyzeRule.getString(infoRule.intro)).let { HtmlFormatter.format(analyzeRule.getString(infoRule.intro)).let {
@ -125,7 +123,7 @@ object BookInfo {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}") Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}")
DebugLog.e("获取简介出错", e) DebugLog.e("获取简介出错", e)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
try { try {
analyzeRule.getString(infoRule.coverUrl).let { analyzeRule.getString(infoRule.coverUrl).let {
@ -140,7 +138,7 @@ object BookInfo {
DebugLog.e("获取封面出错", e) DebugLog.e("获取封面出错", e)
} }
if (book.type != BookType.file) { if (book.type != BookType.file) {
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接") Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true) book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl
@ -149,7 +147,7 @@ object BookInfo {
} }
Debug.log(bookSource.bookSourceUrl, "${book.tocUrl}") Debug.log(bookSource.bookSourceUrl, "${book.tocUrl}")
} else { } else {
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取文件下载链接") Debug.log(bookSource.bookSourceUrl, "┌获取文件下载链接")
book.downloadUrls = analyzeRule.getStringList(infoRule.downloadUrls, isUrl = true) book.downloadUrls = analyzeRule.getStringList(infoRule.downloadUrls, isUrl = true)
if (book.downloadUrls == null) { if (book.downloadUrls == null) {

@ -14,9 +14,9 @@ import io.legado.app.model.analyzeRule.RuleData
import io.legado.app.utils.HtmlFormatter import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat import io.legado.app.utils.StringUtils.wordCountFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import splitties.init.appCtx import splitties.init.appCtx
import kotlin.coroutines.coroutineContext
/** /**
* 获取书籍列表 * 获取书籍列表
@ -24,8 +24,7 @@ import splitties.init.appCtx
object BookList { object BookList {
@Throws(Exception::class) @Throws(Exception::class)
fun analyzeBookList( suspend fun analyzeBookList(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
ruleData: RuleData, ruleData: RuleData,
analyzeUrl: AnalyzeUrl, analyzeUrl: AnalyzeUrl,
@ -46,11 +45,10 @@ object BookList {
analyzeRule.setContent(body).setBaseUrl(baseUrl) analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(baseUrl) analyzeRule.setRedirectUrl(baseUrl)
bookSource.bookUrlPattern?.let { bookSource.bookUrlPattern?.let {
scope.ensureActive() coroutineContext.ensureActive()
if (baseUrl.matches(it.toRegex())) { if (baseUrl.matches(it.toRegex())) {
Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") Debug.log(bookSource.bookSourceUrl, "≡链接为详情页")
getInfoItem( getInfoItem(
scope,
bookSource, bookSource,
analyzeRule, analyzeRule,
analyzeUrl, analyzeUrl,
@ -81,11 +79,11 @@ object BookList {
} }
Debug.log(bookSource.bookSourceUrl, "┌获取书籍列表") Debug.log(bookSource.bookSourceUrl, "┌获取书籍列表")
collections = analyzeRule.getElements(ruleList) collections = analyzeRule.getElements(ruleList)
scope.ensureActive() coroutineContext.ensureActive()
if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) { if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析") Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析")
getInfoItem( getInfoItem(
scope, bookSource, analyzeRule, analyzeUrl, body, baseUrl, ruleData.getVariable() bookSource, analyzeRule, analyzeUrl, body, baseUrl, ruleData.getVariable()
)?.let { searchBook -> )?.let { searchBook ->
searchBook.infoHtml = body searchBook.infoHtml = body
bookList.add(searchBook) bookList.add(searchBook)
@ -102,7 +100,7 @@ object BookList {
Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}") Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}")
for ((index, item) in collections.withIndex()) { for ((index, item) in collections.withIndex()) {
getSearchItem( getSearchItem(
scope, bookSource, analyzeRule, item, baseUrl, ruleData.getVariable(), bookSource, analyzeRule, item, baseUrl, ruleData.getVariable(),
index == 0, index == 0,
ruleName = ruleName, ruleName = ruleName,
ruleBookUrl = ruleBookUrl, ruleBookUrl = ruleBookUrl,
@ -127,8 +125,7 @@ object BookList {
} }
@Throws(Exception::class) @Throws(Exception::class)
private fun getInfoItem( private suspend fun getInfoItem(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
analyzeRule: AnalyzeRule, analyzeRule: AnalyzeRule,
analyzeUrl: AnalyzeUrl, analyzeUrl: AnalyzeUrl,
@ -144,7 +141,6 @@ object BookList {
book.type = bookSource.bookSourceType book.type = bookSource.bookSourceType
analyzeRule.ruleData = book analyzeRule.ruleData = book
BookInfo.analyzeBookInfo( BookInfo.analyzeBookInfo(
scope,
book, book,
body, body,
analyzeRule, analyzeRule,
@ -160,8 +156,7 @@ object BookList {
} }
@Throws(Exception::class) @Throws(Exception::class)
private fun getSearchItem( private suspend fun getSearchItem(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
analyzeRule: AnalyzeRule, analyzeRule: AnalyzeRule,
item: Any, item: Any,
@ -184,16 +179,16 @@ object BookList {
searchBook.originOrder = bookSource.customOrder searchBook.originOrder = bookSource.customOrder
analyzeRule.ruleData = searchBook analyzeRule.ruleData = searchBook
analyzeRule.setContent(item) analyzeRule.setContent(item)
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名", log) Debug.log(bookSource.bookSourceUrl, "┌获取书名", log)
searchBook.name = BookHelp.formatBookName(analyzeRule.getString(ruleName)) searchBook.name = BookHelp.formatBookName(analyzeRule.getString(ruleName))
Debug.log(bookSource.bookSourceUrl, "${searchBook.name}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.name}", log)
if (searchBook.name.isNotEmpty()) { if (searchBook.name.isNotEmpty()) {
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) Debug.log(bookSource.bookSourceUrl, "┌获取作者", log)
searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor)) searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor))
Debug.log(bookSource.bookSourceUrl, "${searchBook.author}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.author}", log)
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) Debug.log(bookSource.bookSourceUrl, "┌获取分类", log)
try { try {
searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",") searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",")
@ -201,7 +196,7 @@ object BookList {
} catch (e: Exception) { } catch (e: Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数", log) Debug.log(bookSource.bookSourceUrl, "┌获取字数", log)
try { try {
searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount)) searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount))
@ -209,7 +204,7 @@ object BookList {
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log) Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log)
try { try {
searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter) searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter)
@ -217,7 +212,7 @@ object BookList {
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) Debug.log(bookSource.bookSourceUrl, "┌获取简介", log)
try { try {
searchBook.intro = HtmlFormatter.format(analyzeRule.getString(ruleIntro)) searchBook.intro = HtmlFormatter.format(analyzeRule.getString(ruleIntro))
@ -225,7 +220,7 @@ object BookList {
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log)
try { try {
analyzeRule.getString(ruleCoverUrl).let { analyzeRule.getString(ruleCoverUrl).let {
@ -237,7 +232,7 @@ object BookList {
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log) Debug.log(bookSource.bookSourceUrl, "${e.localizedMessage}", log)
} }
scope.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log) Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log)
searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, isUrl = true) searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, isUrl = true)
if (searchBook.bookUrl.isEmpty()) { if (searchBook.bookUrl.isEmpty()) {

@ -29,12 +29,11 @@ object WebBook {
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
): Coroutine<ArrayList<SearchBook>> { ): Coroutine<ArrayList<SearchBook>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
searchBookAwait(scope, bookSource, key, page) searchBookAwait(bookSource, key, page)
} }
} }
suspend fun searchBookAwait( suspend fun searchBookAwait(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
key: String, key: String,
page: Int? = 1, page: Int? = 1,
@ -58,7 +57,6 @@ object WebBook {
} }
} }
return BookList.analyzeBookList( return BookList.analyzeBookList(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
ruleData = ruleData, ruleData = ruleData,
analyzeUrl = analyzeUrl, analyzeUrl = analyzeUrl,
@ -81,12 +79,11 @@ object WebBook {
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
): Coroutine<List<SearchBook>> { ): Coroutine<List<SearchBook>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
exploreBookAwait(scope, bookSource, url, page) exploreBookAwait(bookSource, url, page)
} }
} }
suspend fun exploreBookAwait( suspend fun exploreBookAwait(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
url: String, url: String,
page: Int? = 1, page: Int? = 1,
@ -108,7 +105,6 @@ object WebBook {
} }
} }
return BookList.analyzeBookList( return BookList.analyzeBookList(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
ruleData = ruleData, ruleData = ruleData,
analyzeUrl = analyzeUrl, analyzeUrl = analyzeUrl,
@ -129,12 +125,11 @@ object WebBook {
canReName: Boolean = true, canReName: Boolean = true,
): Coroutine<Book> { ): Coroutine<Book> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
getBookInfoAwait(scope, bookSource, book, canReName) getBookInfoAwait(bookSource, book, canReName)
} }
} }
suspend fun getBookInfoAwait( suspend fun getBookInfoAwait(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
canReName: Boolean = true, canReName: Boolean = true,
@ -142,7 +137,6 @@ object WebBook {
book.type = bookSource.bookSourceType book.type = bookSource.bookSourceType
if (!book.infoHtml.isNullOrEmpty()) { if (!book.infoHtml.isNullOrEmpty()) {
BookInfo.analyzeBookInfo( BookInfo.analyzeBookInfo(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
baseUrl = book.bookUrl, baseUrl = book.bookUrl,
@ -166,7 +160,6 @@ object WebBook {
} }
} }
BookInfo.analyzeBookInfo( BookInfo.analyzeBookInfo(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
baseUrl = book.bookUrl, baseUrl = book.bookUrl,
@ -188,12 +181,11 @@ object WebBook {
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<List<BookChapter>> { ): Coroutine<List<BookChapter>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
getChapterListAwait(scope, bookSource, book).getOrThrow() getChapterListAwait(bookSource, book).getOrThrow()
} }
} }
suspend fun getChapterListAwait( suspend fun getChapterListAwait(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
): Result<List<BookChapter>> { ): Result<List<BookChapter>> {
@ -201,7 +193,6 @@ object WebBook {
return kotlin.runCatching { return kotlin.runCatching {
if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) { if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {
BookChapterList.analyzeChapterList( BookChapterList.analyzeChapterList(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
baseUrl = book.tocUrl, baseUrl = book.tocUrl,
@ -224,7 +215,6 @@ object WebBook {
} }
} }
BookChapterList.analyzeChapterList( BookChapterList.analyzeChapterList(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
baseUrl = book.tocUrl, baseUrl = book.tocUrl,
@ -270,7 +260,6 @@ object WebBook {
} }
return if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { return if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) {
BookContent.analyzeContent( BookContent.analyzeContent(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
bookChapter = bookChapter, bookChapter = bookChapter,
@ -300,7 +289,6 @@ object WebBook {
} }
} }
BookContent.analyzeContent( BookContent.analyzeContent(
scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
bookChapter = bookChapter, bookChapter = bookChapter,
@ -342,13 +330,13 @@ object WebBook {
): Result<Book> { ): Result<Book> {
return kotlin.runCatching { return kotlin.runCatching {
scope.isActive scope.isActive
searchBookAwait(scope, bookSource, name).firstOrNull { searchBookAwait(bookSource, name).firstOrNull {
it.name == name && it.author == author it.name == name && it.author == author
}?.let { searchBook -> }?.let { searchBook ->
scope.isActive scope.isActive
var book = searchBook.toBook() var book = searchBook.toBook()
if (book.tocUrl.isBlank()) { if (book.tocUrl.isBlank()) {
book = getBookInfoAwait(scope, bookSource, book) book = getBookInfoAwait(bookSource, book)
} }
return@runCatching book return@runCatching book
} }

@ -24,8 +24,11 @@ import io.legado.app.utils.activityPendingIntent
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.servicePendingIntent import io.legado.app.utils.servicePendingIntent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import org.mozilla.javascript.WrappedException import org.mozilla.javascript.WrappedException
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min import kotlin.math.min
@ -139,7 +142,7 @@ class CheckSourceService : BaseService() {
if (CheckSource.checkSearch) { if (CheckSource.checkSearch) {
if (!source.searchUrl.isNullOrBlank()) { if (!source.searchUrl.isNullOrBlank()) {
source.removeGroup("搜索链接规则为空") source.removeGroup("搜索链接规则为空")
val searchBooks = WebBook.searchBookAwait(this, source, searchWord) val searchBooks = WebBook.searchBookAwait(source, searchWord)
if (searchBooks.isEmpty()) { if (searchBooks.isEmpty()) {
source.addGroup("搜索失效") source.addGroup("搜索失效")
} else { } else {
@ -164,7 +167,7 @@ class CheckSourceService : BaseService() {
source.addGroup("发现规则为空") source.addGroup("发现规则为空")
} else { } else {
source.removeGroup("发现规则为空") source.removeGroup("发现规则为空")
val exploreBooks = WebBook.exploreBookAwait(this, source, url) val exploreBooks = WebBook.exploreBookAwait(source, url)
if (exploreBooks.isEmpty()) { if (exploreBooks.isEmpty()) {
source.addGroup("发现失效") source.addGroup("发现失效")
} else { } else {
@ -205,13 +208,13 @@ class CheckSourceService : BaseService() {
//校验详情 //校验详情
if (CheckSource.checkInfo) { if (CheckSource.checkInfo) {
if (mBook.tocUrl.isBlank()) { if (mBook.tocUrl.isBlank()) {
mBook = WebBook.getBookInfoAwait(this, source, mBook) mBook = WebBook.getBookInfoAwait(source, mBook)
} }
//校验目录 //校验目录
if (CheckSource.checkCategory && if (CheckSource.checkCategory &&
source.bookSourceType != BookType.file source.bookSourceType != BookType.file
) { ) {
val toc = WebBook.getChapterListAwait(this, source, mBook).getOrThrow() val toc = WebBook.getChapterListAwait(source, mBook).getOrThrow()
val nextChapterUrl = toc.getOrNull(1)?.url ?: toc.first().url val nextChapterUrl = toc.getOrNull(1)?.url ?: toc.first().url
//校验正文 //校验正文
if (CheckSource.checkContent) { if (CheckSource.checkContent) {

@ -23,7 +23,6 @@ import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor import io.legado.app.help.ContentProcessor
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -37,6 +36,7 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.coroutineContext
class CacheViewModel(application: Application) : BaseViewModel(application) { class CacheViewModel(application: Application) : BaseViewModel(application) {
@ -79,9 +79,9 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val uri = Uri.parse(path) val uri = Uri.parse(path)
val doc = DocumentFile.fromTreeUri(context, uri) val doc = DocumentFile.fromTreeUri(context, uri)
?: throw NoStackTraceException("获取导出文档失败") ?: throw NoStackTraceException("获取导出文档失败")
export(this, doc, book) export(doc, book)
} else { } else {
export(this, FileUtils.createFolderIfNotExist(path), book) export(FileUtils.createFolderIfNotExist(path), book)
} }
}.onError { }.onError {
exportProgress.remove(book.bookUrl) exportProgress.remove(book.bookUrl)
@ -98,14 +98,14 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
private suspend fun export(scope: CoroutineScope, doc: DocumentFile, book: Book) { private suspend fun export(doc: DocumentFile, book: Book) {
val filename = "${getExportFileName(book)}.txt" val filename = "${getExportFileName(book)}.txt"
DocumentUtils.delete(doc, filename) DocumentUtils.delete(doc, filename)
val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename) val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename)
?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹") ?: throw NoStackTraceException("创建文档失败,请尝试重新设置导出文件夹")
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
getAllContents(scope, book) { text, srcList -> getAllContents(book) { text, srcList ->
bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset))) bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset)))
stringBuilder.append(text) stringBuilder.append(text)
srcList?.forEach { srcList?.forEach {
@ -128,12 +128,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
} }
private suspend fun export(scope: CoroutineScope, file: File, book: Book) { private suspend fun export(file: File, book: Book) {
val filename = "${getExportFileName(book)}.txt" val filename = "${getExportFileName(book)}.txt"
val bookPath = FileUtils.getPath(file, filename) val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath) val bookFile = FileUtils.createFileWithReplace(bookPath)
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
getAllContents(scope, book) { text, srcList -> getAllContents(book) { text, srcList ->
bookFile.appendText(text, Charset.forName(AppConfig.exportCharset)) bookFile.appendText(text, Charset.forName(AppConfig.exportCharset))
stringBuilder.append(text) stringBuilder.append(text)
srcList?.forEach { srcList?.forEach {
@ -157,7 +157,6 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
private suspend fun getAllContents( private suspend fun getAllContents(
scope: CoroutineScope,
book: Book, book: Book,
append: (text: String, srcList: ArrayList<Triple<String, Int, String>>?) -> Unit append: (text: String, srcList: ArrayList<Triple<String, Int, String>>?) -> Unit
) { ) {
@ -173,7 +172,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
}" }"
append(qy, null) append(qy, null)
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
scope.ensureActive() coroutineContext.ensureActive()
upAdapterLiveData.postValue(book.bookUrl) upAdapterLiveData.postValue(book.bookUrl)
exportProgress[book.bookUrl] = index exportProgress[book.bookUrl] = index
BookHelp.getContent(book, chapter).let { content -> BookHelp.getContent(book, chapter).let { content ->
@ -227,9 +226,9 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val uri = Uri.parse(path) val uri = Uri.parse(path)
val doc = DocumentFile.fromTreeUri(context, uri) val doc = DocumentFile.fromTreeUri(context, uri)
?: throw NoStackTraceException("获取导出文档失败") ?: throw NoStackTraceException("获取导出文档失败")
exportEpub(this, doc, book) exportEpub(doc, book)
} else { } else {
exportEpub(this, FileUtils.createFolderIfNotExist(path), book) exportEpub(FileUtils.createFolderIfNotExist(path), book)
} }
}.onError { }.onError {
exportProgress.remove(book.bookUrl) exportProgress.remove(book.bookUrl)
@ -246,7 +245,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
private suspend fun exportEpub(scope: CoroutineScope, doc: DocumentFile, book: Book) { private suspend fun exportEpub(doc: DocumentFile, book: Book) {
val filename = "${getExportFileName(book)}.epub" val filename = "${getExportFileName(book)}.epub"
DocumentUtils.delete(doc, filename) DocumentUtils.delete(doc, filename)
val epubBook = EpubBook() val epubBook = EpubBook()
@ -259,7 +258,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val contentModel = setAssets(doc, book, epubBook) val contentModel = setAssets(doc, book, epubBook)
//设置正文 //设置正文
setEpubContent(scope, contentModel, book, epubBook) setEpubContent(contentModel, book, epubBook)
DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc ->
context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs ->
EpubWriter().write(epubBook, bookOs) EpubWriter().write(epubBook, bookOs)
@ -269,7 +268,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
private suspend fun exportEpub(scope: CoroutineScope, file: File, book: Book) { private suspend fun exportEpub(file: File, book: Book) {
val filename = "${getExportFileName(book)}.epub" val filename = "${getExportFileName(book)}.epub"
val epubBook = EpubBook() val epubBook = EpubBook()
epubBook.version = "2.0" epubBook.version = "2.0"
@ -283,7 +282,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val bookPath = FileUtils.getPath(file, filename) val bookPath = FileUtils.getPath(file, filename)
val bookFile = FileUtils.createFileWithReplace(bookPath) val bookFile = FileUtils.createFileWithReplace(bookPath)
//设置正文 //设置正文
setEpubContent(scope, contentModel, book, epubBook) setEpubContent(contentModel, book, epubBook)
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
EpubWriter().write(epubBook, FileOutputStream(bookFile)) EpubWriter().write(epubBook, FileOutputStream(bookFile))
} }
@ -438,7 +437,6 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
private suspend fun setEpubContent( private suspend fun setEpubContent(
scope: CoroutineScope,
contentModel: String, contentModel: String,
book: Book, book: Book,
epubBook: EpubBook epubBook: EpubBook
@ -447,7 +445,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule() val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule()
val contentProcessor = ContentProcessor.get(book.name, book.origin) val contentProcessor = ContentProcessor.get(book.name, book.origin)
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
scope.ensureActive() coroutineContext.ensureActive()
upAdapterLiveData.postValue(book.bookUrl) upAdapterLiveData.postValue(book.bookUrl)
exportProgress[book.bookUrl] = index exportProgress[book.bookUrl] = index
BookHelp.getContent(book, chapter).let { content -> BookHelp.getContent(book, chapter).let { content ->

@ -21,7 +21,6 @@ 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.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
@ -155,7 +154,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
} }
val source = bookSourceList[searchIndex] val source = bookSourceList[searchIndex]
val task = Coroutine.async(scope = viewModelScope, context = searchPool!!) { val task = Coroutine.async(scope = viewModelScope, context = searchPool!!) {
val resultBooks = WebBook.searchBookAwait(this, source, name) val resultBooks = WebBook.searchBookAwait(source, name)
resultBooks.forEach { searchBook -> resultBooks.forEach { searchBook ->
if (searchBook.name == name) { if (searchBook.name == name) {
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author)) if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author))
@ -163,7 +162,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
) { ) {
if (searchBook.latestChapterTitle.isNullOrEmpty()) { if (searchBook.latestChapterTitle.isNullOrEmpty()) {
if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) { if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) {
loadBookInfo(this, source, searchBook.toBook()) loadBookInfo(source, searchBook.toBook())
} else { } else {
searchCallback?.searchSuccess(searchBook) searchCallback?.searchSuccess(searchBook)
} }
@ -183,10 +182,10 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
tasks.add(task) tasks.add(task)
} }
private suspend fun loadBookInfo(scope: CoroutineScope, source: BookSource, book: Book) { private suspend fun loadBookInfo(source: BookSource, book: Book) {
WebBook.getBookInfoAwait(scope, source, book) WebBook.getBookInfoAwait(source, book)
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
loadBookToc(scope, source, book) loadBookToc(source, book)
} else { } else {
//从详情页里获取最新章节 //从详情页里获取最新章节
val searchBook = book.toSearchBook() val searchBook = book.toSearchBook()
@ -194,8 +193,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
} }
} }
private suspend fun loadBookToc(scope: CoroutineScope, source: BookSource, book: Book) { private suspend fun loadBookToc(source: BookSource, book: Book) {
val chapters = WebBook.getChapterListAwait(scope, source, book).getOrThrow() val chapters = WebBook.getChapterListAwait(source, book).getOrThrow()
tocMap[book.bookUrl] = chapters tocMap[book.bookUrl] = chapters
book.latestChapterTitle = chapters.last().title book.latestChapterTitle = chapters.last().title
val searchBook: SearchBook = book.toSearchBook() val searchBook: SearchBook = book.toSearchBook()
@ -298,9 +297,9 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
val source = appDb.bookSourceDao.getBookSource(book.origin) val source = appDb.bookSourceDao.getBookSource(book.origin)
?: throw NoStackTraceException("书源不存在") ?: throw NoStackTraceException("书源不存在")
if (book.tocUrl.isEmpty()) { if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(this, source, book) WebBook.getBookInfoAwait(source, book)
} }
val toc = WebBook.getChapterListAwait(this, source, book).getOrThrow() val toc = WebBook.getChapterListAwait(source, book).getOrThrow()
Pair(toc, source) Pair(toc, source)
} }
} }

@ -51,7 +51,7 @@ class BookshelfManageViewModel(application: Application) : BaseViewModel(applica
.onFailure { .onFailure {
context.toastOnUi("获取书籍出错\n${it.localizedMessage}") context.toastOnUi("获取书籍出错\n${it.localizedMessage}")
}.getOrNull()?.let { newBook -> }.getOrNull()?.let { newBook ->
WebBook.getChapterListAwait(this, source, newBook) WebBook.getChapterListAwait(source, newBook)
.onFailure { .onFailure {
context.toastOnUi("获取目录出错\n${it.localizedMessage}") context.toastOnUi("获取目录出错\n${it.localizedMessage}")
}.getOrNull()?.let { toc -> }.getOrNull()?.let { toc ->

@ -245,9 +245,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
sources.forEach { source -> sources.forEach { source ->
WebBook.preciseSearchAwait(this, source, name, author).getOrNull()?.let { book -> WebBook.preciseSearchAwait(this, source, name, author).getOrNull()?.let { book ->
if (book.tocUrl.isEmpty()) { if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(this, source, book) WebBook.getBookInfoAwait(source, book)
} }
val toc = WebBook.getChapterListAwait(this, source, book).getOrThrow() val toc = WebBook.getChapterListAwait(source, book).getOrThrow()
changeTo(source, book, toc) changeTo(source, book, toc)
return@execute return@execute
} }

@ -160,7 +160,7 @@ class SearchContentActivity :
binding.fbStop.visible() binding.fbStop.visible()
val searchResults = withContext(IO) { val searchResults = withContext(IO) {
if (isLocalBook || viewModel.cacheChapterNames.contains(bookChapter.getFileName())) { if (isLocalBook || viewModel.cacheChapterNames.contains(bookChapter.getFileName())) {
viewModel.searchChapter(this, query, bookChapter) viewModel.searchChapter(query, bookChapter)
} else { } else {
null null
} }

@ -10,10 +10,10 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor import io.legado.app.help.ContentProcessor
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.coroutines.coroutineContext
class SearchContentViewModel(application: Application) : BaseViewModel(application) { class SearchContentViewModel(application: Application) : BaseViewModel(application) {
var bookUrl: String = "" var bookUrl: String = ""
@ -38,7 +38,6 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
} }
suspend fun searchChapter( suspend fun searchChapter(
scope: CoroutineScope,
query: String, query: String,
chapter: BookChapter? chapter: BookChapter?
): List<SearchResult> { ): List<SearchResult> {
@ -46,7 +45,7 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
if (chapter != null) { if (chapter != null) {
book?.let { book -> book?.let { book ->
val chapterContent = BookHelp.getContent(book, chapter) val chapterContent = BookHelp.getContent(book, chapter)
scope.ensureActive() coroutineContext.ensureActive()
if (chapterContent != null) { if (chapterContent != null) {
//先搜索没有启用净化的正文 //先搜索没有启用净化的正文
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -55,7 +54,7 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
2 -> ChineseUtils.s2t(chapter.title) 2 -> ChineseUtils.s2t(chapter.title)
else -> chapter.title else -> chapter.title
} }
scope.ensureActive() kotlin.coroutines.coroutineContext.ensureActive()
mContent = contentProcessor!!.getContent( mContent = contentProcessor!!.getContent(
book, chapter, chapterContent, book, chapter, chapterContent,
chineseConvert = true, chineseConvert = true,
@ -63,9 +62,9 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
useReplace = false useReplace = false
).joinToString("") ).joinToString("")
} }
val positions = searchPosition(scope, query) val positions = searchPosition(query)
positions.forEachIndexed { index, position -> positions.forEachIndexed { index, position ->
scope.ensureActive() coroutineContext.ensureActive()
val construct = getResultAndQueryIndex(mContent, position, query) val construct = getResultAndQueryIndex(mContent, position, query)
val result = SearchResult( val result = SearchResult(
resultCountWithinChapter = index, resultCountWithinChapter = index,
@ -85,7 +84,7 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
return searchResultsWithinChapter return searchResultsWithinChapter
} }
private suspend fun searchPosition(scope: CoroutineScope, pattern: String): List<Int> { private suspend fun searchPosition(pattern: String): List<Int> {
val position: MutableList<Int> = mutableListOf() val position: MutableList<Int> = mutableListOf()
var index = mContent.indexOf(pattern) var index = mContent.indexOf(pattern)
if (index >= 0) { if (index >= 0) {
@ -95,7 +94,7 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
index = mContent.indexOf(pattern) index = mContent.indexOf(pattern)
} }
while (index >= 0) { while (index >= 0) {
scope.ensureActive() coroutineContext.ensureActive()
position.add(index) position.add(index)
index = mContent.indexOf(pattern, index + pattern.length) index = mContent.indexOf(pattern, index + pattern.length)
} }

@ -123,9 +123,9 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
AnalyzeRule(book, source).evalJS(preUpdateJs) AnalyzeRule(book, source).evalJS(preUpdateJs)
} }
if (book.tocUrl.isBlank()) { if (book.tocUrl.isBlank()) {
WebBook.getBookInfoAwait(this, source, book) WebBook.getBookInfoAwait(source, book)
} }
val toc = WebBook.getChapterListAwait(this, source, book).getOrThrow() val toc = WebBook.getChapterListAwait(source, book).getOrThrow()
if (book.bookUrl == bookUrl) { if (book.bookUrl == bookUrl) {
appDb.bookDao.update(book) appDb.bookDao.update(book)
} else { } else {

Loading…
Cancel
Save