From d0aa037b4c60203bf160f73c3baf6d70baa9e265 Mon Sep 17 00:00:00 2001
From: Kr328 <39107975+Kr328@users.noreply.github.com>
Date: Wed, 20 Nov 2019 22:48:59 +0800
Subject: [PATCH] add database
---
app/build.gradle | 8 ++-
.../com/github/kr328/clash/MainActivity.kt | 5 --
app/src/main/res/layout/activity_main.xml | 12 +---
core/build.gradle | 2 +-
.../java/com/github/kr328/clash/core/Clash.kt | 5 +-
service/build.gradle | 9 ++-
.../kr328/clash/service/IClashService.aidl | 2 +
.../kr328/clash/service/ClashService.kt | 62 ++++++++++++++++++-
.../github/kr328/clash/service/TunService.kt | 3 +-
.../kr328/clash/service/data/ClashDatabase.kt | 26 ++++++++
.../clash/service/data/ClashProfileDao.kt | 14 +++++
.../clash/service/data/ClashProfileEntity.kt | 10 +++
12 files changed, 134 insertions(+), 24 deletions(-)
create mode 100644 service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt
create mode 100644 service/src/main/java/com/github/kr328/clash/service/data/ClashProfileDao.kt
create mode 100644 service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt
diff --git a/app/build.gradle b/app/build.gradle
index 341e6f0..b74b6d9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,13 +1,14 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.github.kr328.clash"
- minSdkVersion 23
+ minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName "1.0"
@@ -21,10 +22,15 @@ android {
}
dependencies {
+ kapt "androidx.room:room-compiler:$room_version"
+
implementation project(":core")
implementation project(":service")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.core:core-ktx:1.1.0"
+ implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
+ implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
+ implementation "androidx.room:room-runtime:$room_version"
}
diff --git a/app/src/main/java/com/github/kr328/clash/MainActivity.kt b/app/src/main/java/com/github/kr328/clash/MainActivity.kt
index 3cb1ec7..4d52150 100644
--- a/app/src/main/java/com/github/kr328/clash/MainActivity.kt
+++ b/app/src/main/java/com/github/kr328/clash/MainActivity.kt
@@ -4,11 +4,6 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
- companion object {
- private const val REQUEST_CODE = 233
- private const val VPN_REQUEST = 234
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index b1f1561..55707ad 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -6,13 +6,5 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
-
-
\ No newline at end of file
+
+
diff --git a/core/build.gradle b/core/build.gradle
index 025aa05..726680b 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -7,7 +7,7 @@ android {
buildToolsVersion "29.0.2"
defaultConfig {
- minSdkVersion 23
+ minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName "1.0"
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 94fe2d7..97171aa 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
@@ -40,7 +40,10 @@ class Clash(
@Throws(IOException::class)
@Synchronized
- fun loadProfile(url: Uri) {
+ fun loadProfile(url: Uri?) {
+ if ( url == null || url.scheme != "content" )
+ return
+
synchronized(cacheFile) {
context.contentResolver.openInputStream(url)?.use {
it.copyTo(FileOutputStream(cacheFile))
diff --git a/service/build.gradle b/service/build.gradle
index d9a8cc6..8a5c61d 100644
--- a/service/build.gradle
+++ b/service/build.gradle
@@ -9,7 +9,7 @@ android {
defaultConfig {
- minSdkVersion 23
+ minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName "1.0"
@@ -24,13 +24,18 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
-
+ compileOptions {
+ targetCompatibility = 1.8
+ sourceCompatibility = 1.8
+ }
}
dependencies {
kapt "androidx.room:room-compiler:$room_version"
implementation project(":core")
+ implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
+ implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.core:core-ktx:1.1.0"
implementation "androidx.room:room-runtime:$room_version"
diff --git a/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl b/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl
index 18f67a6..271a1b6 100644
--- a/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl
+++ b/service/src/main/aidl/com/github/kr328/clash/service/IClashService.aidl
@@ -1,10 +1,12 @@
package com.github.kr328.clash.service;
import com.github.kr328.clash.service.IClashObserver;
+import com.github.kr328.clash.core.ClashProcessStatus;
interface IClashService {
void start();
void stop();
+ ClashProcessStatus getClashProcessStatus();
void registerObserver(String id, boolean notifyCurrent, in IClashObserver observer);
void unregisterObserver(String id);
void startTunDevice(in ParcelFileDescriptor fd, int mtu);
diff --git a/service/src/main/java/com/github/kr328/clash/service/ClashService.kt b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt
index 584ae07..68ce0ba 100644
--- a/service/src/main/java/com/github/kr328/clash/service/ClashService.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/ClashService.kt
@@ -2,25 +2,32 @@ package com.github.kr328.clash.service
import android.app.Service
import android.content.Intent
+import android.net.Uri
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.os.RemoteException
+import android.util.Log
+import androidx.lifecycle.Observer
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.ClashProcessStatus
+import com.github.kr328.clash.service.data.ClashDatabase
import java.io.IOException
+import java.util.concurrent.Executors
class ClashService : Service() {
companion object {
private val TAG = "ClashForAndroid"
}
+ private val executor = Executors.newSingleThreadExecutor()
private lateinit var clash: Clash
+ private var lastProfile: String? = null
private var status = ClashProcessStatus(ClashProcessStatus.STATUS_STOPPED)
set(value) {
field = value
- for ( observer in observers.values ) {
+ for (observer in observers.values) {
runCatching {
observer.onStatusChanged(status)
}
@@ -28,14 +35,45 @@ class ClashService : Service() {
}
private val observers = mutableMapOf()
+ private val defaultProfileObserver = object: Observer {
+ override fun onChanged(url: String?) {
+ executor.submit {
+ if (url == null)
+ clash.stop()
+ if (url == lastProfile)
+ return@submit
+ try {
+ clash.loadProfile(Uri.parse(url))
+ lastProfile = url
+ } catch (e: IOException) {
+ Log.w(TAG, "Load profile failure")
+
+ if (lastProfile == null) {
+ clash.stop()
+ } else {
+ val p = lastProfile
+ lastProfile = null
+ ClashDatabase.getInstance(this@ClashService)
+ .openClashProfileDao()
+ .setDefaultProfile(p ?: "")
+ }
+ }
+ }
+ }
+ }
+
private inner class ClashServiceImpl : IClashService.Stub() {
- override fun registerObserver(id: String?, notifyCurrent: Boolean, observer: IClashObserver?) {
+ override fun registerObserver(
+ id: String?,
+ notifyCurrent: Boolean,
+ observer: IClashObserver?
+ ) {
if (id == null || observer == null)
throw RemoteException()
observers[id] = observer
- if ( notifyCurrent )
+ if (notifyCurrent)
observer.onStatusChanged(status)
@@ -67,6 +105,10 @@ class ClashService : Service() {
clash.stop()
}
+ override fun getClashProcessStatus(): ClashProcessStatus {
+ return status
+ }
+
override fun startTunDevice(fd: ParcelFileDescriptor, mtu: Int) {
try {
clash.startTunDevice(fd.fileDescriptor, mtu)
@@ -85,6 +127,14 @@ class ClashService : Service() {
cacheDir.resolve("clash_controller")
) {
this.status = it
+
+ when (it.status) {
+ ClashProcessStatus.STATUS_STARTED ->
+ ClashDatabase.getInstance(this)
+ .openClashProfileDao()
+ .observeDefaultProfileUrl()
+ .observeForever(defaultProfileObserver)
+ }
}
}
@@ -94,6 +144,12 @@ class ClashService : Service() {
override fun onDestroy() {
clash.stop()
+ executor.shutdown()
+
+ ClashDatabase.getInstance(this)
+ .openClashProfileDao()
+ .observeDefaultProfileUrl()
+ .removeObserver(defaultProfileObserver)
super.onDestroy()
}
diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt
index 59d8596..f852b38 100644
--- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt
@@ -31,7 +31,7 @@ class TunService : VpnService() {
service
) ?: throw NullPointerException()
- clash.start()
+
clash.registerObserver("tun", false, object: IClashObserver.Stub() {
override fun onStatusChanged(status: ClashProcessStatus?) {
if ( status == null )
@@ -45,6 +45,7 @@ class TunService : VpnService() {
}
}
})
+ clash.start()
}
}
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
new file mode 100644
index 0000000..776a2e7
--- /dev/null
+++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashDatabase.kt
@@ -0,0 +1,26 @@
+package com.github.kr328.clash.service.data
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import java.lang.NullPointerException
+
+@Database(version = 1, exportSchema = false, entities = [ClashProfileEntity::class])
+abstract class ClashDatabase : RoomDatabase() {
+ abstract fun openClashProfileDao(): ClashProfileDao
+
+ companion object {
+ private var instance: ClashDatabase? = null
+
+ fun getInstance(context: Context): ClashDatabase {
+ if ( instance == null )
+ instance = Room.databaseBuilder(context.applicationContext,
+ ClashDatabase::class.java,
+ "clash-config")
+ .enableMultiInstanceInvalidation()
+ .build()
+ return instance ?: throw NullPointerException()
+ }
+ }
+}
\ 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
new file mode 100644
index 0000000..071b86e
--- /dev/null
+++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileDao.kt
@@ -0,0 +1,14 @@
+package com.github.kr328.clash.service.data
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Query
+
+@Dao
+interface ClashProfileDao {
+ @Query("UPDATE profiles SET `default` = CASE WHEN url = :url THEN 1 ELSE 0 END")
+ fun setDefaultProfile(url: String)
+
+ @Query("SELECT url FROM profiles WHERE `default` = 1 LIMIT 1")
+ fun observeDefaultProfileUrl(): LiveData
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..dafabf2
--- /dev/null
+++ b/service/src/main/java/com/github/kr328/clash/service/data/ClashProfileEntity.kt
@@ -0,0 +1,10 @@
+package com.github.kr328.clash.service.data
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+
+@Entity(tableName = "profiles", primaryKeys = ["url"])
+data class ClashProfileEntity(@ColumnInfo(name = "url") val url: String,
+ @ColumnInfo(name = "default") val defaultProfile: Boolean,
+ @ColumnInfo(name = "writable") val writable: Boolean,
+ @ColumnInfo(name = "last_update") val lastUpdate: Long)
\ No newline at end of file