This commit is contained in:
Horis
2025-04-12 14:53:33 +08:00
parent af08f46da1
commit 2cd8013aa8
8 changed files with 53 additions and 23 deletions
@@ -7,6 +7,7 @@ import com.bumptech.glide.load.Options
import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.util.ContentLengthInputStream import com.bumptech.glide.util.ContentLengthInputStream
import com.script.rhino.runScriptWithContext
import io.legado.app.data.entities.BaseSource import io.legado.app.data.entities.BaseSource
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.CookieManager.cookieJarHeader import io.legado.app.help.http.CookieManager.cookieJarHeader
@@ -17,7 +18,6 @@ import io.legado.app.help.source.SourceHelp
import io.legado.app.model.ReadManga import io.legado.app.model.ReadManga
import io.legado.app.utils.ImageUtils import io.legado.app.utils.ImageUtils
import io.legado.app.utils.isWifiConnect import io.legado.app.utils.isWifiConnect
import com.script.rhino.runScriptWithContext
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
@@ -89,6 +89,7 @@ class OkHttpStreamFetcher(
stream?.close() stream?.close()
} }
responseBody?.close() responseBody?.close()
coroutineContext.cancel()
callback = null callback = null
} }
@@ -812,7 +812,7 @@ class AnalyzeRule(
if (bookSource == null || book == null) return if (bookSource == null || book == null) return
runBlocking(coroutineContext) { runBlocking(coroutineContext) {
withTimeout(1800000) { withTimeout(1800000) {
WebBook.preciseSearchAwait(this, bookSource, book.name, book.author) WebBook.preciseSearchAwait(bookSource, book.name, book.author)
.getOrThrow().let { .getOrThrow().let {
book.bookUrl = it.bookUrl book.bookUrl = it.bookUrl
it.variableMap.forEach { entry -> it.variableMap.forEach { entry ->
@@ -19,7 +19,7 @@ import io.legado.app.model.analyzeRule.RuleData
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@@ -363,7 +363,7 @@ object WebBook {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
for (s in bookSourceParts) { for (s in bookSourceParts) {
val source = s.getBookSource() ?: continue val source = s.getBookSource() ?: continue
val book = preciseSearchAwait(scope, source, name, author).getOrNull() val book = preciseSearchAwait(source, name, author).getOrNull()
if (book != null) { if (book != null) {
return@async Pair(book, source) return@async Pair(book, source)
} }
@@ -373,24 +373,19 @@ object WebBook {
} }
suspend fun preciseSearchAwait( suspend fun preciseSearchAwait(
scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
name: String, name: String,
author: String, author: String,
): Result<Book> { ): Result<Book> {
return kotlin.runCatching { return kotlin.runCatching {
scope.isActive coroutineContext.ensureActive()
searchBookAwait( searchBookAwait(
bookSource, name, bookSource, name,
filter = { fName, fAuthor -> fName == name && fAuthor == author }, filter = { fName, fAuthor -> fName == name && fAuthor == author },
shouldBreak = { it > 0 } shouldBreak = { it > 0 }
).firstOrNull()?.let { searchBook -> ).firstOrNull()?.let { searchBook ->
scope.isActive coroutineContext.ensureActive()
var book = searchBook.toBook() return@runCatching searchBook.toBook()
if (book.tocUrl.isBlank()) {
book = getBookInfoAwait(bookSource, book)
}
return@runCatching book
} }
throw NoStackTraceException("未搜索到 $name($author) 书籍") throw NoStackTraceException("未搜索到 $name($author) 书籍")
} }
@@ -350,10 +350,11 @@ class BookInfoActivity :
} }
private fun showCover(book: Book) { private fun showCover(book: Book) {
binding.ivCover.load(book.getDisplayCover(), book.name, book.author, false, book.origin) binding.ivCover.load(book.getDisplayCover(), book.name, book.author, false, book.origin) {
if (!AppConfig.isEInkMode) { if (!AppConfig.isEInkMode) {
BookCover.loadBlur(this, book.getDisplayCover()) BookCover.loadBlur(this, book.getDisplayCover(), false, book.origin)
.into(binding.bgBook) .into(binding.bgBook)
}
} }
} }
@@ -87,10 +87,18 @@ class BookshelfManageViewModel(application: Application) : BaseViewModel(applica
batchChangeSourceProcessLiveData.postValue("${index + 1} / ${books.size}") batchChangeSourceProcessLiveData.postValue("${index + 1} / ${books.size}")
if (book.isLocal) return@forEachIndexed if (book.isLocal) return@forEachIndexed
if (book.origin == source.bookSourceUrl) return@forEachIndexed if (book.origin == source.bookSourceUrl) return@forEachIndexed
val newBook = WebBook.preciseSearchAwait(this, source, book.name, book.author) val newBook = WebBook.preciseSearchAwait(source, book.name, book.author)
.onFailure { .onFailure {
AppLog.put("获取书籍出错\n${it.localizedMessage}", it, true) AppLog.put("搜索书籍出错\n${it.localizedMessage}", it, true)
}.getOrNull() ?: return@forEachIndexed }.getOrNull() ?: return@forEachIndexed
kotlin.runCatching {
if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(source, book)
}
}.onFailure {
AppLog.put("获取书籍详情出错\n${it.localizedMessage}", it, true)
return@forEachIndexed
}
WebBook.getChapterListAwait(source, newBook) WebBook.getChapterListAwait(source, newBook)
.onFailure { .onFailure {
AppLog.put("获取目录出错\n${it.localizedMessage}", it, true) AppLog.put("获取目录出错\n${it.localizedMessage}", it, true)
@@ -152,7 +152,7 @@ class ReadMangaViewModel(application: Application) : BaseViewModel(application)
// 自动换源 // 自动换源
}.mapParallelSafe(AppConfig.threadCount) { source -> }.mapParallelSafe(AppConfig.threadCount) { source ->
val book = WebBook.preciseSearchAwait(this, source, name, author).getOrThrow() val book = WebBook.preciseSearchAwait(source, name, author).getOrThrow()
if (book.tocUrl.isEmpty()) { if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(source, book) WebBook.getBookInfoAwait(source, book)
} }
@@ -292,7 +292,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
}.onStart { }.onStart {
ReadBook.upMsg(context.getString(R.string.source_auto_changing)) ReadBook.upMsg(context.getString(R.string.source_auto_changing))
}.mapParallelSafe(AppConfig.threadCount) { source -> }.mapParallelSafe(AppConfig.threadCount) { source ->
val book = WebBook.preciseSearchAwait(this, source, name, author).getOrThrow() val book = WebBook.preciseSearchAwait(source, name, author).getOrThrow()
if (book.tocUrl.isEmpty()) { if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(source, book) WebBook.getBookInfoAwait(source, book)
} }
@@ -195,7 +195,8 @@ class CoverImageView @JvmOverloads constructor(
loadOnlyWifi: Boolean = false, loadOnlyWifi: Boolean = false,
sourceOrigin: String? = null, sourceOrigin: String? = null,
fragment: Fragment? = null, fragment: Fragment? = null,
lifecycle: Lifecycle? = null lifecycle: Lifecycle? = null,
onLoadFinish: (() -> Unit)? = null
) { ) {
this.bitmapPath = path this.bitmapPath = path
this.name = name?.replace(AppPattern.bdRegex, "")?.trim() this.name = name?.replace(AppPattern.bdRegex, "")?.trim()
@@ -210,15 +211,39 @@ class CoverImageView @JvmOverloads constructor(
if (sourceOrigin != null) { if (sourceOrigin != null) {
options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin) options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin)
} }
val builder = if (fragment != null && lifecycle != null) { var builder = if (fragment != null && lifecycle != null) {
ImageLoader.load(fragment, lifecycle, path) ImageLoader.load(fragment, lifecycle, path)
} else { } else {
ImageLoader.load(context, path)//Glide自动识别http://,content://和file:// ImageLoader.load(context, path)//Glide自动识别http://,content://和file://
} }
builder.apply(options) builder = builder.apply(options)
.placeholder(BookCover.defaultDrawable) .placeholder(BookCover.defaultDrawable)
.error(BookCover.defaultDrawable) .error(BookCover.defaultDrawable)
.listener(glideListener) .listener(glideListener)
if (onLoadFinish != null) {
builder = builder.addListener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable?>?,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
onLoadFinish.invoke()
return false
}
})
}
builder
.centerCrop() .centerCrop()
.into(this) .into(this)
} }