add database

This commit is contained in:
Kr328 committed 2019-11-20 22:48:59 +08:00
1 parent 792190b5f4
commit d0aa037b4c
12 files changed
+134 -24

No files matched your search

+7 -1
View File
@@ -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"
}
@@ -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)
+2 -10
View File
@@ -6,13 +6,5 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+1 -1
View File
@@ -7,7 +7,7 @@ android {
buildToolsVersion "29.0.2"
defaultConfig {
minSdkVersion 23
minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName "1.0"
@@ -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))
+7 -2
View File
@@ -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"
@@ -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);
@@ -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<String, IClashObserver>()
private val defaultProfileObserver = object: Observer<String?> {
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()
}
@@ -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()
}
}
@@ -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()
}
}
}
@@ -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<String?>
}
@@ -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)