This commit is contained in:
Horis 2024-03-08 09:55:39 +08:00
parent 2cda94547e
commit f3be1c36b8
11 changed files with 105 additions and 7 deletions

View File

@ -21,6 +21,7 @@ import io.legado.app.utils.isJson
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.stackTraceStr
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.apache.commons.text.StringEscapeUtils
@ -31,6 +32,7 @@ import java.util.regex.Pattern
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.coroutines.CoroutineContext
/**
* 解析规则获取结果
@ -65,6 +67,8 @@ class AnalyzeRule(
private val stringRuleCache = hashMapOf<String, List<SourceRule>>()
private var coroutineContext: CoroutineContext? = null
@JvmOverloads
fun setContent(content: Any?, baseUrl: String? = null): AnalyzeRule {
if (content == null) throw AssertionError("内容不可空Content cannot be null")
@ -80,6 +84,11 @@ class AnalyzeRule(
return this
}
fun setCoroutineContext(context: CoroutineContext?): AnalyzeRule {
coroutineContext = context
return this
}
fun setBaseUrl(baseUrl: String?): AnalyzeRule {
baseUrl?.let {
this.baseUrl = baseUrl
@ -752,7 +761,7 @@ class AnalyzeRule(
source?.getShareScope()?.let {
scope.prototype = it
}
return RhinoScriptEngine.eval(jsStr, scope)
return RhinoScriptEngine.eval(jsStr, scope, coroutineContext)
}
override fun getSource(): BaseSource? {

View File

@ -168,6 +168,7 @@ object BookChapterList {
val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(redirectUrl)
analyzeRule.setCoroutineContext(coroutineContext)
//获取目录列表
val chapterList = arrayListOf<BookChapter>()
Debug.log(bookSource.bookSourceUrl, "┌获取目录列表", log)

View File

@ -57,6 +57,7 @@ object BookContent {
val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body, baseUrl)
analyzeRule.setRedirectUrl(redirectUrl)
analyzeRule.setCoroutineContext(coroutineContext)
analyzeRule.chapter = bookChapter
analyzeRule.nextChapterUrl = mNextChapterUrl
coroutineContext.ensureActive()
@ -150,7 +151,7 @@ object BookContent {
}
@Throws(Exception::class)
private fun analyzeContent(
private suspend fun analyzeContent(
book: Book,
baseUrl: String,
redirectUrl: String,
@ -164,6 +165,7 @@ object BookContent {
): Pair<String, List<String>> {
val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body, baseUrl)
analyzeRule.setCoroutineContext(coroutineContext)
val rUrl = analyzeRule.setRedirectUrl(redirectUrl)
analyzeRule.nextChapterUrl = nextChapterUrl
val nextUrlList = arrayListOf<String>()

View File

@ -40,6 +40,7 @@ object BookInfo {
val analyzeRule = AnalyzeRule(book, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(redirectUrl)
analyzeRule.setCoroutineContext(coroutineContext)
analyzeBookInfo(book, body, analyzeRule, bookSource, baseUrl, redirectUrl, canReName)
}

View File

@ -45,6 +45,7 @@ object BookList {
val analyzeRule = AnalyzeRule(ruleData, bookSource)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
analyzeRule.setRedirectUrl(baseUrl)
analyzeRule.setCoroutineContext(coroutineContext)
if (isSearch) bookSource.bookUrlPattern?.let {
coroutineContext.ensureActive()
if (baseUrl.matches(it.toRegex())) {

View File

@ -18,6 +18,7 @@ import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
@Suppress("MemberVisibilityCanBePrivate")
object WebBook {
@ -196,12 +197,14 @@ object WebBook {
}
}
fun runPreUpdateJs(bookSource: BookSource, book: Book): Result<Boolean> {
suspend fun runPreUpdateJs(bookSource: BookSource, book: Book): Result<Boolean> {
return kotlin.runCatching {
val preUpdateJs = bookSource.ruleToc?.preUpdateJs
if (!preUpdateJs.isNullOrBlank()) {
kotlin.runCatching {
AnalyzeRule(book, bookSource).evalJS(preUpdateJs)
AnalyzeRule(book, bookSource)
.setCoroutineContext(coroutineContext)
.evalJS(preUpdateJs)
}.onFailure {
AppLog.put("执行preUpdateJs规则失败 书源:${bookSource.bookSourceName}", it)
throw it

View File

@ -6,6 +6,7 @@ package com.script
import org.mozilla.javascript.Scriptable
import java.io.Reader
import java.io.StringReader
import kotlin.coroutines.CoroutineContext
abstract class AbstractScriptEngine(val bindings: Bindings? = null) : ScriptEngine {
@ -64,6 +65,10 @@ abstract class AbstractScriptEngine(val bindings: Bindings? = null) : ScriptEngi
return this.eval(reader, getRuntimeScope(context))
}
override fun eval(script: String, scope: Scriptable, coroutineContext: CoroutineContext?): Any? {
return this.eval(StringReader(script), scope, coroutineContext)
}
@Throws(ScriptException::class)
override fun eval(reader: Reader, bindings: Bindings): Any? {
return this.eval(reader, getScriptContext(bindings))

View File

@ -5,6 +5,7 @@ package com.script
import org.mozilla.javascript.Scriptable
import java.io.Reader
import kotlin.coroutines.CoroutineContext
interface ScriptEngine {
var context: ScriptContext
@ -14,14 +15,20 @@ interface ScriptEngine {
@Throws(ScriptException::class)
fun eval(reader: Reader, scope: Scriptable): Any?
@Throws(ScriptException::class)
fun eval(reader: Reader, scope: Scriptable, coroutineContext: CoroutineContext?): Any?
@Throws(ScriptException::class)
suspend fun evalSuspend(reader: Reader, scope: Scriptable): Any?
@Throws(ScriptException::class)
fun eval(script: String, scope: Scriptable): Any?
@Throws(ScriptException::class)
suspend fun evalSuspend(script: String, scope: Scriptable): Any?
@Throws(ScriptException::class)
fun eval(script: String, scope: Scriptable): Any?
fun eval(script: String, scope: Scriptable, coroutineContext: CoroutineContext?): Any?
@Throws(ScriptException::class)
fun eval(reader: Reader): Any?

View File

@ -0,0 +1,22 @@
package com.script.rhino
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.ensureActive
import org.mozilla.javascript.Context
import org.mozilla.javascript.ContextFactory
import kotlin.coroutines.CoroutineContext
class RhinoContext(factory: ContextFactory) : Context(factory) {
var coroutineContext: CoroutineContext? = null
@Throws(RhinoInterruptError::class)
fun ensureActive() {
try {
coroutineContext?.ensureActive()
} catch (e: CancellationException) {
throw RhinoInterruptError(e)
}
}
}

View File

@ -0,0 +1,3 @@
package com.script.rhino
class RhinoInterruptError(override val cause: Throwable) : Error()

View File

@ -35,6 +35,7 @@ import java.io.StringReader
import java.lang.reflect.Method
import java.security.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
/**
@ -84,6 +85,38 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
return unwrapReturnValue(ret)
}
override fun eval(
reader: Reader,
scope: Scriptable,
coroutineContext: CoroutineContext?
): Any? {
val cx = Context.enter()
if (cx is RhinoContext) {
cx.coroutineContext = coroutineContext
}
val ret: Any?
try {
var filename = this["javax.script.filename"] as? String
filename = filename ?: "<Unknown source>"
ret = cx.evaluateReader(scope, reader, filename, 1, null)
} catch (re: RhinoException) {
val line = if (re.lineNumber() == 0) -1 else re.lineNumber()
val msg: String = if (re is JavaScriptException) {
re.value.toString()
} else {
re.toString()
}
val se = ScriptException(msg, re.sourceName(), line)
se.initCause(re)
throw se
} catch (var14: IOException) {
throw ScriptException(var14)
} finally {
Context.exit()
}
return unwrapReturnValue(ret)
}
@Throws(ContinuationPending::class)
override suspend fun evalSuspend(reader: Reader, scope: Scriptable): Any? {
val cx = Context.enter()
@ -259,11 +292,12 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
ContextFactory.initGlobal(object : ContextFactory() {
override fun makeContext(): Context {
val cx = super.makeContext()
val cx = RhinoContext(this)
cx.languageVersion = 200
cx.optimizationLevel = -1
cx.setClassShutter(RhinoClassShutter)
cx.wrapFactory = RhinoWrapFactory
cx.instructionObserverThreshold = 10000
return cx
}
@ -275,6 +309,12 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
}
}
override fun observeInstructionCount(cx: Context, instructionCount: Int) {
if (cx is RhinoContext) {
cx.ensureActive()
}
}
override fun doTopCall(
callable: Callable,
cx: Context,
@ -308,7 +348,11 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
thisObj: Scriptable?,
args: Array<Any>
): Any? {
return super.doTopCall(callable, cx, scope, thisObj, args)
try {
return super.doTopCall(callable, cx, scope, thisObj, args)
} catch (e: RhinoInterruptError) {
throw e.cause
}
}
})