mixin
This commit is contained in:
@@ -15,6 +15,7 @@ data class CaptureProfileStep(
|
||||
val path: String,
|
||||
val fileName: String,
|
||||
var postAction: suspend (CaptureResult) -> Unit = {},
|
||||
val uploadType: String? = null,
|
||||
)
|
||||
|
||||
data class CaptureResult(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.bluemangoo.indpjsk.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
data class AppEntry(
|
||||
@@ -16,10 +15,6 @@ object InstalledApps {
|
||||
val pm = context.packageManager
|
||||
return pm.getInstalledApplications(0)
|
||||
.asSequence()
|
||||
.filter { info ->
|
||||
(info.flags and ApplicationInfo.FLAG_SYSTEM) == 0 ||
|
||||
(info.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
|
||||
}
|
||||
.mapNotNull { info ->
|
||||
runCatching {
|
||||
AppEntry(
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package net.bluemangoo.indpjsk.data
|
||||
|
||||
import net.bluemangoo.indpjsk.upload.UploadMixin
|
||||
import net.bluemangoo.indpjsk.upload.UploadProfileCustom
|
||||
import net.bluemangoo.indpjsk.upload.UploadStore
|
||||
import net.bluemangoo.indpjsk.upload.Uploader
|
||||
|
||||
object MixinProfiles {
|
||||
|
||||
fun merged(): List<CaptureProfile> {
|
||||
val accs = collect()
|
||||
if (accs.isEmpty()) return PredefinedProfiles.all
|
||||
|
||||
val builtIns = PredefinedProfiles.all
|
||||
val builtInNames = builtIns.map { it.name }.toSet()
|
||||
val usedProfileIds = builtIns.map { it.id }.toMutableSet()
|
||||
|
||||
val mergedBuiltIns = builtIns.map { base ->
|
||||
val acc = accs[base.name] ?: return@map base
|
||||
mergeInto(base, acc)
|
||||
}
|
||||
|
||||
val mixinOnly = accs.values
|
||||
.filter { it.name !in builtInNames }
|
||||
.mapNotNull { acc ->
|
||||
toProfile(acc, uniqueKey(usedProfileIds, acc.name, sanitize = true))
|
||||
}
|
||||
|
||||
return mergedBuiltIns + mixinOnly
|
||||
}
|
||||
|
||||
fun validate(mixin: UploadMixin): String? {
|
||||
for ((name, steps) in mixin.profiles) {
|
||||
if (steps.isEmpty()) {
|
||||
return "配置「${name}」没有 steps"
|
||||
}
|
||||
for ((name1, hosts, target) in steps) {
|
||||
if (target !in PredefinedProfiles.stepTargets &&
|
||||
runCatching { Regex(target) }.isFailure
|
||||
) {
|
||||
return "步骤「${name1}」的 target 既不是内置类型,也不是合法的路径正则"
|
||||
}
|
||||
if (hosts.isEmpty()) {
|
||||
return "步骤「${name1}」的 hosts 不能为空"
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun collect(): LinkedHashMap<String, ProfileAcc> {
|
||||
val profiles = linkedMapOf<String, ProfileAcc>()
|
||||
|
||||
for ((_, profile) in UploadStore.load()) {
|
||||
val custom = profile as? UploadProfileCustom ?: continue
|
||||
val mixin = custom.mixin ?: continue
|
||||
for ((name, steps) in mixin.profiles) {
|
||||
val key = canonicalName(name)
|
||||
if (key.isBlank()) continue
|
||||
val profileAcc = profiles.getOrPut(key) { ProfileAcc(key) }
|
||||
for ((name1, hosts, target1) in steps) {
|
||||
val target = resolveTarget(target1) ?: continue
|
||||
val stepKey = name1.trim().lowercase()
|
||||
if (stepKey.isEmpty()) continue
|
||||
val stepAcc = profileAcc.steps.getOrPut(stepKey) {
|
||||
StepAcc(name1.trim(), target)
|
||||
}
|
||||
for (host in expandHosts(hosts)) {
|
||||
if (stepAcc.hosts.none { it.equals(host, ignoreCase = true) }) {
|
||||
stepAcc.hosts.add(host)
|
||||
}
|
||||
}
|
||||
stepAcc.uploadProfileIds.add(custom.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return profiles
|
||||
}
|
||||
|
||||
private fun mergeInto(base: CaptureProfile, acc: ProfileAcc): CaptureProfile {
|
||||
val builtInStepKeys = base.steps.map { it.name.trim().lowercase() }.toSet()
|
||||
val usedFileNames = base.steps.map { it.fileName }.toMutableSet()
|
||||
val usedIds = base.steps.map { it.id }.toMutableSet()
|
||||
|
||||
val steps = base.steps.map { step ->
|
||||
val extra = acc.steps[step.name.trim().lowercase()] ?: return@map step
|
||||
step.copy(hosts = (step.hosts + extra.hosts).distinctBy { it.lowercase() })
|
||||
} + acc.steps.values
|
||||
.filter { it.name.trim().lowercase() !in builtInStepKeys }
|
||||
.mapNotNull { buildStep(it, usedFileNames, usedIds) }
|
||||
|
||||
return base.copy(steps = steps)
|
||||
}
|
||||
|
||||
private fun toProfile(acc: ProfileAcc, id: String): CaptureProfile? {
|
||||
val usedFileNames = mutableSetOf<String>()
|
||||
val usedIds = mutableSetOf<String>()
|
||||
val steps = acc.steps.values.mapNotNull { buildStep(it, usedFileNames, usedIds) }
|
||||
if (steps.isEmpty()) return null
|
||||
return CaptureProfile(id = id, name = acc.name, steps = steps)
|
||||
}
|
||||
|
||||
private fun buildStep(
|
||||
step: StepAcc,
|
||||
usedFileNames: MutableSet<String>,
|
||||
usedIds: MutableSet<String>,
|
||||
): CaptureProfileStep? {
|
||||
if (step.hosts.isEmpty()) return null
|
||||
val uploadIds = step.uploadProfileIds.toSet()
|
||||
return CaptureProfileStep(
|
||||
id = uniqueKey(usedIds, step.name, sanitize = false),
|
||||
name = step.name,
|
||||
hosts = step.hosts.toList(),
|
||||
path = step.target.path,
|
||||
fileName = uniqueKey(usedFileNames, step.name, sanitize = true),
|
||||
postAction = { result -> Uploader.tryUpload(result, uploadIds) },
|
||||
uploadType = step.target.uploadType,
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveTarget(raw: String): PredefinedProfiles.StepTarget? {
|
||||
PredefinedProfiles.stepTargets[raw]?.let { return it }
|
||||
if (raw.isBlank()) return null
|
||||
return runCatching { Regex(raw) }
|
||||
.map { PredefinedProfiles.StepTarget(path = raw, uploadType = raw) }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
private fun expandHosts(hosts: List<String>): List<String> =
|
||||
hosts.flatMap { host ->
|
||||
PredefinedProfiles.hostAliases[host.trim().lowercase()] ?: listOf(host)
|
||||
}.map { it.trim() }.filter { it.isNotEmpty() }
|
||||
|
||||
private fun canonicalName(name: String): String =
|
||||
PredefinedProfiles.all
|
||||
.firstOrNull {
|
||||
it.name.equals(name, ignoreCase = true) || it.id.equals(name, ignoreCase = true)
|
||||
}
|
||||
?.name
|
||||
?: name
|
||||
|
||||
private fun uniqueKey(used: MutableSet<String>, base: String, sanitize: Boolean): String {
|
||||
val clean = if (sanitize) {
|
||||
base.filter { it.isLetterOrDigit() || it == '_' || it == '-' }.ifEmpty { "step" }
|
||||
} else {
|
||||
base.ifBlank { "step" }
|
||||
}
|
||||
var candidate = clean
|
||||
var index = 2
|
||||
while (!used.add(candidate)) {
|
||||
candidate = "${clean}_$index"
|
||||
index++
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
private class ProfileAcc(val name: String) {
|
||||
val steps = linkedMapOf<String, StepAcc>()
|
||||
}
|
||||
|
||||
private class StepAcc(
|
||||
val name: String,
|
||||
val target: PredefinedProfiles.StepTarget,
|
||||
) {
|
||||
val hosts = mutableListOf<String>()
|
||||
val uploadProfileIds = linkedSetOf<String>()
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,25 @@ object PredefinedProfiles {
|
||||
private const val SUITE_PATH = "/api/suite/user/(\\d+)(\\?isLogin=true)?$"
|
||||
private const val MYSEKAI_PATH =
|
||||
"/api/user/(\\d+)/mysekai\\?isForceAllReloadOnlyMysekai=True"
|
||||
private const val MYSEKAI_BIRTHDAY_PATH =
|
||||
"/api/user/(\\d+)/mysekai/birthday-party/(\\d+)/delivery"
|
||||
|
||||
data class StepTarget(val path: String, val uploadType: String)
|
||||
|
||||
val stepTargets: Map<String, StepTarget> = mapOf(
|
||||
"suite" to StepTarget(SUITE_PATH, "suite"),
|
||||
"mysekai" to StepTarget(MYSEKAI_PATH, "mysekai"),
|
||||
"birthday" to StepTarget(MYSEKAI_BIRTHDAY_PATH, "birthday"),
|
||||
)
|
||||
|
||||
val hostAliases: Map<String, List<String>>
|
||||
get() = mapOf(
|
||||
"cn" to CN_HOSTS,
|
||||
"jp" to JP_HOSTS,
|
||||
"en" to EN_HOSTS,
|
||||
"tw" to TW_HOSTS,
|
||||
"kr" to KR_HOSTS
|
||||
)
|
||||
|
||||
private fun suiteStep(hosts: List<String>) = CaptureProfileStep(
|
||||
id = "suite",
|
||||
@@ -33,7 +52,7 @@ object PredefinedProfiles {
|
||||
id = "mysekai_birthday",
|
||||
name = "烤森生日双叶",
|
||||
hosts = hosts,
|
||||
path = "/api/user/(\\d+)/mysekai/birthday-party/(\\d+)/delivery",
|
||||
path = MYSEKAI_BIRTHDAY_PATH,
|
||||
fileName = "mysekai_birthday",
|
||||
postAction = { result ->
|
||||
Uploader.tryUpload(result)
|
||||
@@ -108,5 +127,6 @@ object PredefinedProfiles {
|
||||
),
|
||||
)
|
||||
|
||||
fun byId(id: String): CaptureProfile? = all.firstOrNull { it.id == id }
|
||||
fun byId(id: String): CaptureProfile? =
|
||||
MixinProfiles.merged().firstOrNull { it.id == id }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user