[WIP] refactor services
This commit is contained in:
11 files changed
+200
-97
No files matched your search
+1
-2
@@ -53,8 +53,7 @@ dependencies {
|
||||
implementation "androidx.preference:preference-ktx:1.1.0"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
|
||||
implementation "com.charleskorn.kaml:kaml:0.14.0"
|
||||
implementation "com.google.android.material:material:1.2.0-alpha03"
|
||||
implementation 'com.google.firebase:firebase-analytics:17.2.1'
|
||||
implementation 'com.google.firebase:firebase-analytics:17.2.2'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func DownloadAndCheck(url, output, baseDir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = config.Parse(data)
|
||||
_, err = parseConfig(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func SaveAndCheck(data []byte, output, baseDir string) error {
|
||||
constant.SetHomeDir(baseDir)
|
||||
defer constant.SetHomeDir(original)
|
||||
|
||||
_, err := config.Parse(data)
|
||||
_, err := parseConfig(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func MoveAndCheck(source, target, baseDir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = config.Parse(buf)
|
||||
_, err = parseConfig(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -104,3 +104,15 @@ func MoveAndCheck(source, target, baseDir string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseConfig(data []byte) (*config.Config, error) {
|
||||
raw, err := config.UnmarshalRawConfig(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw.ExternalUI = ""
|
||||
raw.ExternalController = ""
|
||||
|
||||
return config.ParseRawConfig(raw)
|
||||
}
|
||||
@@ -15,48 +15,31 @@ import java.lang.IllegalStateException
|
||||
import java.util.concurrent.BrokenBarrierException
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class Clash(
|
||||
context: Context,
|
||||
private val listener: (ProcessEvent) -> Unit
|
||||
) {
|
||||
object Clash{
|
||||
private var initialized = false
|
||||
|
||||
class Poll(private val poll: EventPoll) {
|
||||
fun stop() {
|
||||
poll.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private var currentProcess = ProcessEvent.STOPPED
|
||||
fun initialize(context: Context) {
|
||||
if ( initialized )
|
||||
return
|
||||
initialized = true
|
||||
|
||||
init {
|
||||
val country = context.assets.open("Country.mmdb")
|
||||
.use(InputStream::readBytes)
|
||||
|
||||
Bridge.loadMMDB(country)
|
||||
}
|
||||
|
||||
fun getCurrentProcessStatus(): ProcessEvent {
|
||||
return currentProcess
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (currentProcess == ProcessEvent.STARTED)
|
||||
return
|
||||
|
||||
currentProcess = ProcessEvent.STARTED
|
||||
|
||||
listener(currentProcess)
|
||||
|
||||
Bridge.reset()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (currentProcess == ProcessEvent.STOPPED)
|
||||
return
|
||||
|
||||
currentProcess = ProcessEvent.STOPPED
|
||||
|
||||
listener(currentProcess)
|
||||
|
||||
Bridge.reset()
|
||||
}
|
||||
|
||||
@@ -65,8 +48,6 @@ class Clash(
|
||||
mtu: Int,
|
||||
dns: String
|
||||
) {
|
||||
enforceStarted()
|
||||
|
||||
Bridge.startTunDevice(fd.toLong(), mtu.toLong(), dns)
|
||||
}
|
||||
|
||||
@@ -75,8 +56,6 @@ class Clash(
|
||||
}
|
||||
|
||||
fun loadProfile(path: String, baseDir: String): CompletableFuture<Unit> {
|
||||
enforceStarted()
|
||||
|
||||
return DoneCallbackImpl().apply {
|
||||
Bridge.loadProfileFile(path, baseDir, this)
|
||||
}
|
||||
@@ -101,8 +80,6 @@ class Clash(
|
||||
}
|
||||
|
||||
fun queryProxyGroups(): List<ProxyGroup> {
|
||||
enforceStarted()
|
||||
|
||||
return ProxyGroupCollectionImpl().also { Bridge.queryAllProxyGroups(it) }
|
||||
.filterNotNull()
|
||||
.map { group ->
|
||||
@@ -172,8 +149,4 @@ class Clash(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun enforceStarted() {
|
||||
check(currentProcess != ProcessEvent.STOPPED) { "Clash Stopped" }
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,11 @@ interface IClashManager {
|
||||
ProxyGroup[] queryAllProxies();
|
||||
General queryGeneral();
|
||||
|
||||
// Profiles
|
||||
ParcelableCompletedFuture addProfile(String url);
|
||||
ParcelableCompletedFuture updateProfile(int id);
|
||||
ParcelableCompletedFuture queryAllProfiles();
|
||||
|
||||
// Events
|
||||
ParcelablePipe openBandwidthEvent();
|
||||
ParcelablePipe openLogEvent();
|
||||
|
||||
// Settings
|
||||
|
||||
boolean putSetting(String key, String value);
|
||||
String getSetting(String key);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.github.kr328.clash.service;
|
||||
|
||||
import com.github.kr328.clash.service.ipc.IPCParcelables;
|
||||
|
||||
interface IClashProfileManager {
|
||||
ParcelableCompletedFuture addProfile(String url);
|
||||
ParcelableCompletedFuture updateProfile(int id);
|
||||
ParcelableCompletedFuture queryAllProfiles();
|
||||
}
|
||||
@@ -1,58 +1,34 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.ProxyGroup
|
||||
import com.github.kr328.clash.service.ipc.ParcelableCompletedFuture
|
||||
import com.github.kr328.clash.service.ipc.ParcelablePipe
|
||||
|
||||
class ClashManager(val clash: Clash): IClashManager.Stub() {
|
||||
override fun updateProfile(id: Int): ParcelableCompletedFuture {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun addProfile(url: String?): ParcelableCompletedFuture {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
class ClashManager(context: Context): IClashManager.Stub() {
|
||||
private val settings = context.getSharedPreferences("service", Context.MODE_PRIVATE)
|
||||
|
||||
override fun queryAllProxies(): Array<ProxyGroup> {
|
||||
return clash.queryProxyGroups().toTypedArray()
|
||||
return Clash.queryProxyGroups().toTypedArray()
|
||||
}
|
||||
|
||||
override fun queryGeneral(): General {
|
||||
return clash.queryGeneral()
|
||||
return Clash.queryGeneral()
|
||||
}
|
||||
|
||||
override fun setSelectProxy(proxy: String?, selected: String?): Boolean {
|
||||
require(proxy != null && selected != null)
|
||||
|
||||
return clash.setSelectedProxy(proxy, selected)
|
||||
return Clash.setSelectedProxy(proxy, selected)
|
||||
}
|
||||
|
||||
override fun openBandwidthEvent(): ParcelablePipe {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun queryAllProfiles(): ParcelableCompletedFuture {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun startHealthCheck(group: String?): ParcelableCompletedFuture {
|
||||
require(group != null)
|
||||
|
||||
return ParcelableCompletedFuture().apply {
|
||||
clash.startHealthCheck(group).whenComplete { _: Unit?, u: Throwable? ->
|
||||
if ( u != null )
|
||||
this.completeExceptionally(u)
|
||||
else
|
||||
this.complete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun openLogEvent(): ParcelablePipe {
|
||||
return object: ParcelablePipe() {
|
||||
val stream = clash.openLogEvent().apply {
|
||||
val stream = Clash.openBandwidthEvent().apply {
|
||||
onEvent {
|
||||
send(it)
|
||||
}
|
||||
@@ -63,4 +39,42 @@ class ClashManager(val clash: Clash): IClashManager.Stub() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startHealthCheck(group: String?): ParcelableCompletedFuture {
|
||||
require(group != null)
|
||||
|
||||
return ParcelableCompletedFuture().apply {
|
||||
Clash.startHealthCheck(group).whenComplete { _: Unit?, u: Throwable? ->
|
||||
if ( u != null )
|
||||
this.completeExceptionally(u)
|
||||
else
|
||||
this.complete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun openLogEvent(): ParcelablePipe {
|
||||
return object: ParcelablePipe() {
|
||||
val stream = Clash.openLogEvent().apply {
|
||||
onEvent {
|
||||
send(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClose() {
|
||||
stream.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun putSetting(key: String?, value: String?): Boolean {
|
||||
settings.edit {
|
||||
putString(key, value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getSetting(key: String?): String {
|
||||
return settings.getString(key, "")!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
import com.github.kr328.clash.service.data.ClashProfileEntity
|
||||
import com.github.kr328.clash.service.ipc.ParcelableCompletedFuture
|
||||
|
||||
class ClashProfileManager(private val database: ClashDatabase): IClashProfileManager.Stub() {
|
||||
override fun updateProfile(id: Int): ParcelableCompletedFuture {
|
||||
val entity = database.openClashProfileDao().queryProfileById(id)
|
||||
|
||||
require(entity != null && entity.type == ClashProfileEntity.Type.URL)
|
||||
|
||||
val result = ParcelableCompletedFuture()
|
||||
}
|
||||
|
||||
override fun queryAllProfiles(): ParcelableCompletedFuture {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun addProfile(url: String?): ParcelableCompletedFuture {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@Database(version = 1, exportSchema = false, entities = [ClashProfileEntity::class, ClashProfileProxyEntity::class])
|
||||
@Database(version = 2, exportSchema = false, entities = [ClashProfileEntity::class, ClashProfileProxyEntity::class])
|
||||
abstract class ClashDatabase : RoomDatabase() {
|
||||
abstract fun openClashProfileDao(): ClashProfileDao
|
||||
abstract fun openClashProfileProxyDao(): ClashProfileProxyDao
|
||||
@@ -19,7 +19,7 @@ abstract class ClashDatabase : RoomDatabase() {
|
||||
context.applicationContext,
|
||||
ClashDatabase::class.java,
|
||||
"clash-config"
|
||||
).build()
|
||||
).addMigrations(ClashDatabaseMigrations.VERSION_1_2).build()
|
||||
return instance ?: throw NullPointerException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.github.kr328.clash.service.data
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import java.security.SecureRandom
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
object ClashDatabaseMigrations {
|
||||
val VERSION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.beginTransaction()
|
||||
|
||||
database.execSQL("ALTER TABLE profiles RENAME TO _profiles")
|
||||
|
||||
database.execSQL("CREATE TABLE profiles(" +
|
||||
"name TEXT NOT NULL, " +
|
||||
"type INTEGER NOT NULL, " +
|
||||
"uri TEXT NOT NULL, " +
|
||||
"file TEXT NOT NULL, " +
|
||||
"base TEXT NOT NULL" +
|
||||
"active INTEGER NOT NULL, " +
|
||||
"last_update INTEGER NOT NULL, " +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)")
|
||||
|
||||
val cursor = database.query("SELECT name, token, file, active, last_update FROM _profile")
|
||||
val random = SecureRandom()
|
||||
val bases = mutableSetOf<Long>()
|
||||
|
||||
cursor.moveToFirst()
|
||||
while ( !cursor.isAfterLast ) {
|
||||
// old
|
||||
val name = cursor.getString(0)
|
||||
val token = cursor.getString(1)
|
||||
val file = cursor.getString(2)
|
||||
val active = cursor.getInt(3)
|
||||
val lastUpdate = cursor.getLong(4)
|
||||
|
||||
// new
|
||||
val type = when {
|
||||
token.startsWith("url") -> ClashProfileEntity.Type.URL.id
|
||||
token.startsWith("file") -> ClashProfileEntity.Type.FILE.id
|
||||
else -> ClashProfileEntity.Type.UNKNOWN.id
|
||||
}
|
||||
val uri = token.removePrefix("url|").removePrefix("file|")
|
||||
var base = random.nextLong().absoluteValue
|
||||
|
||||
while ( bases.contains(base) )
|
||||
base = random.nextLong().absoluteValue
|
||||
|
||||
database.insert("profiles",
|
||||
SQLiteDatabase.CONFLICT_ABORT,
|
||||
ContentValues().apply {
|
||||
put("name", name)
|
||||
put("type", type)
|
||||
put("uri", uri)
|
||||
put("file", file)
|
||||
put("active", active)
|
||||
put("last_update", lastUpdate)
|
||||
put("base", base.toString())
|
||||
})
|
||||
}
|
||||
cursor.close()
|
||||
|
||||
database.execSQL("DROP TABLE _profiles")
|
||||
|
||||
database.endTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ interface ClashProfileDao {
|
||||
@Query("SELECT * FROM profiles")
|
||||
fun queryProfiles(): Array<ClashProfileEntity>
|
||||
|
||||
@Query("SELECT * FROM profiles WHERE id = :id")
|
||||
fun queryProfileById(id: Int): ClashProfileEntity?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun addProfile(profile: ClashProfileEntity)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Parcelable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverter
|
||||
import com.github.kr328.clash.core.serialization.Parcels
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -12,12 +13,18 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class ClashProfileEntity(
|
||||
@ColumnInfo(name = "name") val name: String,
|
||||
@ColumnInfo(name = "token") val token: String,
|
||||
@ColumnInfo(name = "type") val type: Type,
|
||||
@ColumnInfo(name = "uri") val uri: String,
|
||||
@ColumnInfo(name = "file") val file: String,
|
||||
@ColumnInfo(name = "base") val base: String,
|
||||
@ColumnInfo(name = "active") val active: Boolean,
|
||||
@ColumnInfo(name = "last_update") val lastUpdate: Long,
|
||||
@ColumnInfo(name = "id") @PrimaryKey(autoGenerate = true) val id: Int = 0
|
||||
) : Parcelable {
|
||||
enum class Type(val id: Int) {
|
||||
URL(1), FILE(2), UNKNOWN(-1)
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
Parcels.dump(serializer(), this, parcel)
|
||||
}
|
||||
@@ -27,24 +34,19 @@ data class ClashProfileEntity(
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fileToken(content: String): String {
|
||||
return "file|$content"
|
||||
@TypeConverter
|
||||
fun typeToInt(value: Type?): Int {
|
||||
return value?.id ?: -1
|
||||
}
|
||||
|
||||
fun urlToken(content: String): String {
|
||||
return "url|$content"
|
||||
}
|
||||
|
||||
fun isFileToken(token: String): Boolean {
|
||||
return token.startsWith("file|")
|
||||
}
|
||||
|
||||
fun isUrlToken(token: String): Boolean {
|
||||
return token.startsWith("url|")
|
||||
}
|
||||
|
||||
fun getUrl(token: String): String {
|
||||
return token.removePrefix("url|")
|
||||
@TypeConverter
|
||||
fun intToType(value: Int?): Type {
|
||||
return when ( value ) {
|
||||
Type.URL.id -> Type.URL
|
||||
Type.FILE.id -> Type.FILE
|
||||
Type.UNKNOWN.id -> Type.UNKNOWN
|
||||
else -> Type.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
|
||||
Reference in new issue
Block a user