[WIP] refactor services

This commit is contained in:
Kr328 committed 2020-01-20 12:09:11 +08:00
1 parent e979bec321
commit 419c6301dd
11 files changed
+200 -97

No files matched your search

+1 -2
View File
@@ -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'
}
+15 -3
View File
@@ -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