perf: check type

This commit is contained in:
lisonge 2024-02-26 23:04:44 +08:00
parent 4758448739
commit ad394c5a84
13 changed files with 129 additions and 136 deletions

View File

@ -16,7 +16,6 @@ import kotlinx.coroutines.launch
import li.songe.gkd.composition.CompositionActivity
import li.songe.gkd.composition.CompositionExt.useLifeCycleLog
import li.songe.gkd.service.ManageService
import li.songe.gkd.service.updateLauncherAppId
import li.songe.gkd.ui.NavGraphs
import li.songe.gkd.ui.component.ConfirmDialog
import li.songe.gkd.ui.theme.AppTheme
@ -47,10 +46,8 @@ class MainActivity : CompositionActivity({
}
}
// 每次打开页面更新记录桌面 appId
updateLauncherAppId()
ManageService.autoStart(this)
mainVm
setContent {
val navController = rememberNavController()

View File

@ -3,10 +3,12 @@ package li.songe.gkd
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import li.songe.gkd.data.RawSubscription
import li.songe.gkd.data.SubsItem
import li.songe.gkd.db.DbSet
import li.songe.gkd.service.updateLauncherAppId
import li.songe.gkd.util.authActionFlow
import li.songe.gkd.util.checkUpdate
import li.songe.gkd.util.initFolder
@ -15,17 +17,42 @@ import li.songe.gkd.util.logZipDir
import li.songe.gkd.util.newVersionApkDir
import li.songe.gkd.util.snapshotZipDir
import li.songe.gkd.util.storeFlow
import li.songe.gkd.util.subsIdToRawFlow
import li.songe.gkd.util.subsItemsFlow
import li.songe.gkd.util.updateSubscription
class MainViewModel : ViewModel() {
init {
appScope.launchTry(Dispatchers.IO) {
// 在某些机型由于未知原因创建失败
// 在此保证每次重新打开APP都能重新检测创建
initFolder()
// 每次打开页面更新记录桌面 appId
updateLauncherAppId()
val localSubsItem = SubsItem(
id = -2, order = -2, mtime = System.currentTimeMillis()
)
if (!DbSet.subsItemDao.query().first().any { s -> s.id == localSubsItem.id }) {
viewModelScope.launchTry(Dispatchers.IO) {
subsItemsFlow.debounce(1000).collect { subsItems ->
if (!subsItems.any { s -> s.id == localSubsItem.id }) {
DbSet.subsItemDao.insert(localSubsItem)
}
}
}
viewModelScope.launchTry(Dispatchers.IO) {
subsIdToRawFlow.debounce(1000).collect { subsIdToRaw ->
if (!subsIdToRaw.containsKey(localSubsItem.id)) {
updateSubscription(
RawSubscription(
id = localSubsItem.id,
name = "本地订阅",
version = 0
)
)
}
}
}
viewModelScope.launchTry(Dispatchers.IO) {
// 每次进入删除缓存
@ -40,14 +67,8 @@ class MainViewModel : ViewModel() {
}
}
viewModelScope.launchTry(Dispatchers.IO) {
// 在某些机型由于未知原因创建失败
// 在此保证每次重新打开APP都能重新检测创建
initFolder()
}
if (storeFlow.value.autoCheckAppUpdate) {
appScope.launch {
viewModelScope.launch {
try {
checkUpdate()
} catch (e: Exception) {

View File

@ -254,34 +254,7 @@ data class RawSubscription(
(apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) }
}
val allSelectorStrings by lazy {
rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten()
}
val allSelector by lazy {
allSelectorStrings.map { s -> Selector.parseOrNull(s) }
}
val unknownPropertyNames by lazy {
allSelector.filterNotNull().flatMap { r -> r.propertyNames.toList() }.distinct()
.filterNot { n -> allowPropertyNames.contains(n) }
}
val errorDesc by lazy {
allSelector.forEachIndexed { i, s ->
if (s == null) {
return@lazy "非法选择器:${allSelectorStrings[i]}"
}
}
unknownPropertyNames.forEach { n ->
return@lazy "非法属性名:${n}"
}
if (rules.any { r -> r.position?.isValid == false }) {
return@lazy "非法位置"
}
null
}
val errorDesc by lazy { getErrorDesc() }
val valid by lazy { errorDesc == null }
@ -349,31 +322,7 @@ data class RawSubscription(
override val excludeVersionCodes: List<Long>?,
) : RawGroupProps, RawAppRuleProps {
val allSelectorStrings by lazy {
rules.map { r -> (r.matches ?: emptyList()) + (r.excludeMatches ?: emptyList()) }
.flatten()
}
val allSelector by lazy {
allSelectorStrings.map { s -> Selector.parseOrNull(s) }
}
val unknownPropertyNames by lazy {
allSelector.filterNotNull().flatMap { r -> r.propertyNames.toList() }.distinct()
.filterNot { n -> allowPropertyNames.contains(n) }
}
val errorDesc by lazy {
allSelector.forEachIndexed { i, s ->
if (s == null) {
return@lazy "非法选择器:${allSelectorStrings[i]}"
}
}
unknownPropertyNames.forEach { n ->
return@lazy "非法属性名:${n}"
}
null
}
val errorDesc by lazy { getErrorDesc() }
val valid by lazy { errorDesc == null }
@ -419,6 +368,36 @@ data class RawSubscription(
companion object {
private fun RawGroupProps.getErrorDesc(): String? {
val allSelectorStrings =
rules.map { r -> (r.matches ?: emptyList()) + (r.excludeMatches ?: emptyList()) }
.flatten()
val allSelector = allSelectorStrings.map { s -> Selector.parseOrNull(s) }
allSelector.forEachIndexed { i, s ->
if (s == null) {
return "非法选择器:${allSelectorStrings[i]}"
}
}
allSelector.forEach { s ->
s?.nameToTypeList?.forEach { (name, type) ->
if (!allowPropertyNames.contains(name)) {
return "非法属性名:${name}"
}
if (type != "null" && allowPropertyNames[name] != type) {
return "非法类型:${name}=$type"
}
}
}
rules.forEach { r ->
if (r.position?.isValid == false) {
return "非法位置:${r.position}"
}
}
return null
}
private val expVars = arrayOf(
"left",
"top",

View File

@ -5,6 +5,7 @@ import android.graphics.Rect
import android.view.accessibility.AccessibilityNodeInfo
import li.songe.selector.Selector
import li.songe.selector.Transform
import li.songe.selector.data.PrimitiveValue
val AccessibilityService.safeActiveWindow: AccessibilityNodeInfo?
get() = try {
@ -128,35 +129,37 @@ val getChildren: (AccessibilityNodeInfo) -> Sequence<AccessibilityNodeInfo> = {
}
}
val allowPropertyNames = setOf(
"id",
"vid",
val allowPropertyNames by lazy {
mapOf(
"id" to PrimitiveValue.StringValue.type,
"vid" to PrimitiveValue.StringValue.type,
"name",
"text",
"text.length",
"desc",
"desc.length",
"name" to PrimitiveValue.StringValue.type,
"text" to PrimitiveValue.StringValue.type,
"text.length" to PrimitiveValue.IntValue.type,
"desc" to PrimitiveValue.StringValue.type,
"desc.length" to PrimitiveValue.IntValue.type,
"clickable",
"focusable",
"checkable",
"checked",
"editable",
"longClickable",
"visibleToUser",
"clickable" to PrimitiveValue.BooleanValue.type,
"focusable" to PrimitiveValue.BooleanValue.type,
"checkable" to PrimitiveValue.BooleanValue.type,
"checked" to PrimitiveValue.BooleanValue.type,
"editable" to PrimitiveValue.BooleanValue.type,
"longClickable" to PrimitiveValue.BooleanValue.type,
"visibleToUser" to PrimitiveValue.BooleanValue.type,
"left",
"top",
"right",
"bottom",
"width",
"height",
"left" to PrimitiveValue.IntValue.type,
"top" to PrimitiveValue.IntValue.type,
"right" to PrimitiveValue.IntValue.type,
"bottom" to PrimitiveValue.IntValue.type,
"width" to PrimitiveValue.IntValue.type,
"height" to PrimitiveValue.IntValue.type,
"index",
"depth",
"childCount"
"index" to PrimitiveValue.IntValue.type,
"depth" to PrimitiveValue.IntValue.type,
"childCount" to PrimitiveValue.IntValue.type,
)
}
private val getAttr: (AccessibilityNodeInfo, String) -> Any? = { node, name ->
when (name) {

View File

@ -251,14 +251,6 @@ fun initSubsState() {
subscriptions.forEach { s ->
newMap[s.id] = s
}
if (newMap[-2] == null) {
newMap[-2] = RawSubscription(
id = -2,
name = "本地订阅",
version = 0,
author = "gkd",
)
}
subsIdToRawFlow.value = newMap.toImmutableMap()
}
}

View File

@ -3,7 +3,7 @@ package li.songe.selector
import kotlin.js.JsExport
@JsExport
@Suppress("UNUSED")
@Suppress("UNUSED", "UNCHECKED_CAST")
class MultiplatformSelector private constructor(
internal val selector: Selector,
) {
@ -17,15 +17,12 @@ class MultiplatformSelector private constructor(
val qfTextValue = selector.qfTextValue
val canQf = selector.canQf
val isMatchRoot = selector.isMatchRoot
fun checkType(getType: (String) -> String): Boolean {
return selector.checkType(getType)
}
val nameToTypeList = selector.nameToTypeList
fun <T : Any> match(node: T, transform: MultiplatformTransform<T>): T? {
return selector.match(node, transform.transform)
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> matchTrack(node: T, transform: MultiplatformTransform<T>): Array<T>? {
return selector.matchTracks(node, transform.transform)?.toTypedArray<Any?>() as Array<T>?
}

View File

@ -99,21 +99,11 @@ class Selector internal constructor(private val propertyWrapper: PropertyWrapper
binaryExpressions.map { e -> e.name }.distinct().toTypedArray()
}
fun checkType(getType: (String) -> String): Boolean {
binaryExpressions.forEach { e ->
if (e.value.value != null) {
if (!(when (getType(e.name)) {
"boolean" -> e.value is PrimitiveValue.BooleanValue
"int" -> e.value is PrimitiveValue.IntValue
"string" -> e.value is PrimitiveValue.StringValue
else -> false
})
) return false
}
}
return true
}
// [name,type][]
val nameToTypeList = run {
binaryExpressions.map { e -> arrayOf(e.name, e.value.type) }.distinct().toTypedArray()
}
val canCacheIndex =
connectKeys.contains(ConnectOperator.BeforeBrother.key) || connectKeys.contains(

View File

@ -6,12 +6,12 @@ data class BinaryExpression(
val name: String,
val operator: CompareOperator,
val value: PrimitiveValue
) :
Expression() {
) : Expression() {
override fun <T> match(node: T, transform: Transform<T>) =
operator.compare(transform.getAttr(node, name), value.value)
override val binaryExpressions = listOf(this)
override val binaryExpressions
get() = arrayOf(this)
override fun toString() = "${name}${operator.key}${value}"
}

View File

@ -11,7 +11,7 @@ sealed class CompareOperator(val key: String) {
Equal,
NotEqual,
Start, NotStart, Include, NotInclude, End, NotEnd, Less, LessEqual, More, MoreEqual
).sortedBy { -it.key.length }
).sortedBy { -it.key.length }.toTypedArray()
}
// example

View File

@ -3,7 +3,7 @@ package li.songe.selector.data
import li.songe.selector.Transform
sealed class Expression {
abstract fun <T> match(node: T, transform: Transform<T>): Boolean
internal abstract fun <T> match(node: T, transform: Transform<T>): Boolean
abstract val binaryExpressions: List<BinaryExpression>
abstract val binaryExpressions: Array<BinaryExpression>
}

View File

@ -11,7 +11,8 @@ data class LogicalExpression(
return operator.compare(node, transform, left, right)
}
override val binaryExpressions = left.binaryExpressions + right.binaryExpressions
override val binaryExpressions
get() = left.binaryExpressions + right.binaryExpressions
override fun toString(): String {
val leftStr = if (left is LogicalExpression && left.operator != operator) {

View File

@ -1,19 +1,31 @@
package li.songe.selector.data
sealed class PrimitiveValue(open val value: Any?) {
data object NullValue : PrimitiveValue(null) {
sealed class PrimitiveValue(open val value: Any?, open val type: String) {
data object NullValue : PrimitiveValue(null, "null") {
override fun toString() = "null"
}
data class BooleanValue(override val value: Boolean) : PrimitiveValue(value) {
data class BooleanValue(override val value: Boolean) : PrimitiveValue(value, type) {
override fun toString() = value.toString()
companion object {
const val type = "boolean"
}
}
data class IntValue(override val value: Int) : PrimitiveValue(value) {
data class IntValue(override val value: Int) : PrimitiveValue(value, type) {
override fun toString() = value.toString()
companion object {
const val type = "int"
}
}
data class StringValue(override val value: String) : PrimitiveValue(value, type) {
companion object {
const val type = "string"
}
data class StringValue(override val value: String) : PrimitiveValue(value) {
override fun toString(): String {
val wrapChar = '"'
val sb = StringBuilder(value.length + 2)

View File

@ -13,7 +13,8 @@ data class PropertySegment(
) {
private val matchAnyName = name.isBlank() || name == "*"
val binaryExpressions = expressions.map { e -> e.binaryExpressions }.flatten()
val binaryExpressions
get() = expressions.flatMap { it.binaryExpressions.toList() }.toTypedArray()
override fun toString(): String {
val matchTag = if (tracked) "@" else ""