mirror of
https://github.com/MetaCubeX/ClashMetaForAndroid.git
synced 2026-06-11 21:01:06 +08:00
feat(profile): support age secret keys (#764)
This commit is contained in:
parent
7f63b750f2
commit
a69dd69aa7
@ -47,7 +47,7 @@ class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
|
||||
val intervalMs = java.util.concurrent.TimeUnit.MINUTES.toMillis(updateInterval)
|
||||
|
||||
create(type, name).also {
|
||||
patch(it, name, url, intervalMs)
|
||||
patch(it, name, url, intervalMs, null)
|
||||
}
|
||||
}
|
||||
startActivity(PropertiesActivity::class.intent.setUUID(uuid))
|
||||
@ -103,4 +103,4 @@ class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
|
||||
@Suppress("DEPRECATION")
|
||||
overridePendingTransition(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
|
||||
|
||||
if (!canceled && profile != original) {
|
||||
withProfile {
|
||||
patch(profile.uuid, profile.name, profile.source, profile.interval)
|
||||
patch(profile.uuid, profile.name, profile.source, profile.interval, profile.ageSecretKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,7 +92,7 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
|
||||
try {
|
||||
withProcessing { updateStatus ->
|
||||
withProfile {
|
||||
patch(profile.uuid, profile.name, profile.source, profile.interval)
|
||||
patch(profile.uuid, profile.name, profile.source, profile.interval, profile.ageSecretKey)
|
||||
|
||||
coroutineScope {
|
||||
commit(profile.uuid) {
|
||||
|
||||
@ -226,9 +226,9 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeLoad(JNIEnv *env, jobject t
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_nativeFetchAndValid(JNIEnv *env, jobject thiz,
|
||||
jobject callback,
|
||||
jstring path,
|
||||
jstring url, jboolean force) {
|
||||
jobject callback,
|
||||
jstring path,
|
||||
jstring url, jboolean force) {
|
||||
TRACE_METHOD();
|
||||
|
||||
jobject _completable = new_global(callback);
|
||||
@ -238,6 +238,21 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeFetchAndValid(JNIEnv *env,
|
||||
fetchAndValid(_completable, _path, _url, force);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_nativeSetAgeSecretKey(JNIEnv *env, jobject thiz,
|
||||
jstring key) {
|
||||
TRACE_METHOD();
|
||||
|
||||
if (key == NULL) {
|
||||
setAgeSecretKey(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
scoped_string _key = get_string(key);
|
||||
|
||||
setAgeSecretKey(_key);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_github_kr328_clash_core_bridge_Bridge_nativeQueryProviders(JNIEnv *env, jobject thiz) {
|
||||
TRACE_METHOD();
|
||||
@ -526,4 +541,4 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeCoreVersion(JNIEnv *env, jo
|
||||
char* Version = make_String(GIT_VERSION);
|
||||
|
||||
return new_string(Version);
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,4 +59,15 @@ func writeOverride(slot C.int, content C.c_string) {
|
||||
//export clearOverride
|
||||
func clearOverride(slot C.int) {
|
||||
config.ClearOverride(config.OverrideSlot(slot))
|
||||
}
|
||||
}
|
||||
|
||||
//export setAgeSecretKey
|
||||
func setAgeSecretKey(key C.c_string) {
|
||||
if key == nil {
|
||||
config.SetGlobalSecretKeys()
|
||||
return
|
||||
}
|
||||
|
||||
k := C.GoString(key)
|
||||
config.SetGlobalSecretKeys(k)
|
||||
}
|
||||
|
||||
7
core/src/main/golang/native/config/age.go
Normal file
7
core/src/main/golang/native/config/age.go
Normal file
@ -0,0 +1,7 @@
|
||||
package config
|
||||
|
||||
import "github.com/metacubex/mihomo/component/age"
|
||||
|
||||
func SetGlobalSecretKeys(secretKeys ...string) {
|
||||
age.SetGlobalSecretKeys(secretKeys...)
|
||||
}
|
||||
@ -225,4 +225,8 @@ object Clash {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun setAgeSecretKey(key: String?) {
|
||||
Bridge.nativeSetAgeSecretKey(key)
|
||||
}
|
||||
}
|
||||
@ -50,6 +50,8 @@ object Bridge {
|
||||
external fun nativeSubscribeLogcat(callback: LogcatInterface)
|
||||
external fun nativeCoreVersion(): String
|
||||
|
||||
external fun nativeSetAgeSecretKey(key: String?)
|
||||
|
||||
private external fun nativeInit(home: String, versionName: String, sdkVersion: Int)
|
||||
|
||||
init {
|
||||
|
||||
@ -121,6 +121,23 @@ class PropertiesDesign(context: Context) : Design<PropertiesDesign.Request>(cont
|
||||
}
|
||||
}
|
||||
|
||||
fun inputAgeSecretKey() {
|
||||
launch {
|
||||
val ageSecretKey = context.requestModelTextInput(
|
||||
initial = profile.ageSecretKey ?: "",
|
||||
title = context.getText(R.string.age_secret_key),
|
||||
hint = context.getText(R.string.age_secret_key_hint),
|
||||
error = context.getText(R.string.age_secret_key_error),
|
||||
validator = ValidatorAgeSecretKey
|
||||
)
|
||||
|
||||
val newKey = ageSecretKey.ifBlank { null }
|
||||
if (newKey != profile.ageSecretKey) {
|
||||
profile = profile.copy(ageSecretKey = newKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inputInterval() {
|
||||
launch {
|
||||
var minutes = TimeUnit.MILLISECONDS.toMinutes(profile.interval)
|
||||
|
||||
@ -22,4 +22,8 @@ val ValidatorHttpUrl: Validator = {
|
||||
|
||||
val ValidatorAutoUpdateInterval: Validator = {
|
||||
it.isEmpty() || (it.toLongOrNull() ?: 0) >= 15
|
||||
}
|
||||
|
||||
val ValidatorAgeSecretKey: Validator = {
|
||||
it.isEmpty() || it.startsWith("AGE-SECRET-KEY-", ignoreCase = true)
|
||||
}
|
||||
3
design/src/main/res/drawable/ic_baseline_key.xml
Normal file
3
design/src/main/res/drawable/ic_baseline_key.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z"/>
|
||||
</vector>
|
||||
@ -77,6 +77,16 @@
|
||||
app:text="@{profile.source}"
|
||||
app:title="@string/url" />
|
||||
|
||||
<com.github.kr328.clash.design.view.ActionTextField
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="@dimen/properties_element_margin_vertical"
|
||||
android:onClick="@{() -> self.inputAgeSecretKey()}"
|
||||
app:icon="@drawable/ic_baseline_key"
|
||||
app:placeholder="@string/age_secret_key_hint"
|
||||
app:text="@{profile.ageSecretKey}"
|
||||
app:title="@string/age_secret_key" />
|
||||
|
||||
<com.github.kr328.clash.design.view.ActionTextField
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@ -47,6 +47,9 @@
|
||||
<string name="accept_http_content">http(s) のみを許可</string>
|
||||
<string name="at_least_15_minutes">15分以上か空白にしてください</string>
|
||||
<string name="should_not_be_blank">空白にはできません</string>
|
||||
<string name="age_secret_key">Age秘密鍵</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-…(任意)</string>
|
||||
<string name="age_secret_key_error">フォーマットが無効です。鍵は AGE-SECRET-KEY- で始まる必要があります</string>
|
||||
<string name="detail">詳細</string>
|
||||
<string name="update">更新</string>
|
||||
<string name="edit">編集</string>
|
||||
|
||||
@ -47,6 +47,9 @@
|
||||
<string name="accept_http_content">http(s) 연결만 허용</string>
|
||||
<string name="at_least_15_minutes">최소 15분 이상 또는 공백</string>
|
||||
<string name="should_not_be_blank">이 필드는 공백이 허용되지 않습니다.</string>
|
||||
<string name="age_secret_key">Age 비밀 키</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-…(선택사항)</string>
|
||||
<string name="age_secret_key_error">잘못된 형식입니다. 키는 AGE-SECRET-KEY- 로 시작해야 합니다</string>
|
||||
<string name="detail">자세히</string>
|
||||
<string name="update">업데이트</string>
|
||||
<string name="edit">편집</string>
|
||||
|
||||
@ -57,6 +57,9 @@
|
||||
<string name="accept_http_content">Принимать только http(s)</string>
|
||||
<string name="at_least_15_minutes">Не менее 15 минут или пустой</string>
|
||||
<string name="should_not_be_blank">Не должно быть пустым</string>
|
||||
<string name="age_secret_key">Секретный ключ Age</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-… (необязательно)</string>
|
||||
<string name="age_secret_key_error">Неверный формат. Ключ должен начинаться с AGE-SECRET-KEY-</string>
|
||||
<string name="detail">Детали</string>
|
||||
<string name="update">Обновить</string>
|
||||
<string name="edit">Изменить</string>
|
||||
|
||||
@ -25,6 +25,9 @@
|
||||
<string name="access_control_packages">Các gói kiểm soát truy cập</string>
|
||||
<string name="access_control_packages_summary">Định cấu hình quyền truy cập cho các ứng dụng</string>
|
||||
<string name="active_unsaved_tips">Hồ sơ cần được lưu trước khi kích hoạt</string>
|
||||
<string name="age_secret_key">Khóa bí mật Age</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-… (tùy chọn)</string>
|
||||
<string name="age_secret_key_error">Định dạng không hợp lệ. Khóa phải bắt đầu bằng AGE-SECRET-KEY-</string>
|
||||
<string name="allow_all_apps">Cho phép tất cả các ứng dụng</string>
|
||||
<string name="allow_bypass">Cho phép bỏ qua</string>
|
||||
<string name="allow_bypass_summary">Cho phép tất cả các ứng dụng bỏ qua kết nối VPN này</string>
|
||||
|
||||
@ -78,6 +78,9 @@
|
||||
<string name="update_time">更新時間</string>
|
||||
<string name="package_name">應用包名稱</string>
|
||||
<string name="install_time">安裝時間</string>
|
||||
<string name="age_secret_key">Age 私鑰</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-…(可選)</string>
|
||||
<string name="age_secret_key_error">格式無效。密鑰應以 AGE-SECRET-KEY- 開頭</string>
|
||||
<string name="feedback">反饋</string>
|
||||
<string name="github_issues">Github Issues</string>
|
||||
<string name="tips_properties"><![CDATA[僅接受 <strong>Clash 配置文件</strong>(包含<strong>代理</strong>/<strong>規則</strong>)]]></string>
|
||||
|
||||
@ -78,6 +78,9 @@
|
||||
<string name="update_time">更新時間</string>
|
||||
<string name="package_name">套件名稱</string>
|
||||
<string name="install_time">安裝時間</string>
|
||||
<string name="age_secret_key">Age 私鑰</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-…(選填)</string>
|
||||
<string name="age_secret_key_error">格式無效。金鑰應以 AGE-SECRET-KEY- 開頭</string>
|
||||
<string name="feedback">回饋</string>
|
||||
<string name="github_issues">Github Issues</string>
|
||||
<string name="tips_properties"><![CDATA[僅接受 <strong>Clash 設定檔</strong> (包含<strong>Proxy</strong> /<strong>規則</strong>)]]></string>
|
||||
|
||||
@ -78,6 +78,9 @@
|
||||
<string name="vpn_service_options">VpnService 选项</string>
|
||||
<string name="options_unavailable">选项在 Clash 运行时不可用</string>
|
||||
<string name="search">查找</string>
|
||||
<string name="age_secret_key">Age 私钥</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-…(可选)</string>
|
||||
<string name="age_secret_key_error">格式无效。密钥应以 AGE-SECRET-KEY- 开头</string>
|
||||
<string name="system_apps">系统应用</string>
|
||||
<string name="update_time">更新时间</string>
|
||||
<string name="package_name">应用包名称</string>
|
||||
|
||||
@ -62,6 +62,9 @@
|
||||
|
||||
<string name="accept_http_content">Accept only http(s)</string>
|
||||
<string name="at_least_15_minutes">At least 15 minutes or empty</string>
|
||||
<string name="age_secret_key">Age Secret Key</string>
|
||||
<string name="age_secret_key_hint">AGE-SECRET-KEY-… (optional)</string>
|
||||
<string name="age_secret_key_error">Invalid format. Key should start with AGE-SECRET-KEY-</string>
|
||||
<string name="should_not_be_blank">Should not be blank</string>
|
||||
<string name="detail">Detail</string>
|
||||
<string name="update">Update</string>
|
||||
|
||||
@ -37,7 +37,7 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(type: Profile.Type, name: String, source: String): UUID {
|
||||
override suspend fun create(type: Profile.Type, name: String, source: String, ageSecretKey: String?): UUID {
|
||||
val uuid = generateProfileUUID()
|
||||
val pending = Pending(
|
||||
uuid = uuid,
|
||||
@ -49,6 +49,7 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
total = 0,
|
||||
download = 0,
|
||||
expire = 0,
|
||||
ageSecretKey = ageSecretKey,
|
||||
)
|
||||
|
||||
PendingDao().insert(pending)
|
||||
@ -81,6 +82,7 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
total = imported.total,
|
||||
download = imported.download,
|
||||
expire = imported.expire,
|
||||
ageSecretKey = imported.ageSecretKey
|
||||
)
|
||||
|
||||
cloneImportedFiles(uuid, newUUID)
|
||||
@ -90,7 +92,7 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
return newUUID
|
||||
}
|
||||
|
||||
override suspend fun patch(uuid: UUID, name: String, source: String, interval: Long) {
|
||||
override suspend fun patch(uuid: UUID, name: String, source: String, interval: Long, ageSecretKey: String?) {
|
||||
val pending = PendingDao().queryByUUID(uuid)
|
||||
|
||||
if (pending == null) {
|
||||
@ -110,6 +112,7 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
total = 0,
|
||||
download = 0,
|
||||
expire = 0,
|
||||
ageSecretKey = ageSecretKey,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
@ -121,6 +124,7 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
total = 0,
|
||||
download = 0,
|
||||
expire = 0,
|
||||
ageSecretKey = ageSecretKey,
|
||||
)
|
||||
|
||||
PendingDao().update(newPending)
|
||||
@ -188,7 +192,8 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
download,
|
||||
total,
|
||||
expire,
|
||||
old?.createdAt ?: System.currentTimeMillis()
|
||||
old?.createdAt ?: System.currentTimeMillis(),
|
||||
ageSecretKey = old.ageSecretKey
|
||||
)
|
||||
|
||||
if (old != null) {
|
||||
@ -266,19 +271,20 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
val expire = pending?.expire ?: imported?.expire ?: return null
|
||||
|
||||
return Profile(
|
||||
uuid,
|
||||
name,
|
||||
type,
|
||||
source,
|
||||
active != null && imported?.uuid == active,
|
||||
interval,
|
||||
upload,
|
||||
download,
|
||||
total,
|
||||
expire,
|
||||
resolveUpdatedAt(uuid),
|
||||
imported != null,
|
||||
pending != null
|
||||
uuid = uuid,
|
||||
name = name,
|
||||
type = type,
|
||||
source = source,
|
||||
active = active != null && imported?.uuid == active,
|
||||
interval = interval,
|
||||
upload = upload,
|
||||
download = download,
|
||||
total = total,
|
||||
expire = expire,
|
||||
updatedAt = resolveUpdatedAt(uuid),
|
||||
imported = imported != null,
|
||||
pending = pending != null,
|
||||
ageSecretKey = if (pending != null) pending.ageSecretKey else imported?.ageSecretKey,
|
||||
)
|
||||
}
|
||||
|
||||
@ -309,4 +315,4 @@ class ProfileManager(private val context: Context) : IProfileManager,
|
||||
ProfileReceiver.scheduleNext(context, imported)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,6 @@ import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.math.BigDecimal
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@ -34,8 +33,8 @@ object ProfileProcessor {
|
||||
withContext(NonCancellable) {
|
||||
processLock.withLock {
|
||||
val snapshot = profileLock.withLock {
|
||||
val pending = PendingDao().queryByUUID(uuid)
|
||||
?: throw IllegalArgumentException("profile $uuid not found")
|
||||
val pending =
|
||||
PendingDao().queryByUUID(uuid) ?: throw IllegalArgumentException("profile $uuid not found")
|
||||
|
||||
pending.enforceFieldValid()
|
||||
|
||||
@ -48,6 +47,8 @@ object ProfileProcessor {
|
||||
pending
|
||||
}
|
||||
|
||||
Clash.setAgeSecretKey(snapshot.ageSecretKey?.takeIf { it.isNotBlank() })
|
||||
|
||||
val force = snapshot.type != Profile.Type.File
|
||||
var cb = callback
|
||||
|
||||
@ -63,10 +64,8 @@ object ProfileProcessor {
|
||||
|
||||
profileLock.withLock {
|
||||
if (PendingDao().queryByUUID(snapshot.uuid) == snapshot) {
|
||||
context.importedDir.resolve(snapshot.uuid.toString())
|
||||
.deleteRecursively()
|
||||
context.processingDir
|
||||
.copyRecursively(context.importedDir.resolve(snapshot.uuid.toString()))
|
||||
context.importedDir.resolve(snapshot.uuid.toString()).deleteRecursively()
|
||||
context.processingDir.copyRecursively(context.importedDir.resolve(snapshot.uuid.toString()))
|
||||
|
||||
val old = ImportedDao().queryByUUID(snapshot.uuid)
|
||||
var upload: Long = 0
|
||||
@ -77,11 +76,10 @@ object ProfileProcessor {
|
||||
if (snapshot?.type == Profile.Type.Url) {
|
||||
if (snapshot.source.startsWith("https://", true)) {
|
||||
val client = OkHttpClient()
|
||||
val versionName = context.packageManager.getPackageInfo(context.packageName, 0).versionName
|
||||
val request = Request.Builder()
|
||||
.url(snapshot.source)
|
||||
.header("User-Agent", "ClashMetaForAndroid/$versionName")
|
||||
.build()
|
||||
val versionName =
|
||||
context.packageManager.getPackageInfo(context.packageName, 0).versionName
|
||||
val request = Request.Builder().url(snapshot.source)
|
||||
.header("User-Agent", "ClashMetaForAndroid/$versionName").build()
|
||||
|
||||
client.newCall(request).execute().use { response ->
|
||||
val userinfo = response.headers["subscription-userinfo"]
|
||||
@ -99,7 +97,7 @@ object ProfileProcessor {
|
||||
info[0].contains("total") && info[1].isNotEmpty() -> total =
|
||||
BigDecimal(info[1].split('.').first()).longValueExact()
|
||||
|
||||
info[0].contains("expire") && info[1].isNotEmpty() -> expire =
|
||||
info[0].contains("expire") && info[1].isNotEmpty() -> expire =
|
||||
(info[1].toDouble() * 1000).toLong()
|
||||
}
|
||||
}
|
||||
@ -110,8 +108,8 @@ object ProfileProcessor {
|
||||
val intervalHours = updateIntervalHeader.toLongOrNull()
|
||||
if (intervalHours != null) {
|
||||
updateInterval = if (intervalHours > 0) {
|
||||
java.util.concurrent.TimeUnit.HOURS.toMillis(intervalHours)
|
||||
.coerceAtLeast(java.util.concurrent.TimeUnit.MINUTES.toMillis(15))
|
||||
TimeUnit.HOURS.toMillis(intervalHours)
|
||||
.coerceAtLeast(TimeUnit.MINUTES.toMillis(15))
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
@ -129,7 +127,8 @@ object ProfileProcessor {
|
||||
download,
|
||||
total,
|
||||
expire,
|
||||
old?.createdAt ?: System.currentTimeMillis()
|
||||
old?.createdAt ?: System.currentTimeMillis(),
|
||||
ageSecretKey = snapshot.ageSecretKey
|
||||
)
|
||||
if (old != null) {
|
||||
ImportedDao().update(new)
|
||||
@ -139,8 +138,7 @@ object ProfileProcessor {
|
||||
|
||||
PendingDao().remove(snapshot.uuid)
|
||||
|
||||
context.pendingDir.resolve(snapshot.uuid.toString())
|
||||
.deleteRecursively()
|
||||
context.pendingDir.resolve(snapshot.uuid.toString()).deleteRecursively()
|
||||
|
||||
context.sendProfileChanged(snapshot.uuid)
|
||||
} else if (snapshot?.type == Profile.Type.File) {
|
||||
@ -154,7 +152,8 @@ object ProfileProcessor {
|
||||
download,
|
||||
total,
|
||||
expire,
|
||||
old?.createdAt ?: System.currentTimeMillis()
|
||||
old?.createdAt ?: System.currentTimeMillis(),
|
||||
ageSecretKey = snapshot.ageSecretKey
|
||||
)
|
||||
if (old != null) {
|
||||
ImportedDao().update(new)
|
||||
@ -164,8 +163,7 @@ object ProfileProcessor {
|
||||
|
||||
PendingDao().remove(snapshot.uuid)
|
||||
|
||||
context.pendingDir.resolve(snapshot.uuid.toString())
|
||||
.deleteRecursively()
|
||||
context.pendingDir.resolve(snapshot.uuid.toString()).deleteRecursively()
|
||||
|
||||
context.sendProfileChanged(snapshot.uuid)
|
||||
}
|
||||
@ -179,8 +177,8 @@ object ProfileProcessor {
|
||||
withContext(NonCancellable) {
|
||||
processLock.withLock {
|
||||
val snapshot = profileLock.withLock {
|
||||
val imported = ImportedDao().queryByUUID(uuid)
|
||||
?: throw IllegalArgumentException("profile $uuid not found")
|
||||
val imported =
|
||||
ImportedDao().queryByUUID(uuid) ?: throw IllegalArgumentException("profile $uuid not found")
|
||||
|
||||
context.processingDir.deleteRecursively()
|
||||
context.processingDir.mkdirs()
|
||||
@ -191,6 +189,8 @@ object ProfileProcessor {
|
||||
imported
|
||||
}
|
||||
|
||||
Clash.setAgeSecretKey(snapshot.ageSecretKey?.takeIf { it.isNotBlank() })
|
||||
|
||||
var cb = callback
|
||||
|
||||
Clash.fetchAndValid(context.processingDir, snapshot.source, true) {
|
||||
@ -206,8 +206,7 @@ object ProfileProcessor {
|
||||
profileLock.withLock {
|
||||
if (ImportedDao().exists(snapshot.uuid)) {
|
||||
context.importedDir.resolve(snapshot.uuid.toString()).deleteRecursively()
|
||||
context.processingDir
|
||||
.copyRecursively(context.importedDir.resolve(snapshot.uuid.toString()))
|
||||
context.processingDir.copyRecursively(context.importedDir.resolve(snapshot.uuid.toString()))
|
||||
|
||||
context.sendProfileChanged(snapshot.uuid)
|
||||
}
|
||||
@ -261,17 +260,16 @@ object ProfileProcessor {
|
||||
val scheme = Uri.parse(source)?.scheme?.lowercase(Locale.getDefault())
|
||||
|
||||
when {
|
||||
name.isBlank() ->
|
||||
throw IllegalArgumentException("Empty name")
|
||||
name.isBlank() -> throw IllegalArgumentException("Empty name")
|
||||
|
||||
source.isEmpty() && type != Profile.Type.File ->
|
||||
throw IllegalArgumentException("Invalid url")
|
||||
source.isEmpty() && type != Profile.Type.File -> throw IllegalArgumentException("Invalid url")
|
||||
|
||||
source.isNotEmpty() && scheme != "https" && scheme != "http" && scheme != "content" ->
|
||||
throw IllegalArgumentException("Unsupported url $source")
|
||||
source.isNotEmpty() && scheme != "https" && scheme != "http" && scheme != "content" -> throw IllegalArgumentException(
|
||||
"Unsupported url $source"
|
||||
)
|
||||
|
||||
interval != 0L && TimeUnit.MILLISECONDS.toMinutes(interval) < 15 ->
|
||||
throw IllegalArgumentException("Invalid interval")
|
||||
interval != 0L && TimeUnit.MILLISECONDS.toMinutes(interval) < 15 -> throw IllegalArgumentException("Invalid interval")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -55,6 +55,8 @@ class ConfigurationModule(service: Service) : Module<ConfigurationModule.LoadExc
|
||||
val active = ImportedDao().queryByUUID(current)
|
||||
?: throw NullPointerException("No profile selected")
|
||||
|
||||
Clash.setAgeSecretKey(active.ageSecretKey?.takeIf { it.isNotBlank() })
|
||||
|
||||
Clash.load(service.importedDir.resolve(active.uuid.toString())).await()
|
||||
|
||||
val remove = SelectionDao().querySelections(active.uuid)
|
||||
@ -73,4 +75,4 @@ class ConfigurationModule(service: Service) : Module<ConfigurationModule.LoadExc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import java.lang.ref.SoftReference
|
||||
import androidx.room.Database as DB
|
||||
|
||||
@DB(
|
||||
version = 1,
|
||||
version = 2,
|
||||
entities = [Imported::class, Pending::class, Selection::class],
|
||||
exportSchema = false,
|
||||
)
|
||||
@ -45,4 +45,4 @@ abstract class Database : RoomDatabase() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,4 +19,5 @@ data class Imported(
|
||||
@ColumnInfo(name = "total") val total: Long,
|
||||
@ColumnInfo(name = "expire") val expire: Long,
|
||||
@ColumnInfo(name = "createdAt") val createdAt: Long,
|
||||
@ColumnInfo(name = "ageSecretKey") val ageSecretKey: String? = null,
|
||||
)
|
||||
@ -19,4 +19,5 @@ data class Pending(
|
||||
@ColumnInfo(name = "total") val total: Long,
|
||||
@ColumnInfo(name = "expire") val expire: Long,
|
||||
@ColumnInfo(name = "createdAt") val createdAt: Long = System.currentTimeMillis(),
|
||||
@ColumnInfo(name = "ageSecretKey") val ageSecretKey: String? = null,
|
||||
)
|
||||
@ -1,7 +1,17 @@
|
||||
package com.github.kr328.clash.service.data.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
val MIGRATIONS: Array<Migration> = arrayOf()
|
||||
private val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE imported ADD COLUMN ageSecretKey TEXT")
|
||||
database.execSQL("ALTER TABLE pending ADD COLUMN ageSecretKey TEXT")
|
||||
}
|
||||
}
|
||||
|
||||
val LEGACY_MIGRATION = ::migrationFromLegacy
|
||||
val MIGRATIONS: Array<Migration> = arrayOf(
|
||||
MIGRATION_1_2,
|
||||
)
|
||||
|
||||
val LEGACY_MIGRATION = ::migrationFromLegacy
|
||||
|
||||
@ -134,7 +134,8 @@ class Picker(private val context: Context) {
|
||||
imported.type,
|
||||
imported.source,
|
||||
imported.interval,
|
||||
0,0,0,0
|
||||
0,0,0,0,
|
||||
ageSecretKey = imported.ageSecretKey
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -22,11 +22,10 @@ data class Profile(
|
||||
var download: Long,
|
||||
val total: Long,
|
||||
val expire: Long,
|
||||
|
||||
|
||||
val updatedAt: Long,
|
||||
val imported: Boolean,
|
||||
val pending: Boolean,
|
||||
val ageSecretKey: String? = null,
|
||||
) : Parcelable {
|
||||
enum class Type {
|
||||
File, Url, External
|
||||
@ -49,4 +48,4 @@ data class Profile(
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,15 +6,15 @@ import java.util.*
|
||||
|
||||
@BinderInterface
|
||||
interface IProfileManager {
|
||||
suspend fun create(type: Profile.Type, name: String, source: String = ""): UUID
|
||||
suspend fun create(type: Profile.Type, name: String, source: String = "", ageSecretKey: String? = null): UUID
|
||||
suspend fun clone(uuid: UUID): UUID
|
||||
suspend fun commit(uuid: UUID, callback: IFetchObserver? = null)
|
||||
suspend fun release(uuid: UUID)
|
||||
suspend fun delete(uuid: UUID)
|
||||
suspend fun patch(uuid: UUID, name: String, source: String, interval: Long)
|
||||
suspend fun patch(uuid: UUID, name: String, source: String, interval: Long, ageSecretKey: String?)
|
||||
suspend fun update(uuid: UUID)
|
||||
suspend fun queryByUUID(uuid: UUID): Profile?
|
||||
suspend fun queryAll(): List<Profile>
|
||||
suspend fun queryActive(): Profile?
|
||||
suspend fun setActive(profile: Profile)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user