Explorar o código

回退正则超时中断改动

ICU native 的正则匹配不响应线程中断,InterruptibleCharSequence
无法在 native 匹配循环里生效,超时后任务依旧无法取消。
恢复为超时提示 + 3 秒后强制重启应用的原方案。

This reverts commit 49146229150ff3a2e4a6786f96e90a5fc2c5ea29.
5151561 hai 3 meses
pai
achega
9fee3876c9
Modificáronse 1 ficheiros con 43 adicións e 68 borrados
  1. 43 68
      app/src/main/java/io/legado/app/utils/RegexExtensions.kt

+ 43 - 68
app/src/main/java/io/legado/app/utils/RegexExtensions.kt

@@ -1,5 +1,6 @@
 package io.legado.app.utils
 
+import androidx.core.os.postDelayed
 import com.script.ScriptBindings
 import com.script.rhino.RhinoScriptEngine
 import io.legado.app.data.appDb
@@ -7,13 +8,15 @@ import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.exception.RegexTimeoutException
 import io.legado.app.help.CrashHandler
+import io.legado.app.help.coroutine.Coroutine
 import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.TimeoutCancellationException
-import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.runInterruptible
-import kotlinx.coroutines.withTimeout
+import kotlinx.coroutines.suspendCancellableCoroutine
 import splitties.init.appCtx
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+private val handler by lazy { buildMainHandler() }
 
 /**
  * 带有超时检测的正则替换
@@ -36,76 +39,48 @@ fun CharSequence.replace(
         null
     }
     return runBlocking {
-        try {
-            withTimeout(timeout) {
-                val timeoutContext = currentCoroutineContext()
-                runInterruptible(IO) {
+        suspendCancellableCoroutine { block ->
+            val coroutine = Coroutine.async(executeContext = IO) {
+                try {
                     val pattern = regex.toPattern()
-                    val matcher = pattern.matcher(InterruptibleCharSequence(charSequence))
+                    val matcher = pattern.matcher(charSequence)
                     val stringBuffer = StringBuffer()
-                    try {
-                        while (matcher.find()) {
-                            if (isJs) {
-                                val jsResult = RhinoScriptEngine.run {
-                                    val bindings = ScriptBindings()
-                                    bindings["result"] = matcher.group()
-                                    bindings["chapter"] = chapter
-                                    bindings["book"] = book
-                                    eval(
-                                        replacement1,
-                                        getRuntimeScope(bindings),
-                                        timeoutContext,
-                                    )
-                                }.toString()
-                                val quotedResult = jsResult.quoteReplacementJs()
-                                matcher.appendReplacement(stringBuffer, quotedResult)
-                            } else {
-                                matcher.appendReplacement(stringBuffer, replacement1)
-                            }
+                    while (matcher.find()) {
+                        if (isJs) {
+                            val jsResult = RhinoScriptEngine.run {
+                                val bindings = ScriptBindings()
+                                bindings["result"] = matcher.group()
+                                bindings["chapter"] = chapter
+                                bindings["book"] = book
+                                eval(replacement1, bindings)
+                            }.toString()
+                            val quotedResult = jsResult.quoteReplacementJs()
+                            matcher.appendReplacement(stringBuffer, quotedResult)
+                        } else {
+                            matcher.appendReplacement(stringBuffer, replacement1)
+                        }
+                    }
+                    matcher.appendTail(stringBuffer)
+                    block.resume(stringBuffer.toString())
+                } catch (e: Exception) {
+                    block.resumeWithException(e)
+                }
+            }
+            handler.postDelayed(timeout) {
+                if (coroutine.isActive) {
+                    val timeoutMsg =
+                        "替换超时,3秒后还未结束将重启应用\n替换规则$regex\n替换内容:$charSequence"
+                    val exception = RegexTimeoutException(timeoutMsg)
+                    block.cancel(exception)
+                    appCtx.longToastOnUi(timeoutMsg)
+                    CrashHandler.saveCrashInfo2File(exception)
+                    handler.postDelayed(3000) {
+                        if (coroutine.isActive) {
+                            appCtx.restart()
                         }
-                        matcher.appendTail(stringBuffer)
-                    } catch (_: RegexInterruptedException) {
-                        throw InterruptedException()
                     }
-                    stringBuffer.toString()
                 }
             }
-        } catch (_: TimeoutCancellationException) {
-            val timeoutMsg = "替换超时\n替换规则$regex\n替换内容:$charSequence"
-            val exception = RegexTimeoutException(timeoutMsg)
-            appCtx.longToastOnUi(timeoutMsg)
-            CrashHandler.saveCrashInfo2File(exception)
-            throw exception
-        }
-    }
-}
-
-/**
- * Checks the worker thread's interrupted state while java.util.regex reads its input.
- */
-private class InterruptibleCharSequence(private val delegate: CharSequence) : CharSequence {
-
-    private var counter = 0
-
-    override val length: Int get() = delegate.length
-
-    override fun get(index: Int): Char {
-        if ((counter++ and CHECK_MASK) == 0 && Thread.currentThread().isInterrupted) {
-            throw RegexInterruptedException()
         }
-        return delegate[index]
-    }
-
-    override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
-        InterruptibleCharSequence(delegate.subSequence(startIndex, endIndex))
-
-    override fun toString(): String = delegate.toString()
-
-    private companion object {
-        const val CHECK_MASK = 0x3FF
     }
 }
-
-private class RegexInterruptedException : RuntimeException() {
-    override fun fillInStackTrace(): Throwable = this
-}