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

View File

@ -3,10 +3,12 @@ package li.songe.gkd
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import li.songe.gkd.data.RawSubscription
import li.songe.gkd.data.SubsItem import li.songe.gkd.data.SubsItem
import li.songe.gkd.db.DbSet import li.songe.gkd.db.DbSet
import li.songe.gkd.service.updateLauncherAppId
import li.songe.gkd.util.authActionFlow import li.songe.gkd.util.authActionFlow
import li.songe.gkd.util.checkUpdate import li.songe.gkd.util.checkUpdate
import li.songe.gkd.util.initFolder 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.newVersionApkDir
import li.songe.gkd.util.snapshotZipDir import li.songe.gkd.util.snapshotZipDir
import li.songe.gkd.util.storeFlow 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() { class MainViewModel : ViewModel() {
init { init {
appScope.launchTry(Dispatchers.IO) { // 在某些机型由于未知原因创建失败
// 在此保证每次重新打开APP都能重新检测创建
initFolder()
// 每次打开页面更新记录桌面 appId
updateLauncherAppId()
val localSubsItem = SubsItem( val localSubsItem = SubsItem(
id = -2, order = -2, mtime = System.currentTimeMillis() 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) 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) { viewModelScope.launchTry(Dispatchers.IO) {
// 每次进入删除缓存 // 每次进入删除缓存
@ -40,14 +67,8 @@ class MainViewModel : ViewModel() {
} }
} }
viewModelScope.launchTry(Dispatchers.IO) {
// 在某些机型由于未知原因创建失败
// 在此保证每次重新打开APP都能重新检测创建
initFolder()
}
if (storeFlow.value.autoCheckAppUpdate) { if (storeFlow.value.autoCheckAppUpdate) {
appScope.launch { viewModelScope.launch {
try { try {
checkUpdate() checkUpdate()
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -254,34 +254,7 @@ data class RawSubscription(
(apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) } (apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) }
} }
val allSelectorStrings by lazy { val errorDesc by lazy { getErrorDesc() }
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 valid by lazy { errorDesc == null } val valid by lazy { errorDesc == null }
@ -349,31 +322,7 @@ data class RawSubscription(
override val excludeVersionCodes: List<Long>?, override val excludeVersionCodes: List<Long>?,
) : RawGroupProps, RawAppRuleProps { ) : RawGroupProps, RawAppRuleProps {
val allSelectorStrings by lazy { val errorDesc by lazy { getErrorDesc() }
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 valid by lazy { errorDesc == null } val valid by lazy { errorDesc == null }
@ -419,6 +368,36 @@ data class RawSubscription(
companion object { 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( private val expVars = arrayOf(
"left", "left",
"top", "top",

View File

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

View File

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

View File

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

View File

@ -6,12 +6,12 @@ data class BinaryExpression(
val name: String, val name: String,
val operator: CompareOperator, val operator: CompareOperator,
val value: PrimitiveValue val value: PrimitiveValue
) : ) : Expression() {
Expression() {
override fun <T> match(node: T, transform: Transform<T>) = override fun <T> match(node: T, transform: Transform<T>) =
operator.compare(transform.getAttr(node, name), value.value) 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}" override fun toString() = "${name}${operator.key}${value}"
} }

View File

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

View File

@ -3,7 +3,7 @@ package li.songe.selector.data
import li.songe.selector.Transform import li.songe.selector.Transform
sealed class Expression { 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) 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 { override fun toString(): String {
val leftStr = if (left is LogicalExpression && left.operator != operator) { val leftStr = if (left is LogicalExpression && left.operator != operator) {

View File

@ -1,19 +1,31 @@
package li.songe.selector.data package li.songe.selector.data
sealed class PrimitiveValue(open val value: Any?) { sealed class PrimitiveValue(open val value: Any?, open val type: String) {
data object NullValue : PrimitiveValue(null) { data object NullValue : PrimitiveValue(null, "null") {
override fun toString() = "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() 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() 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 { override fun toString(): String {
val wrapChar = '"' val wrapChar = '"'
val sb = StringBuilder(value.length + 2) val sb = StringBuilder(value.length + 2)

View File

@ -13,7 +13,8 @@ data class PropertySegment(
) { ) {
private val matchAnyName = name.isBlank() || name == "*" 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 { override fun toString(): String {
val matchTag = if (tracked) "@" else "" val matchTag = if (tracked) "@" else ""