owenclave / app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt

github - dev - back to tree - upstream
/****************************************************************************** * * * Copyright (C) 2021 by nekohasekai * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ******************************************************************************/ package io.nekohasekai.sagernet.database import android.content.Context import android.content.Intent import androidx.room.* import com.esotericsoftware.kryo.io.ByteBufferInput import com.esotericsoftware.kryo.io.ByteBufferOutput import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.aidl.TrafficStats import io.nekohasekai.sagernet.fmt.AbstractBean import io.nekohasekai.sagernet.fmt.KryoConverters import io.nekohasekai.sagernet.fmt.Serializable import io.nekohasekai.sagernet.fmt.anytls.AnyTLSBean import io.nekohasekai.sagernet.fmt.anytls.toUri import io.nekohasekai.sagernet.fmt.buildV2RayConfig import io.nekohasekai.sagernet.fmt.http.HttpBean import io.nekohasekai.sagernet.fmt.http.toUri import io.nekohasekai.sagernet.fmt.http3.Http3Bean import io.nekohasekai.sagernet.fmt.http3.toUri import io.nekohasekai.sagernet.fmt.hysteria2.Hysteria2Bean import io.nekohasekai.sagernet.fmt.hysteria2.toUri import io.nekohasekai.sagernet.fmt.internal.BalancerBean import io.nekohasekai.sagernet.fmt.internal.ChainBean import io.nekohasekai.sagernet.fmt.internal.ConfigBean import io.nekohasekai.sagernet.fmt.juicity.JuicityBean import io.nekohasekai.sagernet.fmt.juicity.toUri import io.nekohasekai.sagernet.fmt.mieru.MieruBean import io.nekohasekai.sagernet.fmt.mieru.toUri import io.nekohasekai.sagernet.fmt.naive.NaiveBean import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig import io.nekohasekai.sagernet.fmt.naive.toUri import io.nekohasekai.sagernet.fmt.shadowquic.ShadowQUICBean import io.nekohasekai.sagernet.fmt.shadowquic.buildShadowQUICConfig import io.nekohasekai.sagernet.fmt.shadowquic.toUri import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean import io.nekohasekai.sagernet.fmt.shadowsocks.toUri import io.nekohasekai.sagernet.fmt.shadowsocksr.ShadowsocksRBean import io.nekohasekai.sagernet.fmt.shadowsocksr.toUri import io.nekohasekai.sagernet.fmt.socks.SOCKSBean import io.nekohasekai.sagernet.fmt.socks.toUri import io.nekohasekai.sagernet.fmt.ssh.SSHBean import io.nekohasekai.sagernet.fmt.trojan.TrojanBean import io.nekohasekai.sagernet.fmt.trojan.toUri import io.nekohasekai.sagernet.fmt.trusttunnel.TrustTunnelBean import io.nekohasekai.sagernet.fmt.snell.SnellBean import io.nekohasekai.sagernet.fmt.olcrtc.OLCRTCBean import io.nekohasekai.sagernet.fmt.olcrtc.toUri import io.nekohasekai.sagernet.ui.profile.SnellSettingsActivity import io.nekohasekai.sagernet.fmt.trusttunnel.toUri import io.nekohasekai.sagernet.fmt.tuic5.Tuic5Bean import io.nekohasekai.sagernet.fmt.tuic5.toUri import io.nekohasekai.sagernet.fmt.v2ray.VLESSBean import io.nekohasekai.sagernet.fmt.v2ray.VMessBean import io.nekohasekai.sagernet.fmt.v2ray.toUri import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean import io.nekohasekai.sagernet.ktx.app import io.nekohasekai.sagernet.ktx.applyDefaultValues import io.nekohasekai.sagernet.ui.profile.* @Entity( tableName = "proxy_entities", indices = [Index("groupId", name = "groupId")] ) data class ProxyEntity( @PrimaryKey(autoGenerate = true) var id: Long = 0L, var groupId: Long = 0L, var type: Int = 0, var userOrder: Long = 0L, var tx: Long = 0L, var rx: Long = 0L, var status: Int = 0, var ping: Int = 0, var uuid: String = "", var error: String? = null, var socksBean: SOCKSBean? = null, var httpBean: HttpBean? = null, var ssBean: ShadowsocksBean? = null, var ssrBean: ShadowsocksRBean? = null, var vmessBean: VMessBean? = null, var vlessBean: VLESSBean? = null, var trojanBean: TrojanBean? = null, var naiveBean: NaiveBean? = null, var hysteria2Bean: Hysteria2Bean? = null, var mieruBean: MieruBean? = null, var tuic5Bean: Tuic5Bean? = null, var sshBean: SSHBean? = null, var wgBean: WireGuardBean? = null, var juicityBean: JuicityBean? = null, var http3Bean: Http3Bean? = null, var anytlsBean: AnyTLSBean? = null, var shadowquicBean: ShadowQUICBean? = null, var trustTunnelBean: TrustTunnelBean? = null, var snellBean: SnellBean? = null, var olcrtcBean: OLCRTCBean? = null, var configBean: ConfigBean? = null, var chainBean: ChainBean? = null, var balancerBean: BalancerBean? = null, /** Icon index for the profile card. -1 = auto (random/derived from name). */ @androidx.room.ColumnInfo(defaultValue = "-1") var iconIndex: Int = -1 ) : Serializable() { companion object { const val TYPE_SOCKS = 0 const val TYPE_HTTP = 1 const val TYPE_SS = 2 const val TYPE_SSR = 3 const val TYPE_VMESS = 4 const val TYPE_VLESS = 5 const val TYPE_TROJAN = 6 const val TYPE_NAIVE = 9 const val TYPE_HYSTERIA2 = 21 const val TYPE_SSH = 17 const val TYPE_WG = 18 const val TYPE_MIERU = 19 const val TYPE_TUIC5 = 23 const val TYPE_JUICITY = 25 const val TYPE_HTTP3 = 26 const val TYPE_ANYTLS = 27 const val TYPE_SHADOWQUIC = 28 const val TYPE_TRUSTTUNNEL = 29 const val TYPE_SNELL = 30 const val TYPE_OLCRTC = 31 const val TYPE_CHAIN = 8 const val TYPE_BALANCER = 14 const val TYPE_CONFIG = 13 val chainName by lazy { app.getString(R.string.proxy_chain) } val configName by lazy { app.getString(R.string.custom_config) } val balancerName by lazy { app.getString(R.string.balancer) } @JvmField val CREATOR = object : CREATOR() { override fun newInstance(): ProxyEntity { return ProxyEntity() } override fun newArray(size: Int): Array { return arrayOfNulls(size) } } } @Ignore @Transient var dirty: Boolean = false @Ignore @Transient var stats: TrafficStats? = null override fun initializeDefaultValues() { } override fun serializeToBuffer(output: ByteBufferOutput) { output.writeInt(0) output.writeLong(id) output.writeLong(groupId) output.writeInt(type) output.writeLong(userOrder) output.writeLong(tx) output.writeLong(rx) output.writeInt(status) output.writeInt(ping) output.writeString(uuid) output.writeString(error) val data = KryoConverters.serialize(requireBean()) output.writeVarInt(data.size, true) output.writeBytes(data) output.writeBoolean(dirty) } override fun deserializeFromBuffer(input: ByteBufferInput) { val version = input.readInt() id = input.readLong() groupId = input.readLong() type = input.readInt() userOrder = input.readLong() tx = input.readLong() rx = input.readLong() status = input.readInt() ping = input.readInt() uuid = input.readString() error = input.readString() putByteArray(input.readBytes(input.readVarInt(true))) dirty = input.readBoolean() } fun putByteArray(byteArray: ByteArray) { when (type) { TYPE_SOCKS -> socksBean = KryoConverters.socksDeserialize(byteArray) TYPE_HTTP -> httpBean = KryoConverters.httpDeserialize(byteArray) TYPE_SS -> ssBean = KryoConverters.shadowsocksDeserialize(byteArray) TYPE_SSR -> ssrBean = KryoConverters.shadowsocksRDeserialize(byteArray) TYPE_VMESS -> vmessBean = KryoConverters.vmessDeserialize(byteArray) TYPE_VLESS -> vlessBean = KryoConverters.vlessDeserialize(byteArray) TYPE_TROJAN -> trojanBean = KryoConverters.trojanDeserialize(byteArray) TYPE_NAIVE -> naiveBean = KryoConverters.naiveDeserialize(byteArray) TYPE_HYSTERIA2 -> hysteria2Bean = KryoConverters.hysteria2Deserialize(byteArray) TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray) TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray) TYPE_MIERU -> mieruBean = KryoConverters.mieruDeserialize(byteArray) TYPE_TUIC5 -> tuic5Bean = KryoConverters.tuic5Deserialize(byteArray) TYPE_JUICITY -> juicityBean = KryoConverters.juicityDeserialize(byteArray) TYPE_HTTP3 -> http3Bean = KryoConverters.http3Deserialize(byteArray) TYPE_ANYTLS -> anytlsBean = KryoConverters.anytlsDeserialize(byteArray) TYPE_SHADOWQUIC -> shadowquicBean = KryoConverters.shadowquicDeserialize(byteArray) TYPE_TRUSTTUNNEL -> trustTunnelBean = KryoConverters.trusttunnelDeserialize(byteArray) TYPE_SNELL -> snellBean = KryoConverters.snellDeserialize(byteArray) TYPE_OLCRTC -> olcrtcBean = KryoConverters.olcrtcDeserialize(byteArray) TYPE_CONFIG -> configBean = KryoConverters.configDeserialize(byteArray) TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray) TYPE_BALANCER -> balancerBean = KryoConverters.balancerBeanDeserialize(byteArray) } } fun displayType() = when (type) { TYPE_SOCKS -> socksBean!!.protocolName() TYPE_HTTP -> httpBean!!.protocolName() TYPE_SS -> ssBean!!.protocolName() TYPE_SSR -> "ShadowsocksR" TYPE_VMESS -> "VMess" TYPE_VLESS -> "VLESS" TYPE_TROJAN -> "Trojan" TYPE_NAIVE -> "NaïveProxy" TYPE_HYSTERIA2 -> "Hysteria 2" TYPE_SSH -> "SSH" TYPE_WG -> "WireGuard" TYPE_MIERU -> "mieru" TYPE_TUIC5 -> "TUIC" TYPE_JUICITY -> "Juicity" TYPE_HTTP3 -> "HTTP/3" TYPE_ANYTLS -> "AnyTLS" TYPE_SHADOWQUIC -> "ShadowQUIC" TYPE_TRUSTTUNNEL -> "TrustTunnel" TYPE_SNELL -> snellBean!!.protocolName() TYPE_OLCRTC -> "olcrtc" TYPE_CHAIN -> chainName TYPE_CONFIG -> configName TYPE_BALANCER -> balancerName else -> "Invalid" } fun displayName() = requireBean().displayName() fun displayAddress() = requireBean().displayAddress() fun requireBean(): AbstractBean { return when (type) { TYPE_SOCKS -> socksBean TYPE_HTTP -> httpBean TYPE_SS -> ssBean TYPE_SSR -> ssrBean TYPE_VMESS -> vmessBean TYPE_VLESS -> vlessBean TYPE_TROJAN -> trojanBean TYPE_NAIVE -> naiveBean TYPE_HYSTERIA2 -> hysteria2Bean TYPE_SSH -> sshBean TYPE_WG -> wgBean TYPE_MIERU -> mieruBean TYPE_TUIC5 -> tuic5Bean TYPE_JUICITY -> juicityBean TYPE_HTTP3 -> http3Bean TYPE_ANYTLS -> anytlsBean TYPE_SHADOWQUIC -> shadowquicBean TYPE_TRUSTTUNNEL -> trustTunnelBean TYPE_SNELL -> snellBean TYPE_OLCRTC -> olcrtcBean TYPE_CONFIG -> configBean TYPE_CHAIN -> chainBean TYPE_BALANCER -> balancerBean else -> null } ?: SOCKSBean().applyDefaultValues() } fun canExportBackup(): Boolean { return when (type) { TYPE_CHAIN -> false TYPE_BALANCER -> false else -> true } } fun hasShareLink(): Boolean { return when (type) { TYPE_SSH, TYPE_WG, TYPE_SNELL, TYPE_OLCRTC -> false TYPE_CONFIG, TYPE_CHAIN, TYPE_BALANCER -> false else -> true } } fun toLink(): String? = with(requireBean()) { when (this) { is SOCKSBean -> toUri() is HttpBean -> toUri() is ShadowsocksBean -> toUri() is ShadowsocksRBean -> toUri() is VMessBean -> toUri() is VLESSBean -> toUri() is TrojanBean -> toUri() is NaiveBean -> toUri() is Hysteria2Bean -> toUri() is JuicityBean -> toUri() is Tuic5Bean -> toUri() is MieruBean -> toUri() is Http3Bean -> toUri() is AnyTLSBean -> toUri() is TrustTunnelBean -> toUri() is ShadowQUICBean -> toUri() is OLCRTCBean -> toUri() else -> null } } fun exportConfig(): Pair { var name = "${displayName()}.json" return with(requireBean()) { StringBuilder().apply { val config = buildV2RayConfig(this@ProxyEntity, forExport = true) append(config.config) if (!config.index.all { it.chain.isEmpty() }) { name = "${displayName()}.txt" } for ((_, chain) in config.index) { chain.entries.forEachIndexed { _, (triple, profile) -> val port = triple.first val username = triple.second val password = triple.third when (val bean = profile.requireBean()) { is NaiveBean -> { append("\n\n") append(bean.buildNaiveConfig(port, username, password)) } is ShadowQUICBean -> { append("\n\n") append(bean.buildShadowQUICConfig(port, username, password, forExport = true)) } } } } }.toString() } to name } fun needExternal(): Boolean { return when (type) { TYPE_NAIVE -> true TYPE_SHADOWQUIC -> true TYPE_OLCRTC -> true else -> false } } fun putBean(bean: AbstractBean): ProxyEntity { socksBean = null httpBean = null ssBean = null ssrBean = null vmessBean = null vlessBean = null trojanBean = null naiveBean = null hysteria2Bean = null sshBean = null wgBean = null mieruBean = null tuic5Bean = null juicityBean = null http3Bean = null anytlsBean = null shadowquicBean = null trustTunnelBean = null snellBean = null olcrtcBean = null configBean = null chainBean = null balancerBean = null when (bean) { is SOCKSBean -> { type = TYPE_SOCKS socksBean = bean } is HttpBean -> { type = TYPE_HTTP httpBean = bean } is ShadowsocksBean -> { type = TYPE_SS ssBean = bean } is ShadowsocksRBean -> { type = TYPE_SSR ssrBean = bean } is VMessBean -> { type = TYPE_VMESS vmessBean = bean } is VLESSBean -> { type = TYPE_VLESS vlessBean = bean } is TrojanBean -> { type = TYPE_TROJAN trojanBean = bean } is NaiveBean -> { type = TYPE_NAIVE naiveBean = bean } is Hysteria2Bean -> { type = TYPE_HYSTERIA2 hysteria2Bean = bean } is SSHBean -> { type = TYPE_SSH sshBean = bean } is WireGuardBean -> { type = TYPE_WG wgBean = bean } is MieruBean -> { type = TYPE_MIERU mieruBean = bean } is Tuic5Bean -> { type = TYPE_TUIC5 tuic5Bean = bean } is JuicityBean -> { type = TYPE_JUICITY juicityBean = bean } is Http3Bean -> { type = TYPE_HTTP3 http3Bean = bean } is AnyTLSBean -> { type = TYPE_ANYTLS anytlsBean = bean } is ShadowQUICBean -> { type = TYPE_SHADOWQUIC shadowquicBean = bean } is TrustTunnelBean -> { type = TYPE_TRUSTTUNNEL trustTunnelBean = bean } is SnellBean -> { type = TYPE_SNELL snellBean = bean } is OLCRTCBean -> { type = TYPE_OLCRTC olcrtcBean = bean } is ConfigBean -> { type = TYPE_CONFIG configBean = bean } is ChainBean -> { type = TYPE_CHAIN chainBean = bean } is BalancerBean -> { type = TYPE_BALANCER balancerBean = bean } else -> error("Undefined type $type") } return this } fun settingIntent(ctx: Context, isSubscription: Boolean): Intent? { val cls = when (type) { TYPE_SOCKS -> SocksSettingsActivity::class.java TYPE_HTTP -> HttpSettingsActivity::class.java TYPE_SS -> ShadowsocksSettingsActivity::class.java TYPE_SSR -> ShadowsocksRSettingsActivity::class.java TYPE_VMESS -> VMessSettingsActivity::class.java TYPE_VLESS -> VLESSSettingsActivity::class.java TYPE_TROJAN -> TrojanSettingsActivity::class.java TYPE_NAIVE -> NaiveSettingsActivity::class.java TYPE_HYSTERIA2 -> Hysteria2SettingsActivity::class.java TYPE_SSH -> SSHSettingsActivity::class.java TYPE_WG -> WireGuardSettingsActivity::class.java TYPE_MIERU -> MieruSettingsActivity::class.java TYPE_TUIC5 -> Tuic5SettingsActivity::class.java TYPE_JUICITY -> JuicitySettingsActivity::class.java TYPE_HTTP3 -> Http3SettingsActivity::class.java TYPE_ANYTLS -> AnyTLSSettingsActivity::class.java TYPE_SHADOWQUIC -> ShadowQUICSettingsActivity::class.java TYPE_TRUSTTUNNEL -> TrustTunnelSettingsActivity::class.java TYPE_SNELL -> SnellSettingsActivity::class.java TYPE_OLCRTC -> io.nekohasekai.sagernet.ui.profile.OLCRTCSettingsActivity::class.java TYPE_CONFIG -> ConfigSettingsActivity::class.java TYPE_CHAIN -> ChainSettingsActivity::class.java TYPE_BALANCER -> BalancerSettingsActivity::class.java else -> return null } return Intent( ctx, cls ).apply { putExtra(ProfileSettingsActivity.EXTRA_PROFILE_ID, id) putExtra(ProfileSettingsActivity.EXTRA_IS_SUBSCRIPTION, isSubscription) } } @androidx.room.Dao interface Dao { @Query("select * from proxy_entities") fun getAll(): List @Query("SELECT id FROM proxy_entities WHERE groupId = :groupId ORDER BY userOrder") fun getIdsByGroup(groupId: Long): List @Query("SELECT * FROM proxy_entities WHERE groupId = :groupId ORDER BY userOrder") fun getByGroup(groupId: Long): List @Query("SELECT * FROM proxy_entities WHERE id in (:proxyIds)") fun getEntities(proxyIds: List): List @Query("SELECT COUNT(*) FROM proxy_entities WHERE groupId = :groupId") fun countByGroup(groupId: Long): Long @Query("SELECT MAX(userOrder) + 1 FROM proxy_entities WHERE groupId = :groupId") fun nextOrder(groupId: Long): Long? @Query("SELECT * FROM proxy_entities WHERE id = :proxyId") fun getById(proxyId: Long): ProxyEntity? @Query("SELECT COUNT(*) FROM proxy_entities WHERE groupId = :groupId AND id = :proxyId LIMIT 1") fun isIdInGroup(proxyId: Long, groupId: Long): Long @Query("DELETE FROM proxy_entities WHERE id IN (:proxyId)") fun deleteById(proxyId: Long): Int @Query("DELETE FROM proxy_entities WHERE groupId = :groupId") fun deleteByGroup(groupId: Long) @Query("DELETE FROM proxy_entities WHERE groupId in (:groupId)") fun deleteByGroup(groupId: LongArray) @Delete fun deleteProxy(proxy: ProxyEntity): Int @Delete fun deleteProxy(proxies: List): Int @Update fun updateProxy(proxy: ProxyEntity): Int @Update fun updateProxy(proxies: List): Int @Insert fun addProxy(proxy: ProxyEntity): Long @Insert fun insert(proxies: List) @Query("DELETE FROM proxy_entities WHERE groupId = :groupId") fun deleteAll(groupId: Long): Int @Query("DELETE FROM proxy_entities") fun reset() /** * Though UI disallow edit config when it is running, * but like chain and front/landing proxy still can be edited when running. * This can just update the traffic of a proxy entity when not influence other settings. */ @Query( """ UPDATE proxy_entities SET tx = CASE WHEN :tx IS NULL THEN tx ELSE :tx END, rx = CASE WHEN :rx IS NULL THEN rx ELSE :rx END WHERE id = :id """ ) fun updateTraffic(id: Long, tx: Long?, rx: Long?): Int } override fun describeContents(): Int { return 0 } }