diff --git a/app/src/main/java/io/legado/app/help/AppWebDav.kt b/app/src/main/java/io/legado/app/help/AppWebDav.kt index 920f0a01c..9ec8581a0 100644 --- a/app/src/main/java/io/legado/app/help/AppWebDav.kt +++ b/app/src/main/java/io/legado/app/help/AppWebDav.kt @@ -93,7 +93,9 @@ object AppWebDav { val names = arrayListOf() authorization?.let { var files = WebDav(rootWebDavUrl, it).listFiles() - files = files.reversed() + files = files.sortedWith { o1, o2 -> + AlphanumComparator.compare(o1.displayName, o2.displayName) + }.reversed() files.forEach { webDav -> val name = webDav.displayName if (name.startsWith("backup")) { diff --git a/app/src/main/java/io/legado/app/help/book/ContentProcessor.kt b/app/src/main/java/io/legado/app/help/book/ContentProcessor.kt index 7e58d3946..c00b4d52b 100644 --- a/app/src/main/java/io/legado/app/help/book/ContentProcessor.kt +++ b/app/src/main/java/io/legado/app/help/book/ContentProcessor.kt @@ -68,7 +68,7 @@ class ContentProcessor private constructor( } } - fun upRemoveSameTitle() { + private fun upRemoveSameTitle() { val book = appDb.bookDao.getBookByOrigin(bookName, bookOrigin) ?: return removeSameTitleCache.clear() val files = BookHelp.getChapterFiles(book).filter { 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 5df7c93fc..b915899ea 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 @@ -13,7 +13,7 @@ import io.legado.app.model.webBook.WebBook import io.legado.app.utils.* import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout -import org.jsoup.nodes.Entities +import org.apache.commons.text.StringEscapeUtils import org.mozilla.javascript.NativeObject import java.net.URL import java.util.regex.Pattern @@ -49,6 +49,8 @@ class AnalyzeRule( private var objectChangedJS = false private var objectChangedJP = false + private val stringRuleCache = hashMapOf>() + @JvmOverloads fun setContent(content: Any?, baseUrl: String? = null): AnalyzeRule { if (content == null) throw AssertionError("内容不可空(Content cannot be null)") @@ -128,7 +130,7 @@ class AnalyzeRule( @JvmOverloads fun getStringList(rule: String?, mContent: Any? = null, isUrl: Boolean = false): List? { if (rule.isNullOrEmpty()) return null - val ruleList = splitSourceRule(rule, false) + val ruleList = splitSourceRuleCacheString(rule) return getStringList(ruleList, mContent, isUrl) } @@ -208,13 +210,13 @@ class AnalyzeRule( @JvmOverloads fun getString(ruleStr: String?, mContent: Any? = null, isUrl: Boolean = false): String { if (TextUtils.isEmpty(ruleStr)) return "" - val ruleList = splitSourceRule(ruleStr) + val ruleList = splitSourceRuleCacheString(ruleStr) return getString(ruleList, mContent, isUrl) } fun getString(ruleStr: String?, unescape: Boolean): String { if (TextUtils.isEmpty(ruleStr)) return "" - val ruleList = splitSourceRule(ruleStr) + val ruleList = splitSourceRuleCacheString(ruleStr) return getString(ruleList, unescape = unescape) } @@ -270,13 +272,7 @@ class AnalyzeRule( } if (result == null) result = "" val str = if (unescape) { - kotlin.runCatching { - Entities.unescape(result.toString()) - }.onFailure { - log("Entities.unescape() error\n${it.localizedMessage}") - }.getOrElse { - result.toString() - } + StringEscapeUtils.unescapeHtml3(result.toString()) } else result.toString() if (isUrl) { return if (str.isBlank()) { @@ -414,6 +410,21 @@ class AnalyzeRule( return vResult } + /** + * getString 类规则缓存 + */ + fun splitSourceRuleCacheString(ruleStr: String?) : List { + if (ruleStr.isNullOrEmpty()) return emptyList() + val cacheRule = stringRuleCache[ruleStr] + return if (cacheRule != null) { + cacheRule + } else { + val rules = splitSourceRule(ruleStr) + stringRuleCache[ruleStr] = rules + rules + } + } + /** * 分解规则生成规则列表 */ diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 7c3cf3f72..8270557fd 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -23,6 +23,7 @@ import io.legado.app.lib.webdav.WebDavException import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.* import kotlinx.coroutines.runBlocking +import org.apache.commons.text.StringEscapeUtils import org.jsoup.nodes.Entities import splitties.init.appCtx import java.io.* @@ -133,11 +134,7 @@ object LocalBook { } if (book.isEpub) { content = content?.replace("<img", "< img", true) ?: return null - return kotlin.runCatching { - Entities.unescape(content) - }.onFailure { - AppLog.put("HTML实体解码失败\n${it.localizedMessage}", it) - }.getOrDefault(content) + return StringEscapeUtils.unescapeHtml3(content) } return content } diff --git a/app/src/main/java/io/legado/app/utils/AlphanumComparator.kt b/app/src/main/java/io/legado/app/utils/AlphanumComparator.kt new file mode 100644 index 000000000..791acfd4a --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/AlphanumComparator.kt @@ -0,0 +1,76 @@ +package io.legado.app.utils + +object AlphanumComparator : Comparator { + + override fun compare(s1: String, s2: String): Int { + var thisMarker = 0 + var thatMarker = 0 + val s1Length = s1.length + val s2Length = s2.length + + while (thisMarker < s1Length && thatMarker < s2Length) { + val thisChunk = getChunk(s1, s1Length, thisMarker) + thisMarker += thisChunk.length + + val thatChunk = getChunk(s2, s2Length, thatMarker) + thatMarker += thatChunk.length + + // If both chunks contain numeric characters, sort them numerically. + var result: Int + if (isDigit(thisChunk[0]) && isDigit(thatChunk[0])) { + // Simple chunk comparison by length. + val thisChunkLength = thisChunk.length + result = thisChunkLength - thatChunk.length + // If equal, the first different number counts. + if (result == 0) { + for (i in 0 until thisChunkLength) { + result = thisChunk[i] - thatChunk[i] + if (result != 0) { + return result + } + } + } + } else { + result = thisChunk.compareTo(thatChunk) + } + + if (result != 0) { + return result + } + } + + return s1Length - s2Length + } + + private fun getChunk(string: String, length: Int, marker: Int): String { + var current = marker + val chunk = StringBuilder() + var c = string[current] + chunk.append(c) + current++ + if (isDigit(c)) { + while (current < length) { + c = string[current] + if (!isDigit(c)) { + break + } + chunk.append(c) + current++ + } + } else { + while (current < length) { + c = string[current] + if (isDigit(c)) { + break + } + chunk.append(c) + current++ + } + } + return chunk.toString() + } + + private fun isDigit(ch: Char): Boolean { + return ch in '0'..'9' + } +} diff --git a/app/src/main/java/io/legado/app/utils/HtmlFormatter.kt b/app/src/main/java/io/legado/app/utils/HtmlFormatter.kt index 8e4c05ec3..83441d970 100644 --- a/app/src/main/java/io/legado/app/utils/HtmlFormatter.kt +++ b/app/src/main/java/io/legado/app/utils/HtmlFormatter.kt @@ -2,6 +2,7 @@ package io.legado.app.utils import io.legado.app.constant.AppLog import io.legado.app.model.analyzeRule.AnalyzeUrl +import org.apache.commons.text.StringEscapeUtils import org.jsoup.nodes.Entities import java.net.URL import java.util.regex.Pattern @@ -31,12 +32,8 @@ object HtmlFormatter { .replace("\\s*\\n+\\s*".toRegex(), "\n  ") .replace("^[\\n\\s]+".toRegex(), "  ") .replace("[\\n\\s]+$".toRegex(), "") - .let { s -> - kotlin.runCatching { - Entities.unescape(s) - }.onFailure { - AppLog.put("Entities.unescape() error\n${it.localizedMessage}", it) - }.getOrDefault(s) + .let { + StringEscapeUtils.unescapeHtml3(it) } } diff --git a/app/src/main/java/io/legado/app/utils/MD5Utils.kt b/app/src/main/java/io/legado/app/utils/MD5Utils.kt index 3cf170cf8..beaac7530 100644 --- a/app/src/main/java/io/legado/app/utils/MD5Utils.kt +++ b/app/src/main/java/io/legado/app/utils/MD5Utils.kt @@ -9,12 +9,16 @@ import java.io.InputStream @Suppress("unused") object MD5Utils { + private val MD5Digester by lazy { + DigestUtil.digester("MD5") + } + fun md5Encode(str: String?): String { - return DigestUtil.digester("MD5").digestHex(str) + return MD5Digester.digestHex(str) } fun md5Encode(inputStream: InputStream): String { - return DigestUtil.digester("MD5").digestHex(inputStream) + return MD5Digester.digestHex(inputStream) } fun md5Encode16(str: String): String { 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 1a454d464..fcd0938d2 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 @@ -32,9 +32,11 @@ abstract class AbstractScriptEngine(val bindings: Bindings? = null) : ScriptEngi 200 -> { context.setBindings(bindings, 200) } + 100 -> { context.setBindings(bindings, 100) } + else -> { throw IllegalArgumentException("Invalid scope value.") } @@ -84,15 +86,11 @@ abstract class AbstractScriptEngine(val bindings: Bindings? = null) : ScriptEngi } override fun getScriptContext(bindings: Bindings): ScriptContext { - val ctx = SimpleScriptContext() + val ctx = SimpleScriptContext(bindings, context.errorWriter, context.reader, context.writer) val gs = getBindings(200) if (gs != null) { ctx.setBindings(gs, 200) } - ctx.setBindings(bindings, 100) - ctx.reader = context.reader - ctx.writer = context.writer - ctx.errorWriter = context.errorWriter return ctx } -} \ No newline at end of file +} diff --git a/modules/rhino1.7.3/src/main/java/com/script/SimpleScriptContext.kt b/modules/rhino1.7.3/src/main/java/com/script/SimpleScriptContext.kt index db8d881d4..ed4f4b6d3 100644 --- a/modules/rhino1.7.3/src/main/java/com/script/SimpleScriptContext.kt +++ b/modules/rhino1.7.3/src/main/java/com/script/SimpleScriptContext.kt @@ -9,12 +9,14 @@ import java.io.Reader import java.io.Writer import java.util.* -open class SimpleScriptContext : ScriptContext { - private var engineScope: Bindings = SimpleBindings() - override var errorWriter: Writer = PrintWriter(System.err, true) - private var globalScope: Bindings? = null - override var reader: Reader = InputStreamReader(System.`in`) +open class SimpleScriptContext( + private var engineScope: Bindings = SimpleBindings(), + override var errorWriter: Writer = PrintWriter(System.err, true), + override var reader: Reader = InputStreamReader(System.`in`), override var writer: Writer = PrintWriter(System.out, true) +) : ScriptContext { + private var globalScope: Bindings? = null + override fun setBindings(bindings: Bindings?, scope: Int) { when (scope) { 100 -> { @@ -24,6 +26,7 @@ open class SimpleScriptContext : ScriptContext { engineScope = bindings return } + 200 -> { globalScope = bindings return @@ -46,6 +49,7 @@ open class SimpleScriptContext : ScriptContext { 100 -> { return engineScope[name] } + 200 -> { return globalScope?.get(name) } @@ -58,6 +62,7 @@ open class SimpleScriptContext : ScriptContext { 100 -> { return getBindings(100)?.remove(name) } + 200 -> { return getBindings(200)?.remove(name) } @@ -71,6 +76,7 @@ open class SimpleScriptContext : ScriptContext { engineScope[name] = value return } + 200 -> { globalScope?.put(name, value) return