diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index f3162f325..7bb96778b 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -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>() + 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? { diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index 8c0d23532..6f1b758fe 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -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() Debug.log(bookSource.bookSourceUrl, "┌获取目录列表", log) diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index 9d775d388..67918428c 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -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> { val analyzeRule = AnalyzeRule(book, bookSource) analyzeRule.setContent(body, baseUrl) + analyzeRule.setCoroutineContext(coroutineContext) val rUrl = analyzeRule.setRedirectUrl(redirectUrl) analyzeRule.nextChapterUrl = nextChapterUrl val nextUrlList = arrayListOf() diff --git a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt index f0ecbbc66..d260a7348 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt @@ -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) } diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt index 297ec2f50..d9f233dcf 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt @@ -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())) { diff --git a/app/src/main/java/io/legado/app/model/webBook/WebBook.kt b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt index 53cb00113..af03cf51c 100644 --- a/app/src/main/java/io/legado/app/model/webBook/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt @@ -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 { + suspend fun runPreUpdateJs(bookSource: BookSource, book: Book): Result { 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 diff --git a/modules/rhino1.7.3/src/main/java/com/script/AbstractScriptEngine.kt b/modules/rhino1.7.3/src/main/java/com/script/AbstractScriptEngine.kt index dedb0dae9..cb8362f70 100644 --- a/modules/rhino1.7.3/src/main/java/com/script/AbstractScriptEngine.kt +++ b/modules/rhino1.7.3/src/main/java/com/script/AbstractScriptEngine.kt @@ -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)) diff --git a/modules/rhino1.7.3/src/main/java/com/script/ScriptEngine.kt b/modules/rhino1.7.3/src/main/java/com/script/ScriptEngine.kt index aa57213e6..2a3d7e76d 100644 --- a/modules/rhino1.7.3/src/main/java/com/script/ScriptEngine.kt +++ b/modules/rhino1.7.3/src/main/java/com/script/ScriptEngine.kt @@ -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? diff --git a/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoContext.kt b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoContext.kt new file mode 100644 index 000000000..5cd1a261b --- /dev/null +++ b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoContext.kt @@ -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) + } + } + +} diff --git a/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoInterruptedError.kt b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoInterruptedError.kt new file mode 100644 index 000000000..d0532b337 --- /dev/null +++ b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoInterruptedError.kt @@ -0,0 +1,3 @@ +package com.script.rhino + +class RhinoInterruptError(override val cause: Throwable) : Error() diff --git a/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoScriptEngine.kt b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoScriptEngine.kt index 72501377b..eb5ca141c 100644 --- a/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoScriptEngine.kt +++ b/modules/rhino1.7.3/src/main/java/com/script/rhino/RhinoScriptEngine.kt @@ -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 ?: "" + 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? { - return super.doTopCall(callable, cx, scope, thisObj, args) + try { + return super.doTopCall(callable, cx, scope, thisObj, args) + } catch (e: RhinoInterruptError) { + throw e.cause + } } })