From 419c6301dd78e49fceff8bd38b87f90a8c9b2808 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Mon, 20 Jan 2020 12:09:11 +0800 Subject: [PATCH] [WIP] refactor services --- app/build.gradle | 3 +- core/src/main/golang/profile/download.go | 18 ++++- .../java/com/github/kr328/clash/core/Clash.kt | 41 ++-------- .../kr328/clash/service/IClashManager.aidl | 8 +- .../clash/service/IClashProfileManager.aidl | 9 +++ .../kr328/clash/service/ClashManager.kt | 80 +++++++++++-------- .../clash/service/ClashProfileManager.kt | 24 ++++++ .../kr328/clash/service/data/ClashDatabase.kt | 4 +- .../service/data/ClashDatabaseMigrations.kt | 71 ++++++++++++++++ .../clash/service/data/ClashProfileDao.kt | 3 + .../clash/service/data/ClashProfileEntity.kt | 36 +++++---- 11 files changed, 200 insertions(+), 97 deletions(-) create mode 100644 service/src/main/aidl/com/github/kr328/clash/service/IClashProfileManager.aidl create mode 100644 service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt create mode 100644 service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt diff --git a/app/build.gradle b/app/build.gradle index 9beda17..4b7af1d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } diff --git a/core/src/main/golang/profile/download.go b/core/src/main/golang/profile/download.go index e5e1c21..19fbd49 100644 --- a/core/src/main/golang/profile/download.go +++ b/core/src/main/golang/profile/download.go @@ -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) +} diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt index b19430e..df0d808 100644 --- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt +++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt @@ -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 { - enforceStarted() - return DoneCallbackImpl().apply { Bridge.loadProfileFile(path, baseDir, this) } @@ -101,8 +80,6 @@ class Clash( } fun queryProxyGroups(): List { - 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" } - } } \ No newline at end of file diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl index ca5b98d..4f9f9fe 100644 --- a/service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl +++ b/service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl @@ -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); } diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashProfileManager.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashProfileManager.aidl new file mode 100644 index 0000000..63d05ab --- /dev/null +++ b/service/src/main/aidl/com/github/kr328/clash/service/IClashProfileManager.aidl @@ -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(); +} diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt b/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt index 6061070..9fbd7cc 100644 --- a/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt +++ b/service/src/main/java/com/github/kr328/clash/service/ClashManager.kt @@ -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 { - 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, "")!! + } } \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt b/service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt new file mode 100644 index 0000000..428bbe7 --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/ClashProfileManager.kt @@ -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. + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt index 42944d7..9eef229 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt @@ -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() } } diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt new file mode 100644 index 0000000..5a8ba2b --- /dev/null +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabaseMigrations.kt @@ -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() + + 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() + } + } +} \ No newline at end of file diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileDao.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileDao.kt index 2a4f7d8..f55744b 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileDao.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileDao.kt @@ -16,6 +16,9 @@ interface ClashProfileDao { @Query("SELECT * FROM profiles") fun queryProfiles(): Array + @Query("SELECT * FROM profiles WHERE id = :id") + fun queryProfileById(id: Int): ClashProfileEntity? + @Insert(onConflict = OnConflictStrategy.REPLACE) fun addProfile(profile: ClashProfileEntity) diff --git a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt index 66bcd8d..3539961 100644 --- a/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt +++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt @@ -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